Repository: mrlitong/Game-Engine-Development-Usage Branch: master Commit: 95697c4bfb50 Files: 394 Total size: 1.9 MB Directory structure: gitextract_cgk0ykbb/ ├── .gitignore ├── LICENSE ├── README.md ├── Test/ │ ├── ActorBash.cpp │ ├── ActorBash.h │ ├── CameraBash.cpp │ ├── CameraBash.h │ ├── Common.cpp │ ├── Common.h │ ├── FPSRole.cpp │ ├── FPSRole.h │ ├── FPSRoleLocal.cpp │ ├── FPSRoleLocal.h │ ├── GameMain.cpp │ ├── GameMain.h │ ├── GameProcess.cpp │ ├── GameProcess.h │ ├── HMDWrapper.cpp │ ├── HMDWrapper.h │ ├── MathLib.h │ ├── MoveDummy.cpp │ ├── MoveDummy.h │ ├── RayControl.cpp │ ├── RayControl.h │ ├── RoleBase.cpp │ ├── RoleBase.h │ ├── Test.vcxproj │ ├── main.cpp │ └── main.h ├── fpsgame/ │ ├── CommonConvert.cpp │ ├── CommonConvert.h │ ├── Decompose.cpp │ ├── Decompose.h │ ├── Dll.cpp │ ├── Dll.h │ ├── GeomReindex.cpp │ ├── GeomReindex.h │ ├── JSInterfaace_GameView.h │ ├── JSInterface_GameView.cpp │ ├── Math.cpp │ ├── Math.h │ ├── PMDConvert.cpp │ ├── PMDConvert.h │ ├── PSAConvert.cpp │ ├── PSAConvert.h │ ├── Resource.h │ ├── SkillSystem.cpp │ ├── SkillSystem.h │ ├── Star2DControl.cpp │ ├── Star2DControl.h │ ├── StartControl.cpp │ ├── StartControl.h │ ├── StdSkeletons.cpp │ ├── StdSkeletons.h │ ├── XMLFix.cpp │ ├── XMLFix.h │ ├── fpsgame.cpp │ ├── fpsgame.h │ ├── fpsgame.vcxproj │ ├── fpsgame.vcxproj.user │ ├── graphics/ │ │ ├── Camera.cpp │ │ ├── Camera.h │ │ ├── CinemaManager.cpp │ │ ├── CinemaManager.h │ │ ├── CinimaPath.cpp │ │ ├── CinimaPath.h │ │ ├── ColladaManager.cpp │ │ ├── ColladaManager.h │ │ ├── Color.cpp │ │ ├── Color.h │ │ ├── Decal.cpp │ │ ├── Decal.h │ │ ├── Entity.h │ │ ├── Font.cpp │ │ ├── Font.h │ │ ├── FontManager.cpp │ │ ├── FontManager.h │ │ ├── FontMetrics.cpp │ │ ├── FontMetrics.h │ │ ├── Frustum.cpp │ │ ├── Frustum.h │ │ ├── GameView.cpp │ │ ├── GameView.h │ │ ├── HFTracer.cpp │ │ ├── HFTracer.h │ │ ├── HeightMipmap.cpp │ │ ├── HeightMipmap.h │ │ ├── LOSTexture.cpp │ │ ├── LOSTexture.h │ │ ├── LightEnv.cpp │ │ ├── LightEnv.h │ │ ├── MapGenerator.cpp │ │ ├── MapGenerator.h │ │ ├── MapIO.h │ │ ├── MapReader.cpp │ │ ├── MapReader.h │ │ ├── MapWriter.cpp │ │ ├── MapWriter.h │ │ ├── Material.cpp │ │ ├── Material.h │ │ ├── MaterialManager.cpp │ │ ├── MaterialManager.h │ │ ├── MeshManager.cpp │ │ ├── MeshManager.h │ │ ├── MiniPatch.cpp │ │ ├── MiniPatch.h │ │ ├── Model.cpp │ │ ├── Model.h │ │ ├── ModelAbstract.cpp │ │ ├── ModelAbstract.h │ │ ├── ModelDef.cpp │ │ ├── ModelDef.h │ │ ├── ObjectBase.cpp │ │ ├── ObjectBase.h │ │ ├── ObjectEntry.cpp │ │ ├── ObjectEntry.h │ │ ├── ObjectManager.cpp │ │ ├── ObjectManager.h │ │ ├── Overlay.cpp │ │ ├── Overlay.h │ │ ├── ParticleEmitter.cpp │ │ ├── ParticleEmitter.h │ │ ├── ParticleEmitterType.cpp │ │ ├── ParticleEmitterType.h │ │ ├── ParticleManager.cpp │ │ ├── ParticleManager.h │ │ ├── Patch.cpp │ │ ├── Patch.h │ │ ├── RenderableObject.h │ │ ├── SColor.h │ │ ├── ShaderDefines.cpp │ │ ├── ShaderDefines.h │ │ ├── ShaderManager.cpp │ │ ├── ShaderManager.h │ │ ├── ShaderProgram.cpp │ │ ├── ShaderProgram.h │ │ ├── ShaderProgramFFP.cpp │ │ ├── ShaderProgramPtr.h │ │ ├── ShaderTechnique.cpp │ │ ├── ShaderTechnique.h │ │ ├── SkeletonAnim.h │ │ ├── SkeletonAnimDef.cpp │ │ ├── SkeletonAnimDef.h │ │ ├── SkeletonAnimManager.cpp │ │ ├── SkeletonAnimManager.h │ │ ├── Terrain.cpp │ │ ├── Terrain.h │ │ ├── TerrainProperties.cpp │ │ ├── TerrainProperties.h │ │ ├── TerrainTextureEntry.cpp │ │ ├── TerrainTextureEntry.h │ │ ├── TerrainTextureManager.cpp │ │ ├── TerrainTextureManager.h │ │ ├── TerritoryBoundary.cpp │ │ ├── TerritoryBoundary.h │ │ ├── TerritoryTexture.cpp │ │ ├── TerritoryTexture.h │ │ ├── TextRenderer.cpp │ │ ├── TextRenderer.h │ │ ├── Texture.h │ │ ├── TextureConverter.cpp │ │ ├── TextureConverter.h │ │ ├── TextureManager.cpp │ │ ├── TextureManager.h │ │ ├── Unit.cpp │ │ ├── Unit.h │ │ ├── UnitAnimation.cpp │ │ ├── UnitAnimation.h │ │ ├── UnitManager.cpp │ │ └── UnitManager.h │ ├── gui/ │ │ ├── adts/ │ │ │ ├── bit_buf.h │ │ │ ├── cache_adt.h │ │ │ ├── dyn_hash_tbl.h │ │ │ └── ring_buf.h │ │ ├── allocators/ │ │ │ ├── aligned_allocator.h │ │ │ ├── allocator_adapters.h │ │ │ ├── allocator_checker.h │ │ │ ├── allocator_policies.h │ │ │ ├── arena.cpp │ │ │ ├── arena.h │ │ │ ├── dynarray.cpp │ │ │ ├── dynarray.h │ │ │ ├── freelist.cpp │ │ │ ├── freelist.h │ │ │ ├── headerless.cpp │ │ │ ├── headerless.h │ │ │ ├── overrun_protector.h │ │ │ ├── page_aligned.cpp │ │ │ ├── page_aligned.h │ │ │ ├── pool.cpp │ │ │ ├── pool.h │ │ │ ├── shared_ptr.cpp │ │ │ ├── shared_ptr.h │ │ │ ├── tests/ │ │ │ │ ├── test_allocators.h │ │ │ │ └── test_headerless.h │ │ │ ├── unique_range.cpp │ │ │ └── unique_range.h │ │ ├── app_hooks.cpp │ │ ├── app_hooks.h │ │ ├── base32.cpp │ │ ├── base32.h │ │ ├── bits.cpp │ │ ├── bits.h │ │ ├── byte_order.cpp │ │ ├── byte_order.h │ │ ├── code_annotation.h │ │ ├── code_generation.h │ │ ├── config.h │ │ ├── config2.h │ │ ├── debug.cpp │ │ ├── debug.h │ │ ├── debug_stl.cpp │ │ ├── debug_stl.h │ │ ├── external_libraries/ │ │ │ ├── curl.h │ │ │ ├── dbghelp.cpp │ │ │ ├── dbghelp.h │ │ │ ├── dbghelp_funcs.h │ │ │ ├── enet.h │ │ │ ├── glext_funcs.h │ │ │ ├── icu.h │ │ │ ├── libsdl.h │ │ │ ├── libsdl_fwd.h │ │ │ ├── openal.h │ │ │ ├── opengl.h │ │ │ ├── openmp.h │ │ │ ├── png.h │ │ │ ├── powrprof.h │ │ │ ├── suppress_boost_warnings.h │ │ │ ├── tinygettext.h │ │ │ ├── vorbis.h │ │ │ ├── wxwidgets.h │ │ │ └── zlib.h │ │ ├── file/ │ │ │ ├── archive/ │ │ │ │ ├── archive.cpp │ │ │ │ ├── archive.h │ │ │ │ ├── archive_zip.cpp │ │ │ │ ├── archive_zip.h │ │ │ │ ├── codec.cpp │ │ │ │ ├── codec.h │ │ │ │ ├── codec_zlib.cpp │ │ │ │ ├── codec_zlib.h │ │ │ │ ├── disabled_tests/ │ │ │ │ │ ├── test_codec_zlib.h │ │ │ │ │ ├── test_compression.h │ │ │ │ │ ├── test_fat_time.h │ │ │ │ │ └── test_zip.h │ │ │ │ ├── stream.cpp │ │ │ │ └── stream.h │ │ │ ├── common/ │ │ │ │ ├── file_loader.cpp │ │ │ │ ├── file_loader.h │ │ │ │ ├── file_stats.cpp │ │ │ │ ├── file_stats.h │ │ │ │ ├── real_directory.cpp │ │ │ │ ├── real_directory.h │ │ │ │ ├── tests/ │ │ │ │ │ └── test_trace.h │ │ │ │ ├── trace.cpp │ │ │ │ └── trace.h │ │ │ ├── disabled_tests/ │ │ │ │ ├── test_file_cache.h │ │ │ │ └── test_path.h │ │ │ ├── file.cpp │ │ │ ├── file.h │ │ │ ├── file_system.cpp │ │ │ ├── file_system.h │ │ │ ├── io/ │ │ │ │ ├── io.cpp │ │ │ │ ├── io.h │ │ │ │ ├── write_buffer.cpp │ │ │ │ └── write_buffer.h │ │ │ └── vfs/ │ │ │ ├── file_cache.cpp │ │ │ ├── file_cache.h │ │ │ ├── tests/ │ │ │ │ └── test_vfs_tree.h │ │ │ ├── vfs.cpp │ │ │ ├── vfs.h │ │ │ ├── vfs_lookup.cpp │ │ │ ├── vfs_lookup.h │ │ │ ├── vfs_path.cpp │ │ │ ├── vfs_path.h │ │ │ ├── vfs_populate.cpp │ │ │ ├── vfs_populate.h │ │ │ ├── vfs_tree.cpp │ │ │ ├── vfs_tree.h │ │ │ ├── vfs_util.cpp │ │ │ └── vfs_util.h │ │ ├── fnv_hash.cpp │ │ ├── fnv_hash.h │ │ ├── frequency_filter.cpp │ │ ├── frequency_filter.h │ │ ├── input.cpp │ │ ├── input.h │ │ ├── lib.cpp │ │ ├── lib.h │ │ ├── lib_api.h │ │ ├── module_init.cpp │ │ ├── module_init.h │ │ ├── ogl.cpp │ │ ├── ogl.h │ │ ├── os_path.h │ │ ├── path.cpp │ │ ├── path.h │ │ ├── posix/ │ │ │ ├── posix.cpp │ │ │ └── posix.h │ │ ├── rand.cpp │ │ ├── rand.h │ │ ├── regex.cpp │ │ ├── regex.h │ │ ├── res/ │ │ │ └── handle.h │ │ ├── secure_crt.cpp │ │ ├── secure_crt.h │ │ ├── sysdep/ │ │ │ ├── os/ │ │ │ │ ├── unix/ │ │ │ │ │ ├── udbg.cpp │ │ │ │ │ ├── udbg.h │ │ │ │ │ ├── ufilesystem.cpp │ │ │ │ │ ├── unix.cpp │ │ │ │ │ ├── unix.h │ │ │ │ │ ├── unix_executable_pathname.cpp │ │ │ │ │ ├── unix_executable_pathname.h │ │ │ │ │ ├── unuma.cpp │ │ │ │ │ ├── unuma.h │ │ │ │ │ ├── uvm.cpp │ │ │ │ │ └── x/ │ │ │ │ │ └── x.cpp │ │ │ │ └── win/ │ │ │ │ ├── aken/ │ │ │ │ │ ├── aken.c │ │ │ │ │ ├── aken.h │ │ │ │ │ ├── makefile │ │ │ │ │ └── sources │ │ │ │ ├── comctl6.manifest │ │ │ │ ├── error_dialog.h │ │ │ │ ├── error_dialog.rc │ │ │ │ ├── icon.rc │ │ │ │ ├── mahaf.cpp │ │ │ │ ├── mahaf.h │ │ │ │ ├── manifest.cpp │ │ │ │ ├── manifest.rc │ │ │ │ ├── tests/ │ │ │ │ │ ├── test_ia32.h │ │ │ │ │ └── test_wdbg_sym.h │ │ │ │ ├── wclipboard.cpp │ │ │ │ ├── wcpu.cpp │ │ │ │ ├── wcpu.h │ │ │ │ ├── wcursor.cpp │ │ │ │ ├── wdbg.cpp │ │ │ │ ├── wdbg.h │ │ │ │ ├── wdbg_heap.cpp │ │ │ │ ├── wdbg_heap.h │ │ │ │ ├── wdbg_sym.cpp │ │ │ │ ├── wdbg_sym.h │ │ │ │ ├── wdir_watch.cpp │ │ │ │ ├── wdll_delay_load.cpp │ │ │ │ ├── wdll_delay_load.h │ │ │ │ ├── wdll_main.h │ │ │ │ ├── wdll_ver.cpp │ │ │ │ └── wdll_ver.h │ │ │ ├── smbios.h │ │ │ ├── snd.cpp │ │ │ ├── snd.h │ │ │ ├── stl.h │ │ │ ├── sysdep.h │ │ │ ├── tests/ │ │ │ │ ├── test_rtl.h │ │ │ │ └── test_sysdep.h │ │ │ └── vm.h │ │ ├── tests/ │ │ │ ├── test_adts.h │ │ │ ├── test_base32.h │ │ │ ├── test_bits.h │ │ │ ├── test_byte_order.h │ │ │ ├── test_cache_adt.h │ │ │ ├── test_fnv_hash.h │ │ │ ├── test_lib.h │ │ │ ├── test_path.h │ │ │ ├── test_path_util.h │ │ │ ├── test_rand.h │ │ │ ├── test_regex.h │ │ │ ├── test_secure_crt.h │ │ │ └── test_wchar.h │ │ ├── tex/ │ │ │ ├── tex.cpp │ │ │ ├── tex.h │ │ │ ├── tex_bmp.cpp │ │ │ ├── tex_codec.cpp │ │ │ ├── tex_codec.h │ │ │ ├── tex_dds.cpp │ │ │ ├── tex_internal.h │ │ │ ├── tex_png.cpp │ │ │ └── tex_tga.cpp │ │ ├── timer.cpp │ │ ├── timer.h │ │ ├── types.h │ │ ├── utf8.cpp │ │ ├── utf8.h │ │ └── wsecure_crt.cpp │ ├── precompiled.cpp │ ├── precompiled.h │ ├── stdafx.cpp │ ├── stdafx.h │ ├── targetver.h │ ├── test_Color.h │ └── tests.py ├── fpsgame.rc ├── fpsgame.sln ├── fpsgame.vcxproj └── sys/ ├── SysControl.cpp └── SysControl.h ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ *.filters /Test.VC.VC.opendb /Test.VC.db *.sdf *.vs *.pdb *.log *.tlog *.idb x64/* *.suo *.ipch # Prerequisites *.d # Compiled Object files *.slo *.lo *.o *.obj # Precompiled Headers *.gch *.pch # Compiled Dynamic libraries *.so *.dylib *.dll # Fortran module files *.mod *.smod # Compiled Static libraries *.lai *.la *.a *.lib # Executables *.exe *.out *.app ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2016 Li Tong Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ fpsgame ==== A simple FPS fight game based on 0 A.D..which is also an open-source game engine and a free software. You can get more information on this [link](https://play0ad.com/). ================================================ FILE: Test/ActorBash.cpp ================================================ #include "Engine.h" #include "Game.h" #include "ObjectDummy.h" #include "BodyRigid.h" #include "BodyDummy.h" #include "Physics.h" #include "ShapeCapsule.h" #include "ActorBase.h" #include "Visualizer.h" #include "sys/SysControl.h" using namespace MathLib; #ifdef MEMORY_INFO #define new new(__FILE__, __LINE__) #endif // MEMORY_INFO /* Test high performance. */ #define ACTOR_BASE_IFPS (1.0f / 120.0f) #define ACTOR_BASE_CLAMP 15.0f #define ACTOR_BASE_COLLISIONS 4 /* */ CActorBase::CActorBase() { m_vUp = vec3(0.0f, 0.0f, 1.0f); m_pObject = new CObjectDummy(); m_pDummy = new CBodyDummy(); m_pShape = new CShapeCapsule(1.0f, 1.0f); m_nFlush = 0; m_vPosition = Vec3_zero; m_vVelocity = Vec3_zero; m_fPhiAngle = 0.0f; //倾斜角 二维平面坐标系中,直线向Y周延伸的方向与X轴正向之间的夹角 m_fThetaAngle = 0.0f; //方位角,正北方那条线与当前线条按照顺时针走过的角度 for (int i = 0; i < NUM_STATES; i++) { m_pStates[i] = 0; m_pTimes[i] = 0.0f; } m_pDummy->SetEnabled(1); m_pObject->SetBody(NULL); m_pObject->SetBody(m_pDummy); m_pShape->SetBody(NULL); m_pShape->SetBody(m_pDummy); m_pObject->SetWorldTransform(Get_Body_Transform()); m_pShape->SetRestitution(0.0f); m_pShape->SetCollisionMask(2); SetEnabled(1); SetViewDirection(vec3(0.0f, 1.0f, 0.0f)); SetCollision(1); SetCollisionRadius(0.3f); SetCollisionHeight(1.0f); SetMinVelocity(2.0f); SetMaxVelocity(4.0f); SetAcceleration(8.0f); SetDamping(8.0f); SetJumping(1.5f); SetFriction(2.0f); SetGround(0); SetCeiling(0); } CActorBase::~CActorBase() { m_pDummy->SetObject(NULL); delete m_pObject; delete m_pDummy; } void CActorBase::SetEnabled(int enable) { m_nEnable = enable; m_pDummy->SetEnabled(m_nEnable); } int CActorBase::IsEnabled() const { return m_nEnable; } void CActorBase::Update(float ifps) { if (!m_nEnable) { return; } // impulse vec3 impulse = vec3_zero; // ortho basis vec3 tangent, binormal; OrthoBasis(m_vUp, tangent, binormal); // current basis vec3 x = quat(m_vUp, -m_fPhiAngle) * binormal; vec3 y = Normalize(Cross(m_vUp, x)); vec3 z = Normalize(Cross(x, y)); handle states Update_States(1, ifps); // old velocity float x_velocity = Dot(x, m_vVelocity); float y_velocity = Dot(y, m_vVelocity); float z_velocity = Dot(z, m_vVelocity); // movement if (m_pStates[STATE_FORWARD]) impulse += x; if (m_pStates[STATE_BACKWARD]) impulse -= x; if (m_pStates[STATE_MOVE_LEFT]) impulse += y; if (m_pStates[STATE_MOVE_RIGHT]) impulse -= y; impulse.normalize(); //velocity if (m_pStates[STATE_RUN]) impulse *= m_fMaxVelocity; else impulse *= m_fMinVelocity; // jump if (m_pStates[STATE_JUMP] == STATE_BEGIN) { impulse += z * CMathCore::Sqrt(2.0f * 9.8f * m_fJumping) / (m_fAcceleration * ifps); } // rotate velocity if (GetGround()) { m_vVelocity = x * x_velocity + y * y_velocity + z * z_velocity; } // time float time = ifps * g_Engine.pPhysics->GetScale(); // target velocity float target_velocity = Length(vec2(Dot(x, impulse), Dot(y, impulse))); // penetration tolerance float penetration = g_Engine.pPhysics->GetPenetrationTolerance(); float penetration_2 = penetration * 2.0f; // frozen linear velocity float frozen_velocity = g_Engine.pPhysics->GetFrozenLinearVelocity(); // friction float friction = 0.0f; if (target_velocity < EPSILON) { friction = m_fFriction; } //clear collision flags if (GetCollision()) { m_nGround = 0; m_nCeiling = 0; } // movement do { // adaptive time step float ifps = Min(time, ACTOR_BASE_IFPS); time -= ifps; // save old velocity float old_velocity = Length(vec2(Dot(x, m_vVelocity), Dot(y, m_vVelocity))); // integrate velocity m_vVelocity += impulse * (m_fAcceleration * ifps); m_vVelocity += g_Engine.pPhysics->GetGravity() * ifps; // damping float current_velocity = Length(vec2(Dot(x, m_vVelocity), Dot(y, m_vVelocity))); if (target_velocity < EPSILON || current_velocity > target_velocity) { m_vVelocity = (x * Dot(x, m_vVelocity) + y * Dot(y, m_vVelocity)) * CMathCore::Exp(-m_fDamping * ifps) + z * Dot(z, m_vVelocity); } // clamp maximum velocity current_velocity = Length(vec2(Dot(x, m_vVelocity), Dot(y, m_vVelocity))); if (current_velocity > old_velocity) { if (current_velocity > target_velocity) { m_vVelocity = (x * Dot(x, m_vVelocity) + y * Dot(y, m_vVelocity)) * target_velocity / current_velocity + z * Dot(z, m_vVelocity); } } // frozen velocity int is_frozen = 0; if (current_velocity < frozen_velocity) { m_vVelocity = z * Dot(z, m_vVelocity); is_frozen = 1; } // integrate position //m_vPosition += Vec3(m_vVelocity * ifps); // world collision if (GetCollision()) { // get collision vec3 tangent, binormal; const Vec3 *caps = m_pShape->GetCaps(); for (int i = 0; i < ACTOR_BASE_COLLISIONS; i++) { m_pDummy->SetTransform(Get_Body_Transform()); m_pShape->GetCollision(m_vecContacts, 0.0f); if (m_vecContacts.Size() == 0) break; float inum_contacts = 1.0f / CMathCore::Itof(m_vecContacts.Size()); for (int j = 0; j < m_vecContacts.Size(); j++) { const CShape::Contact &c = m_vecContacts[j]; vec3 normalCollision = c.normal; if (is_frozen && c.depth < penetration_2) { m_vPosition += Vec3(z * (Max(c.depth - penetration, 0.0f) * inum_contacts * Dot(z, normalCollision))); } else { m_vPosition += Vec3(normalCollision * (Max(c.depth - penetration, 0.0f) * inum_contacts)); is_frozen = 0; } float normal_velocity = Dot(normalCollision, m_vVelocity); if (normal_velocity < 0.0f) { m_vVelocity -= normalCollision * normal_velocity; } if (friction > EPSILON) { OrthoBasis(c.normal, tangent, binormal); float tangent_velocity = Dot(tangent, m_vVelocity); float binormal_velocity = Dot(binormal, m_vVelocity); if (CMathCore::Abs(tangent_velocity) > EPSILON || CMathCore::Abs(binormal_velocity) > EPSILON) { float friction_velocity = Clamp(Max(-normal_velocity, 0.0f) * friction * CMathCore::RSqrt(tangent_velocity * tangent_velocity + binormal_velocity * binormal_velocity), -1.0f, 1.0f); m_vVelocity -= tangent * tangent_velocity * friction_velocity; m_vVelocity -= binormal * binormal_velocity * friction_velocity; } } if (Dot(c.normal, m_vUp) > 0.5f && Dot(vec3(c.point - caps[0]), m_vUp) < 0.0f) m_nGround = 1; if (Dot(c.normal, m_vUp) < -0.5f && Dot(vec3(c.point - caps[1]), m_vUp) > 0.0f) m_nCeiling = 1; } } m_vPosition += Vec3(m_vVelocity * ifps); } while (time > EPSILON); // current position m_pObject->SetWorldTransform(Get_Body_Transform()); m_WorldBoundBox.Set(m_BoundBox, Translate(m_vPosition)); m_WorldBoundSphere.Set(m_BoundSphere, Translate(m_vPosition)); } } /* */ void CActorBase::Update_Bounds() { float radius = m_pShape->GetRadius(); float hheight = m_pShape->GetHHeight(); m_BoundBox.Set(vec3(-radius, -radius, 0.0f), vec3(radius, radius, (radius + hheight) * 2.0f)); m_BoundSphere.Set(vec3(0.0f, 0.0f, radius + hheight), radius + hheight); m_WorldBoundBox.Set(m_BoundBox, Translate(m_vPosition)); m_WorldBoundSphere.Set(m_BoundSphere, Translate(m_vPosition)); } /* */ void CActorBase::SetIntersectionMask(int mask) { m_pShape->SetIntersectionMask(mask); } int CActorBase::GetIntersectionMask() const { return m_pShape->GetIntersectionMask(); } /* */ void CActorBase::SetCollision(int c) { m_nCollision = c; } int CActorBase::GetCollision() const { return m_nCollision; } void CActorBase::SetCollisionMask(int mask) { m_pShape->SetCollisionMask(mask); } int CActorBase::GetCollisionMask() const { return m_pShape->GetCollisionMask(); } void CActorBase::SetCollisionRadius(float radius) { if (!Compare(m_pShape->GetRadius(), radius)) { m_pDummy->SetPreserveTransform(Mat4(Translate(m_vUp * (radius - m_pShape->GetRadius()))) * m_pDummy->GetTransform()); m_pShape->SetRadius(radius); } Update_Bounds(); } float CActorBase::GetCollisionRadius() const { return m_pShape->GetRadius(); } /* */ void CActorBase::SetCollisionHeight(float height) { if (!Compare(m_pShape->GetHeight(), height)) { m_pDummy->SetPreserveTransform(Mat4(Translate(m_vUp * (height - m_pShape->GetHeight()) * 0.5f)) * m_pDummy->GetTransform()); m_pShape->SetHeight(height); } Update_Bounds(); } float CActorBase::GetCollisionHeight() const { return m_pShape->GetHeight(); } void CActorBase::SetMinVelocity(float velocity) { m_fMinVelocity = Max(velocity, 0.0f); } float CActorBase::GetMinVelocity() const { return m_fMinVelocity; } /* */ void CActorBase::SetMaxVelocity(float velocity) { m_fMaxVelocity = Max(velocity, 0.0f); } float CActorBase::GetMaxVelocity() const { return m_fMaxVelocity; } /* */ void CActorBase::SetAcceleration(float accel) { m_fAcceleration = Max(accel, 0.0f); } float CActorBase::GetAcceleration() const { return m_fAcceleration; } /* */ void CActorBase::SetDamping(float d) { m_fDamping = Max(d, 0.0f); } float CActorBase::GetDamping() const { return m_fDamping; } /* */ void CActorBase::SetJumping(float j) { m_fJumping = Max(j, 0.0f); } float CActorBase::GetJumping() const { return m_fJumping; } /* */ void CActorBase::SetViewDirection(const vec3 &d) { m_vDirection = Normalize(d); // ortho basis vec3 tangent, binormal; OrthoBasis(m_vUp, tangent, binormal); // decompose direction m_fPhiAngle = CMathCore::ATan2(Dot(m_vDirection, tangent), Dot(m_vDirection, binormal)) * RAD2DEG; m_fThetaAngle = CMathCore::ACos(Clamp(Dot(m_vDirection, m_vUp), -1.0f, 1.0f)) * RAD2DEG - 90.0f; m_pObject->SetWorldTransform(Get_Body_Transform()); } const vec3 &CActorBase::GetViewDirection() const { return m_vDirection; } /******************************************************************************\ * * States * \******************************************************************************/ /* */ int CActorBase::GetState(int state) const { assert(state >= 0 && state < NUM_STATES && "CPlayerActor::GetState(): bad state number"); return m_pStates[state]; } float CActorBase::GetStateTime(int state) const { assert(state >= 0 && state < NUM_STATES && "CPlayerActor::GetStateTime(): bad state number"); return m_pTimes[state]; } /******************************************************************************\ * * Contacts * \******************************************************************************/ /* */ int CActorBase::GetNumContacts() const { return m_vecContacts.Size(); } const CShape::Contact &CActorBase::GetContact(int num) const { return m_vecContacts[num]; } /* */ void CActorBase::SetGround(int g) { m_nGround = g; } int CActorBase::GetGround() const { return m_nGround; } /* */ void CActorBase::SetCeiling(int c) { m_nCeiling = c; } int CActorBase::GetCeiling() const { return m_nCeiling; } /* */ Mat4 CActorBase::Get_Body_Transform() const { Vec3 center = m_vPosition + Vec3(m_vUp * (m_pShape->GetHHeight() + m_pShape->GetRadius())); return SetTo(center, center + Vec3(m_vDirection - m_vUp * Dot(m_vDirection, m_vUp)), m_vUp) * Mat4(RotateX(-90.0f) * RotateZ(90.0f)); } /* */ int CActorBase::Update_State(int condition, int state, int begin, int end, float ifps) { // disabled to begin if (condition && m_pStates[state] == STATE_DISABLED && begin) { m_pStates[state] = STATE_BEGIN; m_pTimes[state] = 0.0f; return STATE_BEGIN; } // enabled or begin to end if (condition == 0 && (m_pStates[state] == STATE_ENABLED || m_pStates[state] == STATE_BEGIN) && end) { m_pStates[state] = STATE_END; return STATE_END; } // begin to enabled if ((condition && m_pStates[state] == STATE_BEGIN) || m_pStates[state] == STATE_ENABLED) { m_pStates[state] = STATE_ENABLED; m_pTimes[state] += ifps; return STATE_ENABLED; } // end to disabled if (m_pStates[state] == STATE_END) { m_pStates[state] = STATE_DISABLED; return STATE_DISABLED; } return STATE_DISABLED; } void CActorBase::Update_States(int enabled, float ifps) { // handle states if (enabled) { if (g_pSysControl->GetState(CSysControl::STATE_FORWARD) && g_pSysControl->GetState(CSysControl::STATE_BACKWARD)) { Update_State(0, STATE_FORWARD, 1, 1, ifps); Update_State(0, STATE_BACKWARD, 1, 1, ifps); } else { Update_State(g_pSysControl->GetState(CSysControl::STATE_FORWARD), STATE_FORWARD, 1, 1, ifps); Update_State(g_pSysControl->GetState(CSysControl::STATE_BACKWARD), STATE_BACKWARD, 1, 1, ifps); } if (g_pSysControl->GetState(CSysControl::STATE_MOVE_LEFT) && g_pSysControl->GetState(CSysControl::STATE_MOVE_RIGHT)) { Update_State(0, STATE_MOVE_LEFT, 1, 1, ifps); Update_State(0, STATE_MOVE_RIGHT, 1, 1, ifps); } else { Update_State(g_pSysControl->GetState(CSysControl::STATE_MOVE_LEFT), STATE_MOVE_LEFT, 1, 1, ifps); Update_State(g_pSysControl->GetState(CSysControl::STATE_MOVE_RIGHT), STATE_MOVE_RIGHT, 1, 1, ifps); } Update_State(g_pSysControl->GetState(CSysControl::STATE_CROUCH), STATE_CROUCH, 1, 1, ifps); Update_State(g_pSysControl->GetState(CSysControl::STATE_JUMP), STATE_JUMP, m_nGround, 1, ifps); Update_State(g_pSysControl->GetState(CSysControl::STATE_RUN), STATE_RUN, 1, 1, ifps); } // disable states else { Update_State(0, STATE_FORWARD, 1, 1, ifps); Update_State(0, STATE_BACKWARD, 1, 1, ifps); Update_State(0, STATE_MOVE_LEFT, 1, 1, ifps); Update_State(0, STATE_MOVE_RIGHT, 1, 1, ifps); Update_State(0, STATE_CROUCH, 1, 1, ifps); Update_State(0, STATE_JUMP, m_nGround, m_nGround, ifps); Update_State(0, STATE_RUN, 1, 1, ifps); } } /******************************************************************************\ * * Bounds * \******************************************************************************/ /* */ const CBoundBox &CActorBase::GetBoundBox() const { return m_BoundBox; } const CBoundSphere &CActorBase::GetBoundSphere() const { return m_BoundSphere; } const CWorldBoundBox & CActorBase::GetWorldBoundBox() const { return m_WorldBoundBox; } const CWorldBoundSphere & CActorBase::GetWorldBoundSphere() const { return m_WorldBoundSphere; } void CActorBase::RenderVisualizer() { m_pShape->RenderVisualizer(vec4(1.0f, 0.0f, 0.0f, 1.0f)); g_Engine.pVisualizer->RenderVector(m_pShape->GetCenter(), m_pShape->GetCenter() + m_vDirection, vec4(1.0f, 0.0f, 0.0f, 1.0f)); } void CActorBase::SetFriction(float friction) { m_fFriction = friction; } float CActorBase::GetFriction() const { return m_fFriction; } void CActorBase::SetPosition(const MathLib::vec3& pos) { m_vPosition = pos; } const MathLib::vec3& CActorBase::GetPosition() const { return m_vPosition; } ================================================ FILE: Test/ActorBash.h ================================================ #pragma once #ifndef __ACTOR_BASE_H__ #define __ACTOR_BASE_H__ #include "Shape.h" #include "Bounds.h" #include "Player.h" /* */ class CBRObject; class CBodyDummy; class CBodyRigid; class CObjectDummy; class CShapeCapsule; /* *ײײ뾶ײ߶ȵȣ */ class CActorBase { public: CActorBase(); virtual ~CActorBase(); void SetEnabled(int enable); int IsEnabled() const; void Update(float ifps); // intersection mask void SetIntersectionMask(int mask); int GetIntersectionMask() const; // collision void SetCollision(int collision); int GetCollision() const; // collision mask void SetCollisionMask(int mask); int GetCollisionMask() const; // collision radius void SetCollisionRadius(float radius); float GetCollisionRadius() const; // collision height void SetCollisionHeight(float height); float GetCollisionHeight() const; // maximum friction Ħֵ void SetFriction(float friction); float GetFriction() const; // minimum velocity Сٶ void SetMinVelocity(float velocity); float GetMinVelocity() const; // maximum velocity ٶ void SetMaxVelocity(float velocity); float GetMaxVelocity() const; // acceleration ٶ void SetAcceleration(float acceleration); float GetAcceleration() const; // damping б void SetDamping(float damping); float GetDamping() const; // jumping Ծ void SetJumping(float jumping); float GetJumping() const; // view direction ߳ void SetViewDirection(const MathLib::vec3 &direction); const MathLib::vec3 &GetViewDirection() const; //position λ void SetPosition( const MathLib::vec3 & pos ); const MathLib::vec3& GetPosition() const; enum { //״̬ STATE_FORWARD = 0, STATE_BACKWARD, STATE_MOVE_LEFT, STATE_MOVE_RIGHT, STATE_CROUCH, //׷ STATE_JUMP, STATE_RUN, NUM_STATES, }; // state status //״̬Ƿã״̬ʼ enum { STATE_DISABLED = 0, STATE_ENABLED, STATE_BEGIN, STATE_END, }; // current state int GetState(int state) const; float GetStateTime(int state) const; // contacts int GetNumContacts() const; const CShape::Contact &GetContact(int num) const; // ground flag //ƶ͵λñ־ void SetGround(int ground); int GetGround() const; // ceiling flag //ƶߵλñ־ void SetCeiling(int ceiling); int GetCeiling() const; // bounds Լ virtual const CBoundBox &GetBoundBox() const; virtual const CBoundSphere &GetBoundSphere() const; //ͬ virtual const CWorldBoundBox &GetWorldBoundBox() const; virtual const CWorldBoundSphere &GetWorldBoundSphere() const; virtual void RenderVisualizer(); private: // update bounds //±߽ void Update_Bounds(); // player transformation MathLib::Mat4 Get_Body_Transform() const; // update states int Update_State(int condition,int state,int begin,int end,float ifps); void Update_States(int enabled,float ifps); int m_nFrame; // frame number MathLib::vec3 m_vUp; // up άе MathLib::vec3 m_vVelocity; // velocity vector ǰٶ CObjectDummy *m_pObject; // dummy object CBodyDummy *m_pDummy; // dummy body int m_nCollision; // collision flag CShapeCapsule *m_pShape; // collision shape float m_fFriction; // fraction when frozen float m_fMinVelocity; // minimum velocity float m_fMaxVelocity; // maximum velocity float m_fAcceleration; // acceleration float m_fDamping; // damping float m_fJumping; // jumping int m_nFlush; // flush flag MathLib::Vec3 m_vPosition; // position MathLib::vec3 m_vDirection; // direction float m_fPhiAngle; // phi angle float m_fThetaAngle; // theta angle int m_pStates[NUM_STATES]; // state vector float m_pTimes[NUM_STATES]; // time vector int m_nGround; // ground flag int m_nCeiling; // ceiling flag CBoundBox m_BoundBox; // bounding box CBoundSphere m_BoundSphere; // bounding sphere CWorldBoundBox m_WorldBoundBox; // bounding box CWorldBoundSphere m_WorldBoundSphere; // bounding sphere CVector m_vecContacts; int m_nEnable; }; #endif __PLAYER_ACTOR_H__ ================================================ FILE: Test/CameraBash.cpp ================================================ #include "CameraBase.h" #include "sys/SysControl.h" #include "HMDWrapper.h" #include "Engine.h" #include "Game.h" using namespace MathLib; #ifdef MEMORY_INFO #define new new(__FILE__, __LINE__) #endif // MEMORY_INFO #define CAMERA_BASE_CLAMP 89.9f CCameraBase::CCameraBase(): CPlayer(PLAYER_SPECTATOR) { m_nFlush = 0; m_vPosition = Vec3_zero; m_fPhiAngle = 0.0f; m_fThetaAngle = 0.0f; m_pLastPlayer = NULL; m_nEnabelMouse = 1; SetRadius(1.0f); SetMinThetaAngle(-90.0f); SetMaxThetaAngle(90.0f); SetTurning(90.0f); SetViewDirection(vec3(0.0f,1.0f,0.0f)); SetEnabled(0); SetHMDEnabled(1); } CCameraBase::~CCameraBase() { Leave(); } void CCameraBase::SetEnabled(int nEnable) { if(nEnable) { m_pLastPlayer = g_Engine.pGame->GetPlayer(); CPlayer::SetEnabled(nEnable); g_Engine.pGame->SetPlayer(this); } else { if (m_pLastPlayer) { g_Engine.pGame->SetPlayer(m_pLastPlayer); CPlayer::SetEnabled(nEnable); } } } int CCameraBase::IsEnabled() const { return CPlayer::IsEnabled(); } void CCameraBase::SetHMDEnabled(int nEnable) { m_nHMDEnable = nEnable; } int CCameraBase::IsHMDEnabled() const { return m_nHMDEnable; } void CCameraBase::SetRadius(float r) { m_fRadius = r; } float CCameraBase::GetRadius() const { return m_fRadius; } /******************************************************************************\ * * Parameters * \******************************************************************************/ /* */ void CCameraBase::Update_Bounds() { m_BoundSphere.Set(vec3_zero,m_fRadius); m_BoundBox.Set(m_BoundSphere); Update_World_Position(); } void CCameraBase::SetMinThetaAngle(float angle) { m_fMinThetaAngle = Clamp(angle,-CAMERA_BASE_CLAMP,CAMERA_BASE_CLAMP); } float CCameraBase::GetMinThetaAngle() const { return m_fMinThetaAngle; } /* */ void CCameraBase::SetMaxThetaAngle(float angle) { m_fMaxThetaAngle = Clamp(angle, -CAMERA_BASE_CLAMP, CAMERA_BASE_CLAMP); } float CCameraBase::GetMaxThetaAngle() const { return m_fMaxThetaAngle; } float CCameraBase::GetTurning() const { return m_fTurning; } void CCameraBase::SetPhiAngle(float angle) { angle = angle - m_fPhiAngle; m_vDirection = quat(m_vUp, angle) * m_vDirection; m_fPhiAngle += angle; FlushTransform(); } float CCameraBase::GetPhiAngle() const { return m_fPhiAngle; } void CCameraBase::SetThetaAngle(float angle) { angle = Clamp(angle, m_fMinThetaAngle, m_fMaxThetaAngle) - m_fThetaAngle; m_vDirection = quat(Cross(m_vUp, m_vDirection), angle) * m_vDirection; m_fThetaAngle += angle; FlushTransform(); } float CCameraBase::GetThetaAngle() const { return m_fThetaAngle; } /* * */ void CCameraBase::SetViewDirection(const vec3 &d) //设置视角方位,参考三维坐标系 { m_vDirection = Normalize(d); // ortho basis vec3 tangent, binormal; OrthoBasis(m_vUp, tangent, binormal); // decompose direction m_fPhiAngle = CMathCore::ATan2(Dot(m_vDirection, tangent), Dot(m_vDirection, binormal)) * RAD2DEG; m_fThetaAngle = CMathCore::ACos(Clamp(Dot(m_vDirection, m_vUp), -1.0f, 1.0f)) * RAD2DEG - 90.0f; m_fThetaAngle = Clamp(m_fThetaAngle, m_fMinThetaAngle, m_fMaxThetaAngle); FlushTransform(); } const vec3 &CCameraBase::GetViewDirection() const { return m_vDirection; } void CCameraBase::UpdateControls(float ifps) { if (g_pHMD->GetUseHMD() && m_nHMDEnable) { Update_HMD(ifps); } else { Update_Controls(ifps); } } /******************************************************************************\ * * Flush * \******************************************************************************/ ================================================ FILE: Test/CameraBash.h ================================================ #ifndef __CAMERA_BASE_H__ #define __CAMERA_BASE_H__ #include "Player.h" class CPlayer; /* *ͷǶȣӾ뾶СǶȣת߷򣬵ȡ */ class CCameraBase : public CPlayer { public: CCameraBase(); virtual ~CCameraBase(); void SetEnabled(int nEnable); int IsEnabled() const; void SetHMDEnabled(int nEnable); int IsHMDEnabled() const; void SetRadius(float r); float GetRadius() const; // minimum theta angle void SetMinThetaAngle(float angle); float GetMinThetaAngle() const; // maximum theta angle void SetMaxThetaAngle(float angle); float GetMaxThetaAngle() const; // turning void SetTurning(float turning); float GetTurning() const; // phi angle void SetPhiAngle(float angle); float GetPhiAngle() const; // theta angle void SetThetaAngle(float angle); float GetThetaAngle() const; // view direction void SetViewDirection(const MathLib::vec3 &direction); const MathLib::vec3 &GetViewDirection() const; // update virtual void UpdateControls(float ifps); // flush virtual void FlushTransform(); // bounds virtual const CBoundBox &GetBoundBox() const; virtual const CBoundSphere &GetBoundSphere() const; void SetFlush(int nFlush) { m_nFlush = nFlush; } void SetMouseControls(int nEnable) { m_nEnabelMouse = nEnable; } private: void Update_Controls(float ifps); void Update_HMD(float ifps); // update bounds void Update_Bounds(); // update transformation virtual void Update_Transform(); float m_fRadius; float m_fMinThetaAngle; // minimum theta angle float m_fMaxThetaAngle; // maximum theta angle float m_fAcceleration; // acceleration float m_fTurning; // turning int m_nFlush; // flush flag MathLib::Vec3 m_vPosition; // position MathLib::vec3 m_vDirection; // direction float m_fPhiAngle; // phi angle float m_fThetaAngle; // theta angle CBoundBox m_BoundBox; // bounding box CBoundSphere m_BoundSphere; // bounding sphere CPlayer *m_pLastPlayer; int m_nHMDEnable; int m_nEnabelMouse; } #endif ================================================ FILE: Test/Common.cpp ================================================ #include "Common.h" #include "Engine.h" #include "Game.h" #include "World.h" CCommon::CCommon(void) { } CCommon::~CCommon(void) { } vec3 CCommon::GetTransformDirection(const mat4& matTransform, const vec3& vUP) { return MakeRotationFromYZ(matTransform.getColumn3(1), vUP) * vec3(0.0f, -1.0f, 0.0f); } int CCommon::GetIntersectionPosition(vec3& vRetPoint, const vec3& p0, const vec3& p1, int nMask /*= 2|4*/) { vec3 n; int s; g_Engine.pWorld->GetIntersection(p0, p1, nMask, vRetPoint, n, s); return s >= 0; } int CCommon::GetIntersectionObject(CBRObject** vRetObject, const vec3& p0, const vec3& p1, int nMask /*= 2|4*/) { vec3 p, n; int s; *vRetObject = NULL; *vRetObject = g_Engine.pWorld->GetIntersection(p0, p1, nMask, p, n, s); return s >= 0; } float CCommon::CalcAxisScale(const mat4& modelview, float fov, vec4 objectPosW, float sizeInPixels, float viewHeight) { float worldHeight; // World height on origin's z value vec4 objPosV; objPosV = modelview * objectPosW; worldHeight = 2.0f * CMathCore::Abs(objPosV.z) * (float)CMathCore::Tan(fov / 2.0f * DEG2RAD); return sizeInPixels * (worldHeight / viewHeight); } ================================================ FILE: Test/Common.h ================================================ #pragma once #include "MathLib.h" using namespace MathLib; class CBRObject; class CCommon { public: CCommon(void); ~CCommon(void); static int GetIntersectionObject(CBRObject** vRetObject, const vec3& p0, const vec3& p1, int nMask = 2 | 4); static float CalcAxisScale(float fov,vec4 objectPosW,float sizeInPixels,float viewHeight); static vec3 GetTransformDirection(const mat4& matTransform,const vec3& vUP = vec3(0.0f,0.0f,1.0f)); static int GetIntersectionPosition(const mat4& modelview,vec3& vRetPoint,const vec3& p0,const vec3& p1,int nMask = 2|4); }; ================================================ FILE: Test/FPSRole.cpp ================================================ #include "FPSRole.h" #include "Engine.h" #include "Creature.h" #include "AnimationBlend.h" #include "WorldEffect.h" #include "ObjectMeshSkinned.h" #include "Game.h" #include "ObjectParticles.h" CFPSRole::CFPSRole(void) { m_nFire = 0; m_fAniCoolingTime = 0; m_fAniNowCoolingTime = 0; m_fSudCoolingTime = 0; m_fEmitCoolingTime = 0; m_fEmitNowCoolingTime = 0; m_fSudNowCoolingTime = 0; m_strMuzzleBone = "wuqi02"; m_pArmsMesh = NULL; m_pBulletParticle = NULL; m_strBulletName = "data/effect/particle/zgfwq_sj_02.node"; m_strMuzzleEffect = "data/effect/particle/zgfwq_sj_01.node"; } CFPSRole::~CFPSRole(void) { g_Engine.pGame->RemoveNode(m_pBulletParticle); g_Engine.pGame->RemoveNode(m_pMuzzleEffect); } int CFPSRole::Init( int nRoleID,const char* strCharFile ) { if(CRoleBase::Init(nRoleID,strCharFile)) { m_pStand[0] = (CAnimationBlendRotate*)m_pCreature->GetAnimationBlend("stand_h"); m_pFire[0] = (CAnimationBlendRotate*)m_pCreature->GetAnimationBlend("fire_h"); m_pStand[1] = (CAnimationBlendRotate*)m_pCreature->GetAnimationBlend("stand_v"); m_pFire[1] = (CAnimationBlendRotate*)m_pCreature->GetAnimationBlend("fire_v"); m_pRun[0] = (CAnimationBlendDual*)m_pCreature->GetAnimationBlend("run_h"); m_pRun[1] = (CAnimationBlendDual*)m_pCreature->GetAnimationBlend("run_v"); m_fAniNowCoolingTime = 0.0f; m_fAniCoolingTime = 1.0f / m_pFire[0]->GetTriggerSpeed(); m_fEmitNowCoolingTime = 0; m_fSudNowCoolingTime = 0; m_fSudCoolingTime = m_fAniCoolingTime * 4.0f; //ֵ m_fEmitCoolingTime = m_fAniCoolingTime * 3.0f;//ֵ m_pBulletParticle = (CObjectParticles*)g_Engine.pGame->LoadNode(m_strBulletName); m_pBulletParticle->GetTracker(TRACKER_CUSTOM)->SetTrackValue(0,0.0f,vec4(1.0f,1.0f,1.0f,0.8f)); m_pBulletParticle->SetName(CUtilStr::Format("%d",m_nRoleID)); m_pMuzzleEffect = (CWorldEffect*)g_Engine.pGame->LoadNode(m_strMuzzleEffect); m_pMuzzleEffect->Stop(); m_pMuzzleEffect->SetLoop(0); m_pMuzzleEffect->SetLoopCount(1); } return 1;//always return 1; } void CFPSRole::OnKeyFrame( _ActionCallback_KeyFrame* pKeyInfo ) { //I will finish this function later. } void CFPSRole::OnActionComplete( _ActionCallback_Complete* pActInfo ) { return CRoleBase::OnActionComplete(pActInfo); } void CFPSRole::SetPaceAnimationF_B( int nF_B ) { switch(nF_B) { case 0: m_pRun[0]->CloseBlend(); break; case 1: m_pRun[0]->PlayAnimationA(); break; case 2: m_pRun[0]->PlayAnimationB(); break; } } void CFPSRole::SetPaceAnimationL_R( int nF_B ) { switch(nF_B) { case 0: m_pRun[1]->CloseBlend(); break; case 1: m_pRun[1]->PlayAnimationA(); break; case 2: m_pRun[1]->PlayAnimationB(); break; } } void CFPSRole::OnFire( float fCoolingTime ) { } void CFPSRole::UpdateMuzzleTransform() { m_pFire[0]->LockFrame(2.3f); m_pFire[1]->LockFrame(2.3f); m_pStand[0]->LockFrame(2.3f); m_pStand[1]->LockFrame(2.3f); m_pArmsMesh->UpdateLink_Bone(); m_pArmsMesh->SetTransform(m_pArmsMesh->GetTransform()); m_matMuzzleTransform = m_pArmsMesh->GetWorldBoneTransform(m_nMuzzleBone); } void CFPSRole::SetMuzzleSpinL_R( float vValue ) { m_pStand[1]->SetRotateSpin(vValue); m_pFire[1]->SetRotateSpin(vValue); } void CFPSRole::SetMuzzleSpinU_D( float vValue ) { m_pStand[0]->SetRotateSpin(vValue); m_pFire[0]->SetRotateSpin(vValue); } void CFPSRole::Update( float ifps ) { UpdateMuzzleTransform(); m_pBulletParticle->SetWorldTransform(m_matMuzzleTransform); m_pMuzzleEffect->SetWorldTransform(m_matMuzzleTransform*Scale(0.4f,1.0f,0.5f)); UpdateFire(ifps); CRoleBase::Update(ifps); } void CFPSRole::SetPaceAnimationL_R( int nF_B ) { switch(nF_B) { case 0: { m_pRun[1]->CloseBlend(); }break; case 1: { m_pRun[1]->PlayAnimationA(); }break; case 2: { m_pRun[1]->PlayAnimationB(); }break; } } void CFPSRole::UpdateFire( float ifps ) { m_fAniNowCoolingTime -= ifps; m_fSudNowCoolingTime -= ifps; m_fEmitNowCoolingTime -= ifps; if(m_nFire) { // if(m_fAniNowCoolingTime < EPSILON) { m_fAniNowCoolingTime = m_fAniCoolingTime; m_pFire[0]->OnJumpFrame(); m_pFire[1]->OnJumpFrame(); } // if(m_fSudNowCoolingTime < EPSILON) { m_fSudNowCoolingTime = m_fSudCoolingTime; m_pMuzzleEffect->Play(); } //ӵ if(m_fEmitNowCoolingTime < EPSILON) { m_fEmitNowCoolingTime = m_fEmitCoolingTime; m_pBulletParticle->FireBullet(); OnFire(m_fEmitCoolingTime); } } } int CFPSRole::SetupArms( int nAssembly,int nBody ) { int nRet = m_pCreature->SetupBody(nAssembly,nBody); if(!nRet) { return 0; } CNode* pNode = GetAssemblyNode(nAssembly); if(pNode->GetType() == CNode::OBJECT_MESH_SKINNED) { m_pArmsMesh = (CObjectMeshSkinned*)pNode; m_nMuzzleBone = m_pArmsMesh->FindBone(m_strMuzzleBone); } return 1; } ================================================ FILE: Test/FPSRole.h ================================================ #pragma once #include "Rolebase.h" #include "UtilStr.h" class CAnimationBlendRotate; class CAnimationBlendDual; class CObjectParticles; class CWorldEffect; class CObjectMeshSkinned; class CFPSRole :public CRoleBase { public: CFPSRole(void); virtual ~CFPSRole(void); virtual int Init(int nRoleID, const char* strCharFile); virtual void Update(float ifps); void SetMuzzleSpinU_D(float vValue); void SetMuzzleSpinL_R(float vValue); void SetPaceAnimationF_B(int nF_B); void SetPaceAnimationL_R(int nF_B); void OpenFire() { m_nFire = 1; } void CloseFire() { m_nFire = 0; } int SetupArms(int nAssembly, int nBody); protected: virtual void OnKeyFrame(_ActionCallback_KeyFrame* pKeyInfo); virtual void OnActionComplete(_ActionCallback_Complete* pActInfo); protected: CAnimationBlendRotate* m_pStand[2]; CAnimationBlendRotate* m_pFire[2]; CAnimationBlendDual* m_pRun[2]; int m_nFire;//开枪 float m_fAniCoolingTime;//后坐力动画冷却时间 float m_fAniNowCoolingTime;//后坐力动画已冷却时间 float m_fSudCoolingTime;//开枪声音冷却时间 float m_fSudNowCoolingTime;//开枪声音已冷却时间 float m_fEmitCoolingTime;//发射子弹冷却时间 float m_fEmitNowCoolingTime;//发射子弹已冷却时间 CUtilStr m_strMuzzleBone; int m_nMuzzleBone; CObjectMeshSkinned* m_pArmsMesh; void UpdateFire(float ifps); virtual void OnFire(float fCoolingTime); void UpdateMuzzleTransform(); CUtilStr m_strBulletName; CObjectParticles* m_pBulletParticle; CUtilStr m_strMuzzleEffect; CWorldEffect* m_pMuzzleEffect; mat4 m_matMuzzleTransform; }; ================================================ FILE: Test/FPSRoleLocal.cpp ================================================ #include "FPSRoleLocal.h" #include "Creature.h" #include "Engine.h" #include "Input.h" #include "App.h" #include "ObjectMeshSkinned.h" #include "ActorBase.h" #include "Visualizer.h" #include "game.h" #include "RayControl.h" #include "Common.h" #include "ControlsApp.h" CFPSRoleLocal::CFPSRoleLocal(void) { m_nCharMode = 0; m_nMuzzleBone = -1; m_nUpdateMove = 0; } CFPSRoleLocal::~CFPSRoleLocal(void) { delete m_pActorBase; } int CFPSRoleLocal::Init( int nRoleID,const char* strCharFile ) { CFPSRole::Init(nRoleID,strCharFile); m_pCreature->SetupBody(1,1); m_pCreature->SetHighShadow(1); SetupArms(2,0); m_pActorBase = new CActorBase(); m_pActorBase->SetEnabled(1); // //m_vCameraOffset = vec3(0.0f,0.0f,1.8f); m_vCameraOffset = vec3(0.0f,0.0f,1.8f); //m_vGunOffset = vec3(0.0,0.0,-0.0f); m_vGunOffset = vec3(0.2,0.2,0.2f); //m_vRotateOffset = vec3(-0.076326f, 0.018540f, -1.725809f); // gua dian weizhi); m_vRotateOffset = vec3(-0.076326f, 0.018540f, -1.725809f); // gua dian weizhi); return 1; } void CFPSRoleLocal::Update( float ifps ) { if(m_pFire[0] && m_pFire[1]) { //if(g_Engine.pApp->GetMouseButtonState(CApp::BUTTON_LEFT) || g_Engine.pApp->GetMouseButtonState(CApp::BUTTON_LDCLICK) ) if(g_Engine.pInput->IsLBDownPress()||g_Engine.pInput->IsLBDBDown()) { OpenFire(); //굥 } else { CloseFire(); } } CFPSRole::Update(ifps); CRayControl::Instance().Update(m_matMuzzleTransform,vec3(0.0f,0.5f,-0.03f)); } void CFPSRoleLocal::OnFire( float fCoolingTime ) { CFPSRole::OnFire(fCoolingTime); } void CFPSRoleLocal::UpdateTransform( const MathLib::mat4 & matRotate ) { mat4 m = Translate(m_pActorBase->GetPosition()+ m_vCameraOffset) * Translate(m_vGunOffset) * matRotate * Translate(m_vRotateOffset); m_pCreature->GetMainObjectMesh()->SetWorldTransform(m); } void CFPSRoleLocal::SetActorPosition( const vec3& vPosition ) { m_pActorBase->SetPosition(vPosition); } void CFPSRoleLocal::SetActorDirection( const vec3& vDirection ) { m_pActorBase->SetViewDirection(vDirection); } MathLib::vec3 CFPSRoleLocal::GetActorPosition() { return m_pActorBase->GetPosition(); } void CFPSRoleLocal::UpdateActor( float ifps ) { m_pActorBase->Update(ifps); } ================================================ FILE: Test/FPSRoleLocal.h ================================================ #pragma once #include "FPSRole.h" class CActorBase; class CFPSRoleLocal : public CFPSRole { public: CFPSRoleLocal(void); virtual ~CFPSRoleLocal(void); public: virtual int Init(int nRoleID, const char* strCharFile); virtual void Update(float ifps); void UpdateTransform(const MathLib::mat4 & matRotate); MathLib::vec3 GetActorPosition(); void SetActorPosition(const vec3& vPosition); void SetActorDirection(const vec3& vDirection); void UpdateActor(float ifps); MathLib::vec3 GetCameraOffset() { return m_vCameraOffset; } protected: virtual void OnFire(float fCoolingTime); int m_nCharMode; CActorBase* m_pActorBase; MathLib::vec3 m_vCameraOffset; MathLib::vec3 m_vGunOffset; MathLib::vec3 m_vRotateOffset; }; ================================================ FILE: Test/GameMain.cpp ================================================ #include "GameMain.h" #include "GameProcess.h" #include "sys/SysControl.h" #include "Engine.h" #include "Game.h" CGameMain::CGameMain(void) { } CGameMain::~CGameMain(void) { } int CGameMain::Init() { g_pSysControl->Init(); m_pGameProcess = new CGameProcess(); return m_pGameProcess->Init(); } int CGameMain::ShutDown() { g_pSysControl->Shutdown(); m_pGameProcess->ShutDown(); delete m_pGameProcess; return 1; } int CGameMain::Update() { float ifps = g_Engine.pGame->GetIFps(); g_pSysControl->Update(ifps); m_pGameProcess->Update(); return 1; } int CGameMain::Render() { m_pGameProcess->Render(); return 1; } ================================================ FILE: Test/GameMain.h ================================================ #pragma once class CGameProcess; class CGameMain { public: CGameMain(void); virtual ~CGameMain(void); public: int Init(); int ShutDown(); int Update(); int Render(); CGameProcess* m_pGameProcess; }; ================================================ FILE: Test/GameProcess.cpp ================================================ #include "GameProcess.h" #include "Object.h" #include "Engine.h" #include "World.h" #include "App.h" #include "ToolsCamera.h" #include "ControlsApp.h" #include "MathLib.h" #include "Game.h" #include "Editor.h" #include "Input.h" #include "BlueRayUtils.h" #include "World.h" #include "Action.h" #include "FPSRoleLocal.h" #include "StarControl.h" #include "RayControl.h" #include "CameraBase.h" #include "ActorBase.h" #include "sys/SysControl.h" #include "FileSystem.h" #include "RenderManager.h" #include "Texture.h" using namespace MathLib; CGameProcess::CGameProcess(void) { } CGameProcess::~CGameProcess(void) { } int CGameProcess::Init() { g_Engine.pFileSystem->CacheFilesFormExt("char"); g_Engine.pFileSystem->CacheFilesFormExt("node"); g_Engine.pFileSystem->CacheFilesFormExt("smesh"); g_Engine.pFileSystem->CacheFilesFormExt("sanim"); g_Engine.pWorld->LoadWorld("data/scene/terrain/test/test.world"); //g_Engine.pWorld->LoadWorld("data/scene/terrain/cj/cj.world"); g_Engine.pControls->SetKeyPressFunc(KeyPress); g_Engine.pControls->SetKeyReleaseFunc(KeyRelease); m_pRole = new CFPSRoleLocal(); m_pRole->Init(10001, "data/role/hero/FpsRole/fps.char"); m_pRole->SetActorPosition(vec3(0, 0, 0)); //设置角色初始位置。以门处作为原点,三维坐标系vec3是向量 m_pSkillSystem = new CSkillSystem(this); m_pCameraBase = new CCameraBase(); m_pCameraBase->SetEnabled(1); g_pSysControl->SetMouseGrab(1); m_pStarControl = new CStarControl(); m_pRayControl = new CRayControl(); return 1; } int CGameProcess::ShutDown() //关闭游戏进程 { delete m_pRole; delete m_pSkillSystem; delete m_pCameraBase; delete m_pStarControl; delete m_pRayControl; DelAllListen(); return 0; } int CGameProcess::Update() { float ifps = g_Engine.pGame->GetIFps(); if (g_Engine.pInput->IsKeyDown('1')) { CAction* pAction = m_pRole->OrceAction("attack02"); if (pAction) { pAction->SetupSkillThrow(vec3_zero, -1.0f, 2.0f); m_pRole->StopMove(); } } else if (g_Engine.pInput->IsKeyDown('2')) { CAction* pAction = m_pRole->OrceAction("skill01"); if (pAction) { m_pRole->StopMove(); } } else if (g_Engine.pInput->IsKeyDown('3')) { CAction* pAction = m_pRole->OrceAction("skill02"); if (pAction) { m_pRole->StopMove(); CRoleBase* pTarget = NULL; for (int i = 0; i < 20; i++) { float l = (m_vAIList[i]->GetPosition() - m_pRole->GetPosition()).length(); if (l > 5.0f && l < 15.0f) { pTarget = m_vAIList[i]; break; } } if (pTarget) { CVector vTarget; vTarget.Append(pTarget->GetRoleID()); pAction->SetupSkillBulletTarget(vTarget); m_pRole->SetDirection((pTarget->GetPosition() - m_pRole->GetPosition()).normalize(), 1); } } } else if (g_Engine.pInput->IsKeyDown('4'))//多发子弹 { CAction* pAction = m_pRole->OrceAction("skill02"); if (pAction) { m_pRole->StopMove(); CVector vTarget; for (int i = 0; i < 20; i++) { float l = (m_vAIList[i]->GetPosition() - m_pRole->GetPosition()).length(); if (l > 5.0f && l < 20.0f) { vTarget.Append(m_vAIList[i]->GetRoleID()); } } if (!vTarget.Empty()) { pAction->SetupSkillBulletTarget(vTarget); } } } else if (g_Engine.pInput->IsKeyDown('5')) { CAction* pAction = m_pRole->OrceAction("skill03"); if (pAction) { m_pRole->StopMove(); CVector vPos; pAction->SetupSkillTargetPoint(vPos); } } else if (g_Engine.pInput->IsKeyDown('6')) { CAction* pAction = m_pRole->OrceAction("skill06"); if (pAction) { m_pRole->StopMove(); CVector vPos; for (int i = 0; i < 20; i++) { float l = (m_vAIList[i]->GetPosition() - m_pRole->GetPosition()).length(); if (l > 5.0f && l < 20.0f) vPos.Append(m_vAIList[i]->GetPosition()); } pAction->SetupSkillTargetPoint(vPos); } } else if (g_Engine.pInput->IsKeyDown('7')) { CAction* pAction = m_pRole->OrceAction("skill05"); if (pAction) { m_pRole->StopMove(); CVector vTarget; for (int i = 0; i < 20; i++) { float l = (m_vAIList[i]->GetPosition() - m_pRole->GetPosition()).length(); if (l > 5.0f && l < 20.0f) vTarget.Append(m_vAIList[i]->GetRoleID()); } if (!vTarget.Empty()) { pAction->SetupSkillBulletTarget(vTarget); } } } else if (g_Engine.pInput->IsKeyDown('8')) { CAction* pAction = m_pRole->OrceAction("skill07"); if (pAction) { m_pRole->StopMove(); CVector vPos; for (int i = 0; i < 20; i++) { float l = (m_vAIList[i]->GetPosition() - m_pRole->GetPosition()).length(); if (l > 5.0f && l < 20.0f) vPos.Append(m_vAIList[i]->GetPosition()); } pAction->SetupSkillBulletPosition(vPos); } } else if (g_Engine.pInput->IsKeyDown('9')) { CAction* pAction = m_pRole->OrceAction("skill08"); if (pAction) { m_pRole->StopMove(); pAction->SetupSkillBulletDirection(1); } } else if (g_Engine.pInput->IsKeyUp(CInput::KEY_ESC)) { g_Engine.pApp->Exit(); } if(g_Engine.pInput->IsLBDown()) { vec3 p0,p1; BlueRay::GetPlayerMouseDirection(p0,p1); vec3 vRetPoint,vRetNormal; int nS = -1; g_Engine.pWorld->GetIntersection(p0,p1,CBRObject::MASK_SCENE,vRetPoint,vRetNormal,nS); if(-1 != nS) { m_pRole->MoveToPath(vRetPoint); } } for(int i = 0;i < 20;i++) { if(!m_vAIList[i]->IsMoveing()) { vec3 vPos = vec3(g_Engine.pGame->GetRandomFloat(-20.0f,20.0f),g_Engine.pGame->GetRandomFloat(-20.0f,20.0f),1.1f); m_vAIList[i]->MoveToPath(vPos); } m_vAIList[i]->Update(ifps); } if (g_Engine.pInput->IsLBDown()) //shubiaozuojian { g_pSysControl->SetMouseGrab(1); m_pStarControl->Click(); } if (g_Engine.pInput->IsKeyDown(CInput::KEY_ESC)) { g_pSysControl->SetMouseGrab(0); } m_pCameraBase->SetMouseControls(g_pSysControl->GetMouseGrab()); m_pCameraBase->Update(ifps); m_pRole->SetActorDirection(m_pCameraBase->GetViewDirection()); m_pRole->UpdateActor(ifps); m_pCameraBase->SetPosition(m_pRole->GetActorPosition() + m_pRole->GetCameraOffset()); vec3 x = m_pCameraBase->GetModelview().getRow3(0); vec3 y = m_pCameraBase->GetModelview().getRow3(1); vec3 z = m_pCameraBase->GetModelview().getRow3(2); mat4 r = mat4_identity; r.setColumn3(0, -x); // r.setColumn3(1, z); // r.setColumn3(2, y); // m_pRole->UpdateTransform(r); m_pRole->Update(ifps); m_pSkillSystem->Update(ifps); m_pStarControl->Update(m_pCameraBase->GetPosition(), m_pCameraBase->GetDirection()); return 1; } int CGameProcess::Render() { ////test //m_pTestHero->Render(); //g_Engine.pVisualizer->RenderLine3D(vec3(0.0f,0.0f,1.0f), vec3(10.0f,0.0f,1.0f), vec4(1.0f,0.0f,0.0f,1.0f)); //g_Engine.pVisualizer->RenderLine3D(vec3(0.0f,0.0f,1.0f), vec3(0.0f,10.0f,1.0f), vec4(0.0f,1.0f,0.0f,1.0f)); //g_Engine.pVisualizer->RenderLine3D(vec3(0.0f,0.0f,1.0f), vec3(0.0f,0.0f,11.0f), vec4(0.0f,0.0f,1.0f,1.0f)); ////~test return 1; } int CGameProcess::KeyPress(unsigned int nKey) { if (nKey == 'w') { //g_Engine.pControls->SetState(CControls::STATE_RESTORE, 1); g_Engine.pControls->SetState(CControls::STATE_FORWARD, 1); } else if (nKey == 's') { g_Engine.pControls->SetState(CControls::STATE_RESTORE, 1); g_Engine.pControls->SetState(CControls::STATE_BACKWARD, 1); } else if (nKey == 'a') { g_Engine.pControls->SetState(CControls::STATE_RESTORE, 1); g_Engine.pControls->SetState(CControls::STATE_MOVE_LEFT, 1); } else if (nKey == 'd') { g_Engine.pControls->SetState(CControls::STATE_RESTORE, 1); g_Engine.pControls->SetState(CControls::STATE_MOVE_RIGHT, 1); } else if (nKey == 'q') { g_Engine.pControls->SetState(CControls::STATE_JUMP, 1); } else if (nKey == 'e') { g_Engine.pControls->SetState(CControls::STATE_CROUCH, 1); } else if (nKey == CApp::KEY_SHIFT) { g_Engine.pControls->SetState(CControls::STATE_RUN, 1); } return 1; } int CGameProcess::KeyRelease(unsigned int nKey) { if (nKey == 'w') { g_Engine.pControls->SetState(CControls::STATE_RESTORE, 0); g_Engine.pControls->SetState(CControls::STATE_FORWARD, 0); } else if (nKey == 's') { g_Engine.pControls->SetState(CControls::STATE_RESTORE, 0); g_Engine.pControls->SetState(CControls::STATE_BACKWARD, 0); } else if (nKey == 'a') { g_Engine.pControls->SetState(CControls::STATE_RESTORE, 0); g_Engine.pControls->SetState(CControls::STATE_MOVE_LEFT, 0); } else if (nKey == 'd') { g_Engine.pControls->SetState(CControls::STATE_RESTORE, 0); g_Engine.pControls->SetState(CControls::STATE_MOVE_RIGHT, 0); } else if (nKey == 'q') { g_Engine.pControls->SetState(CControls::STATE_JUMP, 0); } else if (nKey == 'e') { g_Engine.pControls->SetState(CControls::STATE_CROUCH, 0); } else if (nKey == CApp::KEY_SHIFT) { g_Engine.pControls->SetState(CControls::STATE_RUN, 0); } return 1; } CRoleBase* CGameProcess::GetRole(int nID) { for(int i = 0;i < 20;i++) { if(m_vAIList[i]->GetRoleID() == nID) { return m_vAIList[i]; } } return NULL; } CRoleBase* CGameProcess::GetRoleFormIndex(int nIndex) { return m_vAIList[nIndex]; return NULL; } ================================================ FILE: Test/GameProcess.h ================================================ #pragma once #include "MessageBase.h" #include "SkillSystem.h" #include "RoleBase.h" #include "Vector.h" class CFPSRoleLocal; class CCameraBase; class CActorBase; class CStarControl; class CRayControl; class CGameProcess : public CMessageBase { public: CGameProcess(void); virtual ~CGameProcess(void); int Init(); int ShutDown(); int Update(); int Render(); CRoleBase* GetRole(int nID); CRoleBase* GetRoleFormIndex(int nIndex); public: static int KeyPress(unsigned int nKey); static int KeyRelease(unsigned int nKey); protected: CFPSRoleLocal* m_pRole; CSkillSystem* m_pSkillSystem; CCameraBase* m_pCameraBase; CStarControl* m_pStarControl; CRayControl* m_pRayControl; private: }; ================================================ FILE: Test/HMDWrapper.cpp ================================================ #include "HMDWrapper.h" #include "interface\DeviceMgrInterface.h" using namespace MathLib; class CLocalHMDWrapper : public CHMDWrapper { public: CLocalHMDWrapper(); virtual ~CLocalHMDWrapper(); virtual void Init(); virtual void Update(); virtual void Shutdown(); virtual void SetUseHMD(int u); virtual int GetUseHMD() const; virtual const MathLib::vec3 &GetLocalDirection() const; virtual const MathLib::vec3 &GetLocalUp() const; private: int m_nUseHMD; int m_nHaveHMD; vec3 m_vLocalDirection; vec3 m_vLocalUp; }; CLocalHMDWrapper::CLocalHMDWrapper() { m_nUseHMD = 1; m_nHaveHMD = 0; m_vLocalDirection = forward_vector; m_vLocalUp = up_vector; } CLocalHMDWrapper::~CLocalHMDWrapper() { } void CLocalHMDWrapper::Init() { m_nHaveHMD = Interface::CDeviceInterface::GetInstance()->InitEnv(); } void CLocalHMDWrapper::Update() { if (GetUseHMD()) { Interface::SEulerAngles sData; Interface::CDeviceInterface::GetInstance()->GetHMDSensorRotation(sData); Interface::CDeviceInterface::GetInstance()->Run(); vec3 dir = forward_vector; vec3 up = up_vector; quat rotate(-sData.fPitch * RAD2DEG, sData.fRoll * RAD2DEG, sData.fYaw * RAD2DEG); m_vLocalDirection = rotate * dir; m_vLocalUp = rotate * up; } } void CLocalHMDWrapper::Shutdown() { Interface::CDeviceInterface::GetInstance()->Destroy(); } void CLocalHMDWrapper::SetUseHMD(int u) { m_nUseHMD = u; } int CLocalHMDWrapper::GetUseHMD() const { return m_nUseHMD && m_nHaveHMD; } const MathLib::vec3 & CLocalHMDWrapper::GetLocalDirection() const { return m_vLocalDirection; } const MathLib::vec3 & CLocalHMDWrapper::GetLocalUp() const { return m_vLocalUp; } CLocalHMDWrapper LocalHMDWrapper; CHMDWrapper *g_pHMD = &LocalHMDWrapper; ================================================ FILE: Test/HMDWrapper.h ================================================ #ifndef __HMD_WRAPPER_H__ #define __HMD_WRAPPER_H__ #include "MathLib.h" class CHMDWrapper { public: CHMDWrapper() {} virtual ~CHMDWrapper() {} virtual void Init() = 0; virtual void Update() = 0; virtual void Shutdown() = 0; virtual void SetUseHMD(int u) = 0; virtual int GetUseHMD() const = 0; virtual const MathLib::vec3 &GetLocalDirection() const = 0; virtual const MathLib::vec3 &GetLocalUp() const = 0; private: }; extern CHMDWrapper *g_pHMD; #endif ================================================ FILE: Test/MathLib.h ================================================ #pragma once //MathLib.h #ifndef _MATHLIB_ #define _MATHLIB_ //Make some change to check if new branch is created. #include #include #include using namespace std; //------------------------------------------------------------ double integral(double(*f)(double), double a = 0, double b = 1, double tol = 1e-5, long n = 50); //------------------------------------------------------------ double limit(double(*f)(double), double a = 0, double b = 1, double tol = 1e-5, long n = 1); //------------------------------------------------------------ double diff(double(*f)(double), double a = 0, double b = 1, double tol = 1e-5, long n = 1); //------------------------------------------------------------ #define PLOT_WIDTH 60 double plot(double x[], int n, char c = '*'); //------------------------------------------------------------ double fplot(double(*f)(double), double a = 0, double b = 1, int n = 25, char c = '*'); //------------------------------------------------------------ int subset(double *a, int i, int j); void subsort(double *a, int i, int j); //------------------------------------------------------------ void shellsort(double *a, int n); //------------------------------------------------------------ void cmpsort(double *a, int n); //------------------------------------------------------------ void rollsort(double *a, int n); //------------------------------------------------------------ int sgn(double x); int sign(double x); //------------------------------------------------------------ #define FORMAT_STD 1 #define FORMAT_EXP 0 #define MATHLIB_PI 3.14159265 class complex { protected: double real; double imag; double len; double arg; public: complex(double r = 0, double i = 0, int format = FORMAT_STD); void set(double r = 0, double i = 0, int format = FORMAT_STD); void output(int format = FORMAT_STD); void input(int format = FORMAT_STD); friend void set(complex &c, double r = 0, double i = 0, int format = FORMAT_STD); friend void output(complex &c, int format = FORMAT_STD); friend void input(complex &c, int format = FORMAT_STD); friend ostream & operator <<(ostream &out, complex &c); friend istream & operator <<(istream &in, complex &c); double getreal(); double getimag(); double getlen(); double getarg(); operator double() { return sgn(real)*len; } complex operator ~(); complex operator !(); complex operator -(); complex operator +(); friend complex operator +(const complex &, const complex &); friend complex operator -(const complex &, const complex &); friend complex operator *(const complex &, const complex &); friend complex operator /(const complex &, const complex &); friend complex operator &(const complex &, const complex &); friend complex operator |(const complex &, const complex &); friend complex operator ^(const complex &, const double); friend complex operator +=(complex &, const complex &); friend complex operator -=(complex &, const complex &); friend complex operator *=(complex &, const complex &); friend complex operator /=(complex &, const complex &); friend complex operator &=(complex &, const complex &); friend complex operator |=(complex &, const complex &); friend complex operator ^=(complex &, const double); friend int operator ==(const complex &, const complex &); friend int operator !=(const complex &, const complex &); friend int operator <(const complex &, const complex &); friend int operator <=(const complex &, const complex &); friend int operator >(const complex &, const complex &); friend int operator >=(const complex &, const complex &); friend complex exp(const complex &); friend complex log(const complex &); friend complex sin(const complex &); friend complex cos(const complex &); friend double arg(const complex &); friend double abs(const complex &); } #endif //_MATHLIB_ ================================================ FILE: Test/MoveDummy.cpp ================================================ #include "MoveDummy.h" #include "ObjectDummy.h" #include "BodyDummy.h" #include "ShapeCapsule.h" #include "Utils.h" #include "Engine.h" #include "Physics.h" #include "Game.h" #include "World.h" #define MOVE_DUMMY_IFPS (1.0f/100.0f) #define MOVE_DUMMY_CLAMP 89.9f #define MOVE_DUMMY_COLLISIONS 1 using namespace MathLib; CMoveDummy::CMoveDummy() //캯 { m_pObject = new CObjectDummy(); //һʵ m_pDummy = new CBodyDummy(); m_pShape = new CShapeCapsule(0.5f, 1.0f); SetCollisionMask(1); Clear(); } CMoveDummy::~CMoveDummy() { m_pDummy->SetObject(NULL); SAFE_DELETE(m_pObject); SAFE_DELETE(m_pDummy); } void CMoveDummy::SetCollision(int c) { m_nCollision = c; } int CMoveDummy::GetCollision() const { return m_nCollision; } void CMoveDummy::SetCollisionMask(int m) { m_pShape->SetCollisionMask(m); } int CMoveDummy::GetCollisionMask() const { return m_nCollisionMask; } void CMoveDummy::SetGround(int g) { m_nGround = g; } int CMoveDummy::GetGround() const { return m_nGround; } void CMoveDummy::SetCeiling(int c) { m_nCeiling = c; } int CMoveDummy::GetCeiling() const { return m_nCeiling; } void CMoveDummy::SetPosition(const MathLib::vec3& p) { m_vPosition = p; } const MathLib::vec3& CMoveDummy::GetPosition() const { return m_vPosition; } void CMoveDummy::SetCollisionRadius(float radius) { if (!Compare(m_pShape->GetRadius(), radius)) { m_pDummy->SetPreserveTransform(Mat4(Translate(m_vUp * (radius - m_pShape->GetRadius()))) * m_pDummy->GetTransform()); m_pShape->SetRadius(radius); } Update_Bounds(); } float CMoveDummy::GetCollisionRadius() const { return m_pShape->GetRadius(); } void CMoveDummy::SetCollisionHeight(float height) { if (!Compare(m_pShape->GetHeight(), height)) { m_pDummy->SetPreserveTransform(Mat4(Translate(m_vUp * (height - m_pShape->GetHeight()) * 0.5f)) * m_pDummy->GetTransform()); m_pShape->SetHeight(height); } Update_Bounds(); } float CMoveDummy::GetCollisionHeight() const { return m_pShape->GetHeight(); } void CMoveDummy::SetUp(const MathLib::vec3& u) { m_vUp = u; } const MathLib::vec3& CMoveDummy::GetUp() const { return m_vUp; } void CMoveDummy::SetEnabled(int e) { m_nEnabled = e; } float CMoveDummy::GetCollisionHeight() const { return m_pShape->GetHeight(); } void CMoveDummy::SetUp(const MathLib::vec3& u) { m_vUp = u; } const MathLib::vec3& CMoveDummy::GetUp() const { return m_vUp; } void CMoveDummy::SetEnabled(int e) { m_nEnabled = e; } int CMoveDummy::GetEnabled() const { return m_nEnabled; } void CMoveDummy::SetVelocity(const MathLib::vec3& v) { m_vVelocity = v; } const MathLib::vec3& CMoveDummy::GetVelocity() const { return m_vVelocity; } void CMoveDummy::SetMaxVelocity(float v) { m_fMaxVelocity = v; } float CMoveDummy::GetMaxVelocity() const { return m_fMaxVelocity; } void CMoveDummy::SetAcceleration(float a) { m_fAcceleration = a; } float CMoveDummy::GetAcceleration() const { return m_fAcceleration; } float CMoveDummy::GetMaxThrough() const { return m_fMaxThrough; } void CMoveDummy::Clear() { SetVelocity(vec3_zero); SetMaxVelocity(2.5f); SetAcceleration(15.0f); SetCollision(1); SetCollisionMask(1); SetGround(0); SetCeiling(0); SetPosition(vec3_zero); SetUp(vec3(0.0f, 0.0f, 1.0f)); SetEnabled(1); m_pDummy->SetEnabled(1); m_pObject->SetBody(NULL); m_pObject->SetBody(m_pDummy); m_pShape->SetBody(NULL); m_pShape->SetBody(m_pDummy); m_pShape->SetExclusionMask(2); m_pObject->SetWorldTransform(Get_Body_Transform()); SetCollisionRadius(0.5f); SetCollisionHeight(1.0f); SetMaxThrough(0.4f); m_vecContacts.Clear(); } int CMoveDummy::Update(float fIfps, const MathLib::vec3& vDirection) { return Update(fIfps, vDirection, m_vPosition); } int CMoveDummy::Update(float fIfps, const MathLib::vec3& vDirection, const MathLib::vec3& pos) { SetPosition(pos); vec3 vOldPosition = m_vPosition; float fVVelocity = Dot(m_vVelocity, m_vUp); float fHVelocity = CMathCore::Sqrt(CMathCore::Abs(m_vVelocity.length2() - fVVelocity*fVVelocity)); m_vVelocity = m_vUp * fVVelocity + vDirection * fHVelocity; // time float time = fIfps;// * g_Engine.pPhysics->GetScale(); // penetration tolerance float penetration = g_Engine.pPhysics->GetPenetrationTolerance(); float penetration_2 = penetration * 2.0f; // clear collision flags if (GetCollision()) { m_nGround = 0; m_nCeiling = 0; } // frozen linear velocity float frozen_velocity = g_Engine.pPhysics->GetFrozenLinearVelocity(); // movement do { // adaptive time step float ifps = Min(time, MOVE_DUMMY_IFPS); time -= ifps; // integrate velocity m_vVelocity += vDirection * (m_fAcceleration * ifps); m_vVelocity += g_Engine.pPhysics->GetGravity() * ifps; fVVelocity = Dot(m_vVelocity, m_vUp); fHVelocity = CMathCore::Sqrt(m_vVelocity.length2() - fVVelocity*fVVelocity); m_vVelocity = m_vUp * fVVelocity + vDirection * m_fMaxVelocity; } while (time > EPSILON); if (GetCollision()) { int nSurface = -1; } Vec3 vStart = m_vPosition + vDirection * GetCollisionRadius() + Vec3(m_vUp * (m_pShape->GetHHeight() + m_pShape->GetRadius())); Vec3 vEnd = vOldPosition + Vec3(m_vUp * (m_pShape->GetHHeight() + m_pShape->GetRadius())); CBRObject *p = g_Engine.pWorld->GetIntersection(vStart, vEnd, 2, nSurface); if (p != NULL) { m_vPosition = vOldPosition; } // current position m_pObject->SetWorldTransform(Get_Body_Transform()); for (int j = 0; j < m_vecContacts.Size(); j++) { const CShape::Contact &c = m_vecContacts[j]; if (Dot(vDirection, c.normal) < -0.9f) { return 0; } } return 1; } void CMoveDummy::Update_Bounds() { float radius = m_pShape->GetRadius(); float hheight = m_pShape->GetHHeight(); m_BoundBox.Set(vec3(-radius, -radius, 0.0f), vec3(radius, radius, (radius + hheight) * 2.0f)); m_BoundSphere.Set(vec3(0.0f, 0.0f, radius + hheight), radius + hheight); } MathLib::Mat4 CMoveDummy::Get_Body_Transform() const { Vec3 center = m_vPosition + Vec3(m_vUp * (m_pShape->GetHHeight() + m_pShape->GetRadius())); return Translate(center); } ================================================ FILE: Test/MoveDummy.h ================================================ #pragma once #ifndef __MOVE_DUMMY_H__ #define __MOVE_DUMMY_H__ #include "MathLib.h" #include "Bounds.h" #include "Vector.h" #include "Shape.h" class CObjectDummy; class CBodyDummy; class CShapeCapsule; class CMoveDummy { public: CMoveDummy(); ~CMoveDummy(); void SetCollision(int c); int GetCollision() const; void SetCollisionMask(int m); int GetCollisionMask() const; void SetGround(int g); int GetGround() const; void SetCeiling(int c); int GetCeiling() const; void SetPosition(const MathLib::vec3& p); const MathLib::vec3& GetPosition() const; void SetCollisionRadius(float radius); float GetCollisionRadius() const; void SetCollisionHeight(float height); float GetCollisionHeight() const; void SetUp(const MathLib::vec3& u); const MathLib::vec3& GetUp() const; void SetEnabled(int e); int GetEnabled() const; void SetVelocity(const MathLib::vec3& v); const MathLib::vec3& GetVelocity() const; void SetMaxVelocity(float v); float GetMaxVelocity() const; void SetAcceleration(float a); float GetAcceleration() const; void SetMaxThrough(float d); float GetMaxThrough() const; void Clear(); int Update(float fIfps, const MathLib::vec3& vDirection); int Update(float fIfps, const MathLib::vec3& vDirection, const MathLib::vec3& pos); private: void Update_Bounds(); MathLib::Mat4 Get_Body_Transform() const; private: int m_nCollision; int m_nCollisionMask; int m_nGround; int m_nCeiling; CObjectDummy* m_pObject; // dummy object CBodyDummy* m_pDummy; // dummy body CShapeCapsule* m_pShape; // collision shape MathLib::vec3 m_vPosition; MathLib::vec3 m_vUp; int m_nEnabled; MathLib::vec3 m_vVelocity; float m_fMaxVelocity; float m_fAcceleration; float m_fMaxThrough; CBoundBox m_BoundBox; CBoundSphere m_BoundSphere; CVector m_vecContacts; }; #endif ================================================ FILE: Test/RayControl.cpp ================================================ #include "RayControl.h" #include "ObjectParticles.h" #include "Engine.h" #include "Game.h" #include "Object.h" #include "Player.h" #include "Common.h" #include "App.h" CRayControl::CRayControl(void) { m_pStarNormal = NULL; Init(); } CRayControl::~CRayControl(void) { g_Engine.pGame->RemoveNode(m_pStarNormal); } int CRayControl::Init() { m_pStarNormal = (CObjectParticles*)g_Engine.pGame->LoadNode("data/StarControl/ray_line.node"); if (!m_pStarNormal) return 0; m_pStarNormal->GetTracker(TRACKER_CUSTOM)->SetTrackValue(0, 0, vec4(1.0f, 1.0f, 1.0f, 0.5f)); return 1; } void CRayControl::Update(const mat4& matTransform, const vec3 & vOffset) { m_pStarNormal->SetWorldTransform(matTransform*Translate(vOffset)); } int CRayControl::isEnable() { return m_pStarNormal->IsEnabled(); } void CRayControl::SetEnable(int nEnable) { m_pStarNormal->SetEnabled(nEnable); } void CRayControl::SetColor(const vec4& vColor) { m_pStarNormal->SetObjectColor(vColor); } ================================================ FILE: Test/RayControl.h ================================================ #pragma once #include "MathLib.h" #include "Singleton.h" class CObjectParticles; using namespace MathLib; class CRayControl : public CSingleton { public: CRayControl(void); ~CRayControl(void); public: int Init(); void Update(const mat4& matTransform, const vec3 & vOffset = vec3_zero); int isEnable(); void SetEnable(int nEnable); void SetColor(const vec4& vColor); protected: CObjectParticles* m_pStarNormal; }; ================================================ FILE: Test/RoleBase.cpp ================================================ #include "RoleBase.h" #include "Creature.h" #include "interface.h" #include "Action.h" #include "Utils.h" #include "NavigationFinder.h" #include "Engine.h" #include "World.h" #include "Object.h" #include "Avatar.h" CRoleBase::CRoleBase(void) { m_pCreature = NULL; m_pActionComplete = NULL; m_nOrceDirection = 0; m_nMoveing = 0; m_fMoveSpeed = 6.0f; m_nMoveToType = 0; m_nUpdateMove = 1; } CRoleBase::~CRoleBase(void) { SAFE_DELETE(m_pActionComplete); SAFE_DELETE(m_pCreature); SAFE_DELETE(m_pPathFind); } //ʼɫ int CRoleBase::Init(int nRoleID, const char* strCharFile) { if (m_pCreature)return 1; m_nMoveing = 1; m_nRoleID = nRoleID; m_pCreature = new CCreature(); m_pActionComplete = MakeInterface(this, &CRoleBase::OnActionMsg); m_pCreature->SetActionComplete(m_pActionComplete); m_pCreature->Load(strCharFile); m_pCreature->SetupBody(1, 0); m_pCreature->SetPosition(vec3_zero); m_pCreature->SetDirection(vec3(0.0f, -1.0f, 0.0f)); m_pCreature->SetRoleID(nRoleID); m_AngleYaw.SetDirection(vec3(0.0f, -1.0f, 0.0f)); m_pPathFind = new CNavigationFinder(); m_pPathFind->SetMode(CNavigationFinder::MODE_PATHFIND_STRAIGHT); return 1; } void CRoleBase::Update(float ifps) { if (NULL == m_pCreature)return; if (m_nUpdateMove) { m_AngleYaw.Update(ifps); UpdateMove(ifps); m_pCreature->SetDirection(m_AngleYaw.GetForwardDirection(), m_nOrceDirection); } m_pCreature->Update(); } int CRoleBase::OnActionMsg(void* pVoid) { _ActionCallback* pInfo = (_ActionCallback*)pVoid; switch (pInfo->nMsgID) { case 0: { OnKeyFrame((_ActionCallback_KeyFrame*)pVoid); }break; case 1: { OnActionComplete((_ActionCallback_Complete*)pVoid); }break; } return 1; } //ؼ֡ص void CRoleBase::OnKeyFrame(_ActionCallback_KeyFrame* pKeyInfo) { }//ɻص void CRoleBase::OnActionComplete(_ActionCallback_Complete* pActInfo) { if (NULL == m_pCreature)return; if (m_nMoveing) { m_pCreature->PlayAction("run"); } else { m_pCreature->PlayAction("stand"); } } CAction* CRoleBase::PlayAction(const char* szName, int nLoop, float fCorrectingTime /*= 1.0f*/) { if (NULL == m_pCreature)return NULL; if (1 == m_pCreature->PlayAction(szName, nLoop, fCorrectingTime)) { return m_pCreature->GetNowAction(); } return NULL; } CAction* CRoleBase::OrceAction(const char* szName, int nLoop, float fCorrectingTime /*= 1.0f*/) { if (NULL == m_pCreature)return NULL; if (1 == m_pCreature->OrceAction(szName, nLoop, fCorrectingTime)) { return m_pCreature->GetNowAction(); } return NULL; } void CRoleBase::SetPosition(const vec3& vPosition, int nOrce /*= 0*/) { if (NULL == m_pCreature)return; m_pCreature->SetPosition(vPosition, nOrce); } void CRoleBase::SetDirection(const vec3& vDirection, int nOrce /*= 0*/) { if (NULL == m_pCreature)return; if (m_pCreature->GetNowAction()->GetLockMove() && !nOrce)return; m_nOrceDirection = nOrce; m_AngleYaw.SetDirection(vDirection); } void CRoleBase::SetDirectionTo(const vec3& vDirection, int nOrce /*= 0*/) { if (NULL == m_pCreature)return; if (m_pCreature->GetNowAction()->GetLockMove() && !nOrce)return; m_nOrceDirection = nOrce; m_AngleYaw.SetDestDirection(vDirection); } int CRoleBase::MoveTo(const vec3& vPosition) { if (NULL == m_pCreature)return -1; if (m_pCreature->GetNowAction()->GetLockMove())return -1; m_vStartPathPosition = m_vNowPathPosition = m_pCreature->GetPosition(); m_nMoveing = 1; m_nMoveToType = 0; m_vDestPathPosition = vPosition; m_pCreature->PlayAction("run", 1); return 1; } int CRoleBase::MoveToPath(const vec3& vPosition) { if (NULL == m_pCreature)return -1; if (m_pCreature->GetNowAction()->GetLockMove())return -1; m_pPathFind->Create(m_pCreature->GetPosition(), vPosition, 0); if (m_pPathFind->GetReached() || m_pPathFind->GetNumPoints() >= 2) { m_nMoveing = 1; m_nMoveToType = 1; m_nNowPathPoint = 0; m_vNowPathPosition = m_pCreature->GetPosition(); m_pCreature->PlayAction("run", 1); return 1; } return 0; } void CRoleBase::StopMove() { m_nMoveing = 0; m_pCreature->PlayAction("stand", 1); } void CRoleBase::StopMove(const vec3& vPosition) { m_nMoveing = 0; SetPosition(vPosition, 1); m_pCreature->PlayAction("stand", 1); } void CRoleBase::UpdateMove(float ifps) { if (!m_nMoveing)return; vec3 vDir = vec3(0.0f, 1.0f, 0.0f); if (m_nMoveToType == 0) { vDir = (m_vDestPathPosition - m_vStartPathPosition).normalize(); m_vNowPathPosition += vDir * m_fMoveSpeed * ifps; MathLib::vec3 d = (m_vDestPathPosition - m_vNowPathPosition); d.z = 0.0f; int bTest = MathLib::Dot(d.normalize(), vec3(vDir.x, vDir.y, 0.0f)) < 0.0f; if (bTest || vDir.z >= 0.95f) { if (bTest && d.length() > 0.10f) { m_vNowPathPosition = m_vDestPathPosition; } StopMove(); } } } ================================================ FILE: Test/RoleBase.h ================================================ #pragma once #include "MathLib.h" #include "UtilStr.h" class CCreature; class CAction; class CInterfaceBase; class CNavigationFinder; struct _ActionCallback_KeyFrame; struct _ActionCallback_Complete; class CNode; class CRoleBase { public: CRoleBase(void); virtual ~CRoleBase(void); virtual int Init(int nRoleID, const char* strCharFile); virtual void Update(float ifps); int GetRoleID() { return m_nRoleID; } void SetRoleID(int nID) { m_nRoleID = nID; } CAction* PlayAction(const char* szName, int nLoop = 0, float fCorrectingTime = 1.0f); CAction* OrceAction(const char* szName, int nLoop = 0, float fCorrectingTime = 1.0f); void SetPosition(const vec3& vPosition, int nOrce = 0); void SetDirection(const vec3& vDirection, int nOrce = 0); void SetDirectionTo(const vec3& vDirection, int nOrce = 0); virtual int MoveTo(const vec3& vPosition); virtual int MoveToPath(const vec3& vPosition); virtual void StopMove(); virtual void StopMove(const vec3& vPosition); int IsMoveing() { return m_nMoveing; } vec3 GetDirection(); vec3 GetPosition(); float GetRoleRadius(); float GetRoleHeight(); protected: void UpdateMove(float ifps); vec3 TestRoleZ(vec3& vPosition); CNode* GetAssemblyNode(const CUtilStr& strAssembly); CNode* GetAssemblyNode(int nAssembly); CCreature* m_pCreature; int m_nRoleID; int m_nOrceDirection; CAngleYaw m_AngleYaw; CNavigationFinder* m_pPathFind; int m_nMoveing; int m_nMoveToType; float m_fMoveSpeed; int m_nNowPathPoint; vec3 m_vNowPathPosition; vec3 m_vStartPathPosition; vec3 m_vDestPathPosition; int m_nUpdateMove; protected: CInterfaceBase* m_pActionComplete; int OnActionMsg(void* pVoid); virtual void OnKeyFrame(_ActionCallback_KeyFrame* pKeyInfo); virtual void OnActionComplete(_ActionCallback_Complete* pActInfo); }; ================================================ FILE: Test/Test.vcxproj ================================================  Debug Win32 Release Win32 Debug x64 Release x64 15.0 {C944C3E9-7AD8-4117-B664-DBE932214E45} Test 10.0.15063.0 Application true v141 MultiByte Application false v141 true MultiByte Application true v141 MultiByte Application false v141 true MultiByte Level3 Disabled true Level3 Disabled true Level3 MaxSpeed true true true true true Level3 MaxSpeed true true true true true ================================================ FILE: Test/main.cpp ================================================ #include "Engine.h" #include "interface.h" #include "GameMain.h" #include "Console.h" #include "direct.h" #include "HMDWrapper.h" #include "EngineConsole.h" #include "FileSystem.h" int main(int argc, char* argv[]) { #ifdef USE_HMD g_pHMD->Init(); int useHmd = g_pHMD->GetUseHMD(); if (useHmd) { //ͷ3d render_stereo = 2; //Զ崰ڷֱ ͷֱһֱ video_mode = -1; video_width = 2160; video_height = 1200; // ȫʽ 0 1ȫ video_fullscreen = 0; video_resizable = 0; video_vsync = 0; } else { //ͷ render_stereo = 0; //Զ崰ڷֱ video_mode = -1; video_fullscreen = 0; video_width = 1280; video_height = 720; video_resizable = 0; video_vsync = 0; } #else render_stereo = 0; video_mode = -1; video_fullscreen = 0; video_width = 1280; video_height = 720; video_resizable = 0; video_vsync = 0; #endif char pAppPath[260]; getcwd(pAppPath, 260); strcat(pAppPath, "\\"); CGameMain t_Game; CInterfaceBase *pInit = MakeInterface(&t_Game, &CGameMain::Init); CInterfaceBase *pUpdate = MakeInterface(&t_Game, &CGameMain::Update); CInterfaceBase *pShutdown = MakeInterface(&t_Game, &CGameMain::ShutDown); CInterfaceBase *pRender = MakeInterface(&t_Game, &CGameMain::Render); char *pArg[3] = { NULL, "-engine_config", "data/TestProject.cfg" }; g_Engine.pEngine = new CEngine(NULL, pAppPath, NULL, NULL, 3, pArg, NULL, "1123581321", pInit); //1. 2.· 3. 4. 5. 6.һָָ,涨Ǹ 7. 8. 9.һָ //CEngine(CApp *pApp, const char *pAppPath, void *pExternalWindow, const char *pHomePath, int argc, char **argv, const char *pProject, const char *pPassword, CInterfaceBase *pInitInterface); g_Engine.pEngine->SetUpdateInterface(pUpdate); g_Engine.pEngine->SetShutdownInterface(pShutdown); g_Engine.pEngine->SetRenderInterface(pRender); while (g_Engine.pEngine->IsDone() == 0) //isDone { #ifdef USE_HMD g_pHMD->Update(); #endif g_Engine.pEngine->DoUpdate(); g_Engine.pEngine->DoRender(); g_Engine.pEngine->DoSwap(); } delete g_Engine.pEngine; g_Engine.pEngine = NULL; #ifdef USE_HMD g_pHMD->Shutdown(); #endif //return 1; return 0; } ================================================ FILE: Test/main.h ================================================ #pragma once #include int main(void) { return 0; } ================================================ FILE: fpsgame/CommonConvert.cpp ================================================ #include "precompiled.h" #include "CommonConvert.h" #include "StdSkeletons.h" #include #include void require_(int line, bool value, const char* type, const char* message) { if (value) return; char linestr[16]; sprintf(linestr, "%d", line); throw ColladaException(std::string(type) + " (line " + linestr + "): " + message); } const FMVector3 FMVector3_XAxis(1.0f, 0.0f, 0.0f); static float identity[] = { 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 }; FMMatrix44 FMMatrix44_Identity(identity); /** Error handler for libxml2 */ void errorHandler(void* ctx, const char* msg, ...) { char buffer[1024]; va_list ap; va_start(ap, msg); vsnprintf(buffer, sizeof(buffer), msg, ap); buffer[sizeof(buffer) - 1] = '\0'; va_end(ap); *((std::string*)ctx) += buffer; } FColladaErrorHandler::FColladaErrorHandler(std::string& xmlErrors_) : xmlErrors(xmlErrors_) { // Grab all the error output from libxml2, for useful error reporting xmlSetGenericErrorFunc(&xmlErrors, &errorHandler); FUError::AddErrorCallback(FUError::DEBUG_LEVEL, this, &FColladaErrorHandler::OnError); FUError::AddErrorCallback(FUError::WARNING_LEVEL, this, &FColladaErrorHandler::OnError); FUError::AddErrorCallback(FUError::ERROR_LEVEL, this, &FColladaErrorHandler::OnError); } CommonConvert::~CommonConvert() { } FColladaErrorHandler::~FColladaErrorHandler() { xmlSetGenericErrorFunc(NULL, NULL); FUError::RemoveErrorCallback(FUError::DEBUG_LEVEL, this, &FColladaErrorHandler::OnError); FUError::RemoveErrorCallback(FUError::WARNING_LEVEL, this, &FColladaErrorHandler::OnError); FUError::RemoveErrorCallback(FUError::ERROR_LEVEL, this, &FColladaErrorHandler::OnError); } void FColladaErrorHandler::OnError(FUError::Level errorLevel, uint32 errorCode, uint32 UNUSED(lineNumber)) { // Ignore warnings about missing materials, since we ignore materials entirely anyway if (errorCode == FUError::WARNING_INVALID_POLYGON_MAT_SYMBOL) return; const char* errorString = FUError::GetErrorString((FUError::Code) errorCode); if (!errorString) errorString = "Unknown error code"; if (errorLevel == FUError::DEBUG_LEVEL) Log(LOG_INFO, "FCollada %d: %s", errorCode, errorString); else if (errorLevel == FUError::WARNING_LEVEL) Log(LOG_WARNING, "FCollada %d: %s", errorCode, errorString); else throw ColladaException(errorString); } ////////////////////////////////////////////////////////////////////////// void FColladaDocument::LoadFromText(const char *text) { document.reset(FCollada::NewTopDocument()); const char* newText = NULL; size_t newTextSize = 0; FixBrokenXML(text, &newText, &newTextSize); // Log(LOG_INFO, "%s", newText); bool status = FCollada::LoadDocumentFromMemory("unknown.dae", document.get(), (void*)newText, newTextSize); if (newText != text) xmlFree((void*)newText); REQUIRE_SUCCESS(status); } void FColladaDocument::ReadExtras(xmlNode* UNUSED(colladaNode)) { // TODO: This was needed to recognise and load XSI models. // XSI support should be reintroduced some time, but this function // may not be necessary since FCollada might now provide access to the // 'extra' data via a proper API. /* if (! IsEquivalent(colladaNode->name, DAE_COLLADA_ELEMENT)) return; extra.reset(new FCDExtra(document.get())); xmlNodeList extraNodes; FUXmlParser::FindChildrenByType(colladaNode, DAE_EXTRA_ELEMENT, extraNodes); for (xmlNodeList::iterator it = extraNodes.begin(); it != extraNodes.end(); ++it) { xmlNode* extraNode = (*it); extra->LoadFromXML(extraNode); } */ } ////////////////////////////////////////////////////////////////////////// CommonConvert::CommonConvert(const char* text, std::string& xmlErrors) : m_Err(xmlErrors) { m_Doc.LoadFromText(text); FCDSceneNode* root = m_Doc.GetDocument()->GetVisualSceneRoot(); REQUIRE(root != NULL, "has root object"); // Find the instance to convert if (!FindSingleInstance(root, m_Instance, m_EntityTransform)) throw ColladaException("Couldn't find object to convert"); assert(m_Instance); Log(LOG_INFO, "Converting '%s'", m_Instance->GetEntity()->GetName().c_str()); m_IsXSI = false; FCDAsset* asset = m_Doc.GetDocument()->GetAsset(); if (asset && asset->GetContributorCount() >= 1) { std::string tool(asset->GetContributor(0)->GetAuthoringTool()); if (tool.find("XSI") != tool.npos) m_IsXSI = true; } FMVector3 upAxis = m_Doc.GetDocument()->GetAsset()->GetUpAxis(); m_YUp = (upAxis.y != 0); // assume either Y_UP or Z_UP (TODO: does anyone ever do X_UP?) } // CommonConvert::~CommonConvert() // { // } ////////////////////////////////////////////////////////////////////////// // HACK: The originals don't get exported properly from FCollada (3.02, DLL), so define // them here instead of fixing it correctly. const FMVector3 FMVector3_XAxis(1.0f, 0.0f, 0.0f); static float identity[] = { 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 }; FMMatrix44 FMMatrix44_Identity(identity); struct FoundInstance { FCDEntityInstance* instance; FMMatrix44 transform; }; static bool IsVisible_XSI(FCDSceneNode* node, bool& visible) { // Look for FCDExtra* extra = node->GetExtra(); if (!extra) return false; FCDEType* type = extra->GetDefaultType(); if (!type) return false; FCDETechnique* technique = type->FindTechnique("XSI"); if (!technique) return false; FCDENode* visibility1 = technique->FindChildNode("SI_Visibility"); if (!visibility1) return false; FCDENode* visibility2 = visibility1->FindChildNode("xsi_param"); if (!visibility2) return false; if (IsEquivalent(visibility2->GetContent(), "TRUE")) visible = true; else if (IsEquivalent(visibility2->GetContent(), "FALSE")) visible = false; return true; } static bool IsVisible(FCDSceneNode* node) { bool visible = false; // Try the XSI visibility property if (IsVisible_XSI(node, visible)) return visible; // Else fall back to the FCollada-specific setting visible = (node->GetVisibility() != 0.0); return visible; } /** * Recursively finds all entities under the current node. If onlyMarked is * set, only matches entities where the user-defined property was set to * "export" in the modelling program. * * @param node root of subtree to search * @param instances output - appends matching entities * @param transform transform matrix of current subtree * @param onlyMarked only match entities with "export" property */ static void FindInstances(FCDSceneNode* node, std::vector& instances, const FMMatrix44& transform, bool onlyMarked) { for (size_t i = 0; i < node->GetChildrenCount(); ++i) { FCDSceneNode* child = node->GetChild(i); FindInstances(child, instances, transform * node->ToMatrix(), onlyMarked); } for (size_t i = 0; i < node->GetInstanceCount(); ++i) { if (onlyMarked) { if (node->GetNote() != "export") continue; } // Only accept instances of appropriate types, and not e.g. lights FCDEntity::Type type = node->GetInstance(i)->GetEntityType(); if (!(type == FCDEntity::GEOMETRY || type == FCDEntity::CONTROLLER)) continue; // Ignore invisible objects, because presumably nobody wanted to export them if (!IsVisible(node)) continue; FoundInstance f; f.transform = transform * node->ToMatrix(); f.instance = node->GetInstance(i); instances.push_back(f); Log(LOG_INFO, "Found convertible object '%s'", node->GetName().c_str()); } } bool FindSingleInstance(FCDSceneNode* node, FCDEntityInstance*& instance, FMMatrix44& transform) { std::vector instances; FindInstances(node, instances, FMMatrix44_Identity, true); if (instances.size() > 1) { Log(LOG_ERROR, "Found too many export-marked objects"); return false; } if (instances.empty()) { FindInstances(node, instances, FMMatrix44_Identity, false); if (instances.size() > 1) { Log(LOG_ERROR, "Found too many possible objects to convert - try adding the 'export' property to disambiguate one"); return false; } if (instances.empty()) { Log(LOG_ERROR, "Didn't find any objects in the scene"); return false; } } assert(instances.size() == 1); // if we got this far instance = instances[0].instance; transform = instances[0].transform; return true; } ////////////////////////////////////////////////////////////////////////// static bool ReverseSortWeight(const FCDJointWeightPair& a, const FCDJointWeightPair& b) { return (a.weight > b.weight); } void SkinReduceInfluences(FCDSkinController* skin, size_t maxInfluenceCount, float minimumWeight) { // Approximately equivalent to: // skin->ReduceInfluences(maxInfluenceCount, minimumWeight); // except this version merges multiple weights for the same joint for (size_t i = 0; i < skin->GetInfluenceCount(); ++i) { FCDSkinControllerVertex& influence = *skin->GetVertexInfluence(i); std::vector newWeights; for (size_t i = 0; i < influence.GetPairCount(); ++i) { FCDJointWeightPair* weight = influence.GetPair(i); for (size_t j = 0; j < newWeights.size(); ++j) { FCDJointWeightPair& newWeight = newWeights[j]; if (weight->jointIndex == newWeight.jointIndex) { newWeight.weight += weight->weight; goto MERGED_WEIGHTS; } } newWeights.push_back(*weight); MERGED_WEIGHTS:; } // Put highest-weighted influences at the front of the list std::sort(newWeights.begin(), newWeights.end(), ReverseSortWeight); // Limit the maximum number of influences if (newWeights.size() > maxInfluenceCount) newWeights.resize(maxInfluenceCount); // Enforce the minimum weight per influence // (This is done here rather than in the earlier loop, because several // small weights for the same bone might add up to a value above the // threshold) while (!newWeights.empty() && newWeights.back().weight < minimumWeight) newWeights.pop_back(); // Renormalise, so sum(weights)=1 float totalWeight = 0; for (std::vector::iterator itNW = newWeights.begin(); itNW != newWeights.end(); ++itNW) totalWeight += itNW->weight; for (std::vector::iterator itNW = newWeights.begin(); itNW != newWeights.end(); ++itNW) itNW->weight /= totalWeight; // Copy new weights into the skin influence.SetPairCount(0); for (std::vector::iterator itNW = newWeights.begin(); itNW != newWeights.end(); ++itNW) influence.AddPair(itNW->jointIndex, itNW->weight); } skin->SetDirtyFlag(); } void FixSkeletonRoots(FCDControllerInstance& UNUSED(controllerInstance)) { // TODO: Need to reintroduce XSI support at some point #if 0 // HACK: The XSI exporter doesn't do a and FCollada doesn't // seem to know where else to look, so just guess that it's somewhere // under Scene_Root if (controllerInstance.GetSkeletonRoots().empty()) { // HACK (evil): SetSkeletonRoot is declared but not defined, and there's // no other proper way to modify the skeleton-roots list, so cheat horribly FUUriList& uriList = const_cast(controllerInstance.GetSkeletonRoots()); uriList.push_back(FUUri("Scene_Root")); controllerInstance.LinkImport(); } #endif } const Skeleton& FindSkeleton(const FCDControllerInstance& controllerInstance) { // I can't see any proper way to determine the real root of the skeleton, // so just choose an arbitrary bone and search upwards until we find a // recognised ancestor (or until we fall off the top of the tree) const Skeleton* skeleton = NULL; const FCDSceneNode* joint = controllerInstance.GetJoint(0); while (joint && (skeleton = Skeleton::FindSkeleton(joint->GetName().c_str())) == NULL) { joint = joint->GetParent(); } REQUIRE(skeleton != NULL, "recognised skeleton structure"); return *skeleton; } void TransformBones(std::vector& bones, const FMMatrix44& scaleTransform, bool yUp) { for (size_t i = 0; i < bones.size(); ++i) { // Apply the desired transformation to the bone coordinates FMVector3 trans(bones[i].translation, 0); trans = scaleTransform.TransformCoordinate(trans); bones[i].translation[0] = trans.x; bones[i].translation[1] = trans.y; bones[i].translation[2] = trans.z; // DON'T apply the transformation to orientation, because I can't get // that kind of thing to work in practice (interacting nicely between // the models and animations), so this function assumes the transform // just does scaling, so there's no need to rotate anything. (But I think // this code would work for rotation, though not very efficiently.) /* FMMatrix44 m = FMQuaternion(bones[i].orientation[0], bones[i].orientation[1], bones[i].orientation[2], bones[i].orientation[3]).ToMatrix(); m *= scaleTransform; HMatrix matrix; memcpy(matrix, m.Transposed().m, sizeof(matrix)); AffineParts parts; decomp_affine(matrix, &parts); bones[i].orientation[0] = parts.q.x; bones[i].orientation[1] = parts.q.y; bones[i].orientation[2] = parts.q.z; bones[i].orientation[3] = parts.q.w; */ if (yUp) { // TODO: this is all just guesses which seem to work for data // exported from XSI, rather than having been properly thought // through bones[i].translation[2] = -bones[i].translation[2]; bones[i].orientation[2] = -bones[i].orientation[2]; bones[i].orientation[3] = -bones[i].orientation[3]; } else { // Convert bone translations from xyz into xzy axes: std::swap(bones[i].translation[1], bones[i].translation[2]); // To convert the quaternions: imagine you're using the axis/angle // representation, then swap the y,z basis vectors and change the // direction of rotation by negating the angle ( => negating sin(angle) // => negating x,y,z => changing (x,y,z,w) to (-x,-z,-y,w) // but then (-x,-z,-y,w) == (x,z,y,-w) so do that instead) std::swap(bones[i].orientation[1], bones[i].orientation[2]); bones[i].orientation[3] = -bones[i].orientation[3]; } } } ================================================ FILE: fpsgame/CommonConvert.h ================================================ #pragma once #ifndef INCLUDED_COMMONCONVERT #define INCLUDED_COMMONCONVERT #include #include #include #include class Skeleton; struct OutputCB { virtual ~OutputCB() { } virtual void operator() (const char* data, unsigned int length) = 0; }; class ColladaException : public std::exception { private: std::string msg; public: ColladaException(const std::string& msg) :msg(msg) { } ~ColladaException() throw() { } }; /** * Standard error handler - logs FCollada messages using Log(), and also * maintains a list of XML parser errors. */ class FoclladaErrorHandler { private: void OnError(FUError::Level errorLevel, uint32 errorCode, uint32 lineNumber); std::string& xmlErrors; void operator=(FColladaErrorHandler); public: FoclladaErrorHandler(std::string& xmlErrors); ~FoclladaErrorHandler(); }; /** * Standard document loader. Based on FCDocument::LoadFromText, but allows * access to \ nodes at the document level (i.e. directly in \). */ class FColladaDocument { public: void LoadFromText(const char* text); /** Returns the FCDocument that was loaded. */ FCDocument* GetDocument() const { return document.get(); } /** Returns the \ data from the \ element. */ FCDExtra* GetExtra() const { return extra.get(); } private: void ReadExtras(xmlNode* colladaNode); std::unique_ptr document; std::unique_ptr extra; }; #endif ================================================ FILE: fpsgame/Decompose.cpp ================================================ /* Copyright (C) 2015 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #ifdef _MSC_VER # pragma warning(disable: 4244 4305 4127 4701) #endif /**** Decompose.c ****/ /* Ken Shoemake, 1993 */ #include #include "Decompose.h" /******* Matrix Preliminaries *******/ /** Fill out 3x3 matrix to 4x4 **/ #define mat_pad(A) (A[W][X]=A[X][W]=A[W][Y]=A[Y][W]=A[W][Z]=A[Z][W]=0,A[W][W]=1) /** Copy nxn matrix A to C using "gets" for assignment **/ #define mat_copy(C,gets,A,n) {int i,j; for(i=0;i= 0.0) { s = sqrt(tr + mat[W][W]); qu.w = s*0.5; s = 0.5 / s; qu.x = (mat[Z][Y] - mat[Y][Z]) * s; qu.y = (mat[X][Z] - mat[Z][X]) * s; qu.z = (mat[Y][X] - mat[X][Y]) * s; } else { int h = X; if (mat[Y][Y] > mat[X][X]) h = Y; if (mat[Z][Z] > mat[h][h]) h = Z; switch (h) { #define caseMacro(i,j,k,I,J,K) \ case I:\ s = sqrt( (mat[I][I] - (mat[J][J]+mat[K][K])) + mat[W][W] );\ qu.i = s*0.5;\ s = 0.5 / s;\ qu.j = (mat[I][J] + mat[J][I]) * s;\ qu.k = (mat[K][I] + mat[I][K]) * s;\ qu.w = (mat[K][J] - mat[J][K]) * s;\ break caseMacro(x,y,z,X,Y,Z); caseMacro(y,z,x,Y,Z,X); caseMacro(z,x,y,Z,X,Y); } } if (mat[W][W] != 1.0) qu = Qt_Scale(qu, 1/sqrt(mat[W][W])); return (qu); } /******* Decomp Auxiliaries *******/ static HMatrix mat_id = {{1,0,0,0},{0,1,0,0},{0,0,1,0},{0,0,0,1}}; /** Compute either the 1 or infinity norm of M, depending on tpose **/ float mat_norm(HMatrix M, int tpose) { int i; float sum, max; max = 0.0; for (i=0; i<3; i++) { if (tpose) sum = fabs(M[0][i])+fabs(M[1][i])+fabs(M[2][i]); else sum = fabs(M[i][0])+fabs(M[i][1])+fabs(M[i][2]); if (maxmax) {max = abs; col = j;} } return col; } /** Setup u for Household reflection to zero all v components but first **/ void make_reflector(float *v, float *u) { float s = sqrt(vdot(v, v)); u[0] = v[0]; u[1] = v[1]; u[2] = v[2] + ((v[2]<0.0) ? -s : s); s = sqrt(2.0/vdot(u, u)); u[0] = u[0]*s; u[1] = u[1]*s; u[2] = u[2]*s; } /** Apply Householder reflection represented by u to column vectors of M **/ void reflect_cols(HMatrix M, float *u) { int i, j; for (i=0; i<3; i++) { float s = u[0]*M[0][i] + u[1]*M[1][i] + u[2]*M[2][i]; for (j=0; j<3; j++) M[j][i] -= u[j]*s; } } /** Apply Householder reflection represented by u to row vectors of M **/ void reflect_rows(HMatrix M, float *u) { int i, j; for (i=0; i<3; i++) { float s = vdot(u, M[i]); for (j=0; j<3; j++) M[i][j] -= u[j]*s; } } /** Find orthogonal factor Q of rank 1 (or less) M **/ void do_rank1(HMatrix M, HMatrix Q) { float v1[3], v2[3], s; int col; mat_copy(Q,=,mat_id,4); /* If rank(M) is 1, we should find a non-zero column in M */ col = find_max_col(M); if (col<0) return; /* Rank is 0 */ v1[0] = M[0][col]; v1[1] = M[1][col]; v1[2] = M[2][col]; make_reflector(v1, v1); reflect_cols(M, v1); v2[0] = M[2][0]; v2[1] = M[2][1]; v2[2] = M[2][2]; make_reflector(v2, v2); reflect_rows(M, v2); s = M[2][2]; if (s<0.0) Q[2][2] = -1.0; reflect_cols(Q, v1); reflect_rows(Q, v2); } /** Find orthogonal factor Q of rank 2 (or less) M using adjoint transpose **/ void do_rank2(HMatrix M, HMatrix MadjT, HMatrix Q) { float v1[3], v2[3]; float w, x, y, z, c, s, d; int col; /* If rank(M) is 2, we should find a non-zero column in MadjT */ col = find_max_col(MadjT); if (col<0) {do_rank1(M, Q); return;} /* Rank<2 */ v1[0] = MadjT[0][col]; v1[1] = MadjT[1][col]; v1[2] = MadjT[2][col]; make_reflector(v1, v1); reflect_cols(M, v1); vcross(M[0], M[1], v2); make_reflector(v2, v2); reflect_rows(M, v2); w = M[0][0]; x = M[0][1]; y = M[1][0]; z = M[1][1]; if (w*z>x*y) { c = z+w; s = y-x; d = sqrt(c*c+s*s); c = c/d; s = s/d; Q[0][0] = Q[1][1] = c; Q[0][1] = -(Q[1][0] = s); } else { c = z-w; s = y+x; d = sqrt(c*c+s*s); c = c/d; s = s/d; Q[0][0] = -(Q[1][1] = c); Q[0][1] = Q[1][0] = s; } Q[0][2] = Q[2][0] = Q[1][2] = Q[2][1] = 0.0; Q[2][2] = 1.0; reflect_cols(Q, v1); reflect_rows(Q, v2); } /******* Polar Decomposition *******/ /* Polar Decomposition of 3x3 matrix in 4x4, * M = QS. See Nicholas Higham and Robert S. Schreiber, * Fast Polar Decomposition of An Arbitrary Matrix, * Technical Report 88-942, October 1988, * Department of Computer Science, Cornell University. */ float polar_decomp(HMatrix M, HMatrix Q, HMatrix S) { #define TOL 1.0e-6 HMatrix Mk, MadjTk, Ek; float det, M_one, M_inf, MadjT_one, MadjT_inf, E_one, gamma, g1, g2; int i, j; mat_tpose(Mk,=,M,3); M_one = norm_one(Mk); M_inf = norm_inf(Mk); do { adjoint_transpose(Mk, MadjTk); det = vdot(Mk[0], MadjTk[0]); if (det==0.0) {do_rank2(Mk, MadjTk, Mk); break;} MadjT_one = norm_one(MadjTk); MadjT_inf = norm_inf(MadjTk); gamma = sqrt(sqrt((MadjT_one*MadjT_inf)/(M_one*M_inf))/fabs(det)); g1 = gamma*0.5; g2 = 0.5/(gamma*det); mat_copy(Ek,=,Mk,3); mat_binop(Mk,=,g1*Mk,+,g2*MadjTk,3); mat_copy(Ek,-=,Mk,3); E_one = norm_one(Ek); M_one = norm_one(Mk); M_inf = norm_inf(Mk); } while (E_one>(M_one*TOL)); mat_tpose(Q,=,Mk,3); mat_pad(Q); mat_mult(Mk, M, S); mat_pad(S); for (i=0; i<3; i++) for (j=i; j<3; j++) S[i][j] = S[j][i] = 0.5*(S[i][j]+S[j][i]); return (det); } /******* Spectral Decomposition *******/ /* Compute the spectral decomposition of symmetric positive semi-definite S. * Returns rotation in U and scale factors in result, so that if K is a diagonal * matrix of the scale factors, then S = U K (U transpose). Uses Jacobi method. * See Gene H. Golub and Charles F. Van Loan. Matrix Computations. Hopkins 1983. */ HVect spect_decomp(HMatrix S, HMatrix U) { HVect kv; double Diag[3],OffD[3]; /* OffD is off-diag (by omitted index) */ double g,h,fabsh,fabsOffDi,t,theta,c,s,tau,ta,OffDq,a,b; static char nxt[] = {Y,Z,X}; int sweep, i, j; mat_copy(U,=,mat_id,4); Diag[X] = S[X][X]; Diag[Y] = S[Y][Y]; Diag[Z] = S[Z][Z]; OffD[X] = S[Y][Z]; OffD[Y] = S[Z][X]; OffD[Z] = S[X][Y]; for (sweep=20; sweep>0; sweep--) { float sm = fabs(OffD[X])+fabs(OffD[Y])+fabs(OffD[Z]); if (sm==0.0) break; for (i=Z; i>=X; i--) { int p = nxt[i]; int q = nxt[p]; fabsOffDi = fabs(OffD[i]); g = 100.0*fabsOffDi; if (fabsOffDi>0.0) { h = Diag[q] - Diag[p]; fabsh = fabs(h); if (fabsh+g==fabsh) { t = OffD[i]/h; } else { theta = 0.5*h/OffD[i]; t = 1.0/(fabs(theta)+sqrt(theta*theta+1.0)); if (theta<0.0) t = -t; } c = 1.0/sqrt(t*t+1.0); s = t*c; tau = s/(c+1.0); ta = t*OffD[i]; OffD[i] = 0.0; Diag[p] -= ta; Diag[q] += ta; OffDq = OffD[q]; OffD[q] -= s*(OffD[p] + tau*OffD[q]); OffD[p] += s*(OffDq - tau*OffD[p]); for (j=Z; j>=X; j--) { a = U[j][p]; b = U[j][q]; U[j][p] -= s*(b + tau*a); U[j][q] += s*(a - tau*b); } } } } kv.x = Diag[X]; kv.y = Diag[Y]; kv.z = Diag[Z]; kv.w = 1.0; return (kv); } /******* Spectral Axis Adjustment *******/ /* Given a unit quaternion, q, and a scale vector, k, find a unit quaternion, p, * which permutes the axes and turns freely in the plane of duplicate scale * factors, such that q p has the largest possible w component, i.e. the * smallest possible angle. Permutes k's components to go with q p instead of q. * See Ken Shoemake and Tom Duff. Matrix Animation and Polar Decomposition. * Proceedings of Graphics Interface 1992. Details on p. 262-263. */ Quat snuggle(Quat q, HVect *k) { #define SQRTHALF (0.7071067811865475244) #define sgn(n,v) ((n)?-(v):(v)) #define swap(a,i,j) {a[3]=a[i]; a[i]=a[j]; a[j]=a[3];} #define cycle(a,p) if (p) {a[3]=a[0]; a[0]=a[1]; a[1]=a[2]; a[2]=a[3];}\ else {a[3]=a[2]; a[2]=a[1]; a[1]=a[0]; a[0]=a[3];} Quat p; float ka[4]; int i, turn = -1; ka[X] = k->x; ka[Y] = k->y; ka[Z] = k->z; if (ka[X]==ka[Y]) {if (ka[X]==ka[Z]) turn = W; else turn = Z;} else {if (ka[X]==ka[Z]) turn = Y; else if (ka[Y]==ka[Z]) turn = X;} if (turn>=0) { Quat qtoz, qp; unsigned neg[3], win; double mag[3], t; static Quat qxtoz = {0,SQRTHALF,0,SQRTHALF}; static Quat qytoz = {SQRTHALF,0,0,SQRTHALF}; static Quat qppmm = { 0.5, 0.5,-0.5,-0.5}; static Quat qpppp = { 0.5, 0.5, 0.5, 0.5}; static Quat qmpmm = {-0.5, 0.5,-0.5,-0.5}; static Quat qpppm = { 0.5, 0.5, 0.5,-0.5}; static Quat q0001 = { 0.0, 0.0, 0.0, 1.0}; static Quat q1000 = { 1.0, 0.0, 0.0, 0.0}; switch (turn) { default: return (Qt_Conj(q)); case X: q = Qt_Mul(q, qtoz = qxtoz); swap(ka,X,Z) break; case Y: q = Qt_Mul(q, qtoz = qytoz); swap(ka,Y,Z) break; case Z: qtoz = q0001; break; } q = Qt_Conj(q); mag[0] = (double)q.z*q.z+(double)q.w*q.w-0.5; mag[1] = (double)q.x*q.z-(double)q.y*q.w; mag[2] = (double)q.y*q.z+(double)q.x*q.w; for (i=0; i<3; i++) if ((neg[i] = (mag[i]<0.0)) != 0) mag[i] = -mag[i]; if (mag[0]>mag[1]) {if (mag[0]>mag[2]) win = 0; else win = 2;} else {if (mag[1]>mag[2]) win = 1; else win = 2;} switch (win) { case 0: if (neg[0]) p = q1000; else p = q0001; break; case 1: if (neg[1]) p = qppmm; else p = qpppp; cycle(ka,0) break; case 2: if (neg[2]) p = qmpmm; else p = qpppm; cycle(ka,1) break; } qp = Qt_Mul(q, p); t = sqrt(mag[win]+0.5); p = Qt_Mul(p, Qt_(0.0,0.0,-qp.z/t,qp.w/t)); p = Qt_Mul(qtoz, Qt_Conj(p)); } else { float qa[4], pa[4]; unsigned lo, hi, neg[4], par = 0; double all, big, two; qa[0] = q.x; qa[1] = q.y; qa[2] = q.z; qa[3] = q.w; for (i=0; i<4; i++) { pa[i] = 0.0; if ((neg[i] = (qa[i]<0.0)) != 0) qa[i] = -qa[i]; par ^= neg[i]; } /* Find two largest components, indices in hi and lo */ if (qa[0]>qa[1]) lo = 0; else lo = 1; if (qa[2]>qa[3]) hi = 2; else hi = 3; if (qa[lo]>qa[hi]) { if (qa[lo^1]>qa[hi]) {hi = lo; lo ^= 1;} else {hi ^= lo; lo ^= hi; hi ^= lo;} } else {if (qa[hi^1]>qa[lo]) lo = hi^1;} all = (qa[0]+qa[1]+qa[2]+qa[3])*0.5; two = (qa[hi]+qa[lo])*SQRTHALF; big = qa[hi]; if (all>two) { if (all>big) {/*all*/ {int i; for (i=0; i<4; i++) pa[i] = sgn(neg[i], 0.5);} cycle(ka,par) } else {/*big*/ pa[hi] = sgn(neg[hi],1.0);} } else { if (two>big) {/*two*/ pa[hi] = sgn(neg[hi],SQRTHALF); pa[lo] = sgn(neg[lo], SQRTHALF); if (lo>hi) {hi ^= lo; lo ^= hi; hi ^= lo;} if (hi==W) {hi = "\001\002\000"[lo]; lo = 3-hi-lo;} swap(ka,hi,lo) } else {/*big*/ pa[hi] = sgn(neg[hi],1.0);} } p.x = -pa[0]; p.y = -pa[1]; p.z = -pa[2]; p.w = pa[3]; } k->x = ka[X]; k->y = ka[Y]; k->z = ka[Z]; return (p); } /******* Decompose Affine Matrix *******/ /* Decompose 4x4 affine matrix A as TFRUK(U transpose), where t contains the * translation components, q contains the rotation R, u contains U, k contains * scale factors, and f contains the sign of the determinant. * Assumes A transforms column vectors in right-handed coordinates. * See Ken Shoemake and Tom Duff. Matrix Animation and Polar Decomposition. * Proceedings of Graphics Interface 1992. */ void decomp_affine(HMatrix A, AffineParts *parts) { HMatrix Q, S, U; Quat p; float det; parts->t = Qt_(A[X][W], A[Y][W], A[Z][W], 0); det = polar_decomp(A, Q, S); if (det<0.0) { mat_copy(Q,=,-Q,3); parts->f = -1; } else parts->f = 1; parts->q = Qt_FromMatrix(Q); parts->k = spect_decomp(S, U); parts->u = Qt_FromMatrix(U); p = snuggle(parts->u, &parts->k); parts->u = Qt_Mul(parts->u, p); } /******* Invert Affine Decomposition *******/ /* Compute inverse of affine decomposition. */ void invert_affine(AffineParts *parts, AffineParts *inverse) { Quat t, p; inverse->f = parts->f; inverse->q = Qt_Conj(parts->q); inverse->u = Qt_Mul(parts->q, parts->u); inverse->k.x = (parts->k.x==0.0) ? 0.0 : 1.0/parts->k.x; inverse->k.y = (parts->k.y==0.0) ? 0.0 : 1.0/parts->k.y; inverse->k.z = (parts->k.z==0.0) ? 0.0 : 1.0/parts->k.z; inverse->k.w = parts->k.w; t = Qt_(-parts->t.x, -parts->t.y, -parts->t.z, 0); t = Qt_Mul(Qt_Conj(inverse->u), Qt_Mul(t, inverse->u)); t = Qt_(inverse->k.x*t.x, inverse->k.y*t.y, inverse->k.z*t.z, 0); p = Qt_Mul(inverse->q, inverse->u); t = Qt_Mul(p, Qt_Mul(t, Qt_Conj(p))); inverse->t = (inverse->f>0.0) ? t : Qt_(-t.x, -t.y, -t.z, 0); } ================================================ FILE: fpsgame/Decompose.h ================================================ /* Copyright (C) 2009 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /**** Decompose.h - Basic declarations ****/ #ifndef INCLUDED_DECOMPOSE #define INCLUDED_DECOMPOSE typedef struct {float x, y, z, w;} Quat; /* Quaternion */ enum QuatPart {X, Y, Z, W}; typedef Quat HVect; /* Homogeneous 3D vector */ typedef float HMatrix[4][4]; /* Right-handed, for column vectors */ typedef struct { HVect t; /* Translation components */ Quat q; /* Essential rotation */ Quat u; /* Stretch rotation */ HVect k; /* Stretch factors */ float f; /* Sign of determinant */ } AffineParts; float polar_decomp(HMatrix M, HMatrix Q, HMatrix S); HVect spect_decomp(HMatrix S, HMatrix U); Quat snuggle(Quat q, HVect *k); void decomp_affine(HMatrix A, AffineParts *parts); void invert_affine(AffineParts *parts, AffineParts *inverse); #endif ================================================ FILE: fpsgame/Dll.cpp ================================================ /* Copyright (C) 2011 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "CommonConvert.h" #include "PMDConvert.h" #include "PSAConvert.h" #include "StdSkeletons.h" #include #include void default_logger(void*, int severity, const char* message) { fprintf(stderr, "[%d] %s\n", severity, message); } static LogFn g_Logger = &default_logger; static void* g_LoggerCBData = NULL; EXPORT void set_logger(LogFn logger, void* cb_data) { if (logger) { g_Logger = logger; g_LoggerCBData = cb_data; } else { g_Logger = &default_logger; g_LoggerCBData = NULL; } } EXPORT void poll_logger() { if (!g_Logger) { g_Logger = NULL; g_LoggerCBData; } } void Log(int severity, const char* msg, ...) { char buffer[1024]; va_list ap; va_start(ap, msg); vsnprintf(buffer, sizeof(buffer), msg, ap); buffer[sizeof(buffer) - 1] = '\0'; va_end(ap); g_Logger(g_LoggerCBData, severity, buffer); } struct BufferedOutputCallback : public OutputCB { static const unsigned int bufferSize = 4096; char buffer[bufferSize]; unsigned int bufferUsed; OutputFn fn; void* cb_data; BufferedOutputCallback(OutputFn fn, void* cb_data) : fn(fn), cb_data(cb_data), bufferUsed(0) { } ~BufferedOutputCallback() { // flush the buffer if it's not empty if (bufferUsed > 0) fn(cb_data, buffer, bufferUsed); } virtual void operator() (const char* data, unsigned int length) { if (bufferUsed + length > bufferSize) { // will overflow buffer, so flush the buffer first fn(cb_data, buffer, bufferUsed); bufferUsed = 0; if (length > bufferSize) { // new data won't fit in buffer, so send it out unbuffered fn(cb_data, data, length); return; } } // append onto buffer memcpy(buffer + bufferUsed, data, length); bufferUsed += length; assert(bufferUsed <= bufferSize); } }; int convert_dae_to_whatever(const char* dae, OutputFn writer, void* cb_data, void(*conv)(const char*, OutputCB&, std::string&)) { Log(LOG_INFO, "Starting conversion"); FCollada::Initialize(); std::string xmlErrors; BufferedOutputCallback cb(writer, cb_data); try { conv(dae, cb, xmlErrors); } catch (const ColladaException& e) { if (!xmlErrors.empty()) Log(LOG_ERROR, "%s", xmlErrors.c_str()); Log(LOG_ERROR, "%s", e.what()); FCollada::Release(); return -2; } FCollada::Release(); if (!xmlErrors.empty()) { Log(LOG_ERROR, "%s", xmlErrors.c_str()); return -1; } return 0; } EXPORT int convert_dae_to_pmd(const char* dae, OutputFn pmd_writer, void* cb_data) { return convert_dae_to_whatever(dae, pmd_writer, cb_data, ColladaToPMD); } EXPORT int convert_dae_to_psa(const char* dae, OutputFn psa_writer, void* cb_data) { return convert_dae_to_whatever(dae, psa_writer, cb_data, ColladaToPSA); } EXPORT int set_skeleton_definitions(const char* xml, int length) { std::string xmlErrors; try { Skeleton::LoadSkeletonDataFromXml(xml, length, xmlErrors); } catch (const ColladaException& e) { if (!xmlErrors.empty()) Log(LOG_ERROR, "%s", xmlErrors.c_str()); Log(LOG_ERROR, "%s", e.what()); return -1; } return 0; } ================================================ FILE: fpsgame/Dll.h ================================================ #pragma once /* Copyright (C) 2015 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_COLLADA_DLL #define INCLUDED_COLLADA_DLL #ifdef _WIN32 # ifdef COLLADA_DLL # define EXPORT extern "C" __declspec(dllexport) # else # define EXPORT extern "C" __declspec(dllimport) # endif #elif defined(__GNUC__) # define EXPORT extern "C" __attribute__ ((visibility ("default"))) #else # define EXPORT extern "C" #endif #define LOG_INFO 0 #define LOG_WARNING 1 #define LOG_ERROR 2 typedef void(*LogFn) (void* cb_data, int severity, const char* text); typedef void(*OutputFn) (void* cb_data, const char* data, unsigned int length); /* This version number should be bumped whenever incompatible changes * are made, to invalidate old caches. */ #define COLLADA_CONVERTER_VERSION 3 EXPORT void set_logger(LogFn logger, void* cb_data); EXPORT int set_skeleton_definitions(const char* xml, int length); EXPORT int convert_dae_to_pmd(const char* dae, OutputFn pmd_writer, void* cb_data); EXPORT int convert_dae_to_psa(const char* dae, OutputFn psa_writer, void* cb_data); EXPORT void poll_logger(void* cb_data); #endif /* INCLUDED_COLLADA_DLL */ #pragma once ================================================ FILE: fpsgame/GeomReindex.cpp ================================================ /* Copyright (C) 2009 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "GeomReindex.h" #include "FCollada.h" #include "FCDocument/FCDEntity.h" #include "FCDocument/FCDGeometryMesh.h" #include "FCDocument/FCDGeometryPolygons.h" #include "FCDocument/FCDGeometryPolygonsInput.h" #include "FCDocument/FCDGeometrySource.h" #include "FCDocument/FCDSkinController.h" #include #include #include #include typedef std::pair uv_pair_type; struct VertexData { VertexData(const float* pos, const float* norm, const std::vector &uvs, const std::vector& weights) : x(pos[0]), y(pos[1]), z(pos[2]), nx(norm[0]), ny(norm[1]), nz(norm[2]), uvs(uvs), weights(weights) { } float x, y, z; float nx, ny, nz; std::vector uvs; std::vector weights; }; bool similar(float a, float b) { return (fabsf(a - b) < 0.000001f); } bool operator==(const FCDJointWeightPair& a, const FCDJointWeightPair& b) { return (a.jointIndex == b.jointIndex && similar(a.weight, b.weight)); } bool operator<(const FCDJointWeightPair& a, const FCDJointWeightPair& b) { // Sort by decreasing weight, then by increasing joint ID if (a.weight > b.weight) return true; else if (a.weight < b.weight) return false; else if (a.jointIndex < b.jointIndex) return true; else return false; } bool operator==(const uv_pair_type& a, const uv_pair_type& b) { return similar(a.first, b.first) && similar(a.second, b.second); } bool operator==(const VertexData& a, const VertexData& b) { return (similar(a.x, b.x) && similar(a.y, b.y) && similar(a.z, b.z) && similar(a.nx, b.nx) && similar(a.ny, b.ny) && similar(a.nz, b.nz) && (a.uvs == b.uvs) && (a.weights == b.weights)); } bool operator<(const VertexData& a, const VertexData& b) { #define CMP(f) if (a.f < b.f) return true; if (a.f > b.f) return false CMP(x); CMP(y); CMP(z); CMP(nx); CMP(ny); CMP(nz); CMP(uvs); CMP(weights); #undef CMP return false; } template struct InserterWithoutDuplicates { InserterWithoutDuplicates(std::vector& vec) : vec(vec) { } size_t add(const T& val) { typename std::map::iterator it = btree.find(val); if (it != btree.end()) return it->second; size_t idx = vec.size(); vec.push_back(val); btree.insert(std::make_pair(val, idx)); return idx; } std::vector& vec; std::map btree; // for faster lookups (so we can build a duplicate-free list in O(n log n) instead of O(n^2)) private: InserterWithoutDuplicates& operator=(const InserterWithoutDuplicates&); }; void CanonicaliseWeights(std::vector& weights) { // Convert weight-lists into a standard format, so simple vector equality // can be used to determine equivalence std::sort(weights.begin(), weights.end()); } void ReindexGeometry(FCDGeometryPolygons* polys, FCDSkinController* skin) { // Given geometry with: // positions, normals, texcoords, bone blends // each with their own data array and index array, change it to // have a single optimised index array shared by all vertexes. FCDGeometryPolygonsInput* inputPosition = polys->FindInput(FUDaeGeometryInput::POSITION); FCDGeometryPolygonsInput* inputNormal = polys->FindInput(FUDaeGeometryInput::NORMAL); FCDGeometryPolygonsInput* inputTexcoord = polys->FindInput(FUDaeGeometryInput::TEXCOORD); size_t numVertices = polys->GetFaceVertexCount(); assert(inputPosition->GetIndexCount() == numVertices); assert(inputNormal ->GetIndexCount() == numVertices); assert(inputTexcoord->GetIndexCount() == numVertices); const uint32* indicesPosition = inputPosition->GetIndices(); const uint32* indicesNormal = inputNormal->GetIndices(); const uint32* indicesTexcoord = inputTexcoord->GetIndices(); assert(indicesPosition); assert(indicesNormal); assert(indicesTexcoord); // TODO - should be optional, because textureless meshes aren't unreasonable FCDGeometrySourceList texcoordSources; polys->GetParent()->FindSourcesByType(FUDaeGeometryInput::TEXCOORD, texcoordSources); FCDGeometrySource* sourcePosition = inputPosition->GetSource(); FCDGeometrySource* sourceNormal = inputNormal ->GetSource(); const float* dataPosition = sourcePosition->GetData(); const float* dataNormal = sourceNormal ->GetData(); if (skin) { #ifndef NDEBUG size_t numVertexPositions = sourcePosition->GetDataCount() / sourcePosition->GetStride(); assert(skin->GetInfluenceCount() == numVertexPositions); #endif } uint32 stridePosition = sourcePosition->GetStride(); uint32 strideNormal = sourceNormal ->GetStride(); std::vector indicesCombined; std::vector vertexes; InserterWithoutDuplicates inserter(vertexes); for (size_t i = 0; i < numVertices; ++i) { std::vector weights; if (skin) { FCDSkinControllerVertex* influences = skin->GetVertexInfluence(indicesPosition[i]); assert(influences != NULL); for (size_t j = 0; j < influences->GetPairCount(); ++j) { FCDJointWeightPair* pair = influences->GetPair(j); assert(pair != NULL); weights.push_back(*pair); } CanonicaliseWeights(weights); } std::vector uvs; for (size_t set = 0; set < texcoordSources.size(); ++set) { const float* dataTexcoord = texcoordSources[set]->GetData(); uint32 strideTexcoord = texcoordSources[set]->GetStride(); uv_pair_type p; p.first = dataTexcoord[indicesTexcoord[i]*strideTexcoord]; p.second = dataTexcoord[indicesTexcoord[i]*strideTexcoord + 1]; uvs.push_back(p); } VertexData vtx ( &dataPosition[indicesPosition[i]*stridePosition], &dataNormal [indicesNormal [i]*strideNormal], uvs, weights ); size_t idx = inserter.add(vtx); indicesCombined.push_back((uint32)idx); } // TODO: rearrange indicesCombined (and rearrange vertexes to match) to use // the vertex cache efficiently // ( etc) FloatList newDataPosition; FloatList newDataNormal; FloatList newDataTexcoord; std::vector > newWeightedMatches; for (size_t i = 0; i < vertexes.size(); ++i) { newDataPosition.push_back(vertexes[i].x); newDataPosition.push_back(vertexes[i].y); newDataPosition.push_back(vertexes[i].z); newDataNormal .push_back(vertexes[i].nx); newDataNormal .push_back(vertexes[i].ny); newDataNormal .push_back(vertexes[i].nz); newWeightedMatches.push_back(vertexes[i].weights); } // (Slightly wasteful to duplicate this array so many times, but FCollada // doesn't seem to support multiple inputs with the same source data) inputPosition->SetIndices(&indicesCombined.front(), indicesCombined.size()); inputNormal ->SetIndices(&indicesCombined.front(), indicesCombined.size()); inputTexcoord->SetIndices(&indicesCombined.front(), indicesCombined.size()); for (size_t set = 0; set < texcoordSources.size(); ++set) { newDataTexcoord.clear(); for (size_t i = 0; i < vertexes.size(); ++i) { newDataTexcoord.push_back(vertexes[i].uvs[set].first); newDataTexcoord.push_back(vertexes[i].uvs[set].second); } texcoordSources[set]->SetData(newDataTexcoord, 2); } sourcePosition->SetData(newDataPosition, 3); sourceNormal ->SetData(newDataNormal, 3); if (skin) { skin->SetInfluenceCount(newWeightedMatches.size()); for (size_t i = 0; i < newWeightedMatches.size(); ++i) { skin->GetVertexInfluence(i)->SetPairCount(0); for (size_t j = 0; j < newWeightedMatches[i].size(); ++j) skin->GetVertexInfluence(i)->AddPair(newWeightedMatches[i][j].jointIndex, newWeightedMatches[i][j].weight); } } } ================================================ FILE: fpsgame/GeomReindex.h ================================================ /* Copyright (C) 2009 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_GEOMREINDEX #define INCLUDED_GEOMREINDEX class FCDGeometryPolygons; class FCDSkinController; void ReindexGeometry(FCDGeometryPolygons* polys, FCDSkinController* skin = 0); #endif // INCLUDED_GEOMREINDEX ================================================ FILE: fpsgame/JSInterfaace_GameView.h ================================================ /* Copyright (C) 2013 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_JSINTERFACE_GAMEVIEW #define INCLUDED_JSINTERFACE_GAMEVIEW #include "ps/CStr.h" #include "scriptinterface/ScriptInterface.h" #define DECLARE_BOOLEAN_SCRIPT_SETTING(NAME) \ bool Get##NAME##Enabled(ScriptInterface::CxPrivate* pCxPrivate); \ void Set##NAME##Enabled(ScriptInterface::CxPrivate* pCxPrivate, bool Enabled); namespace JSI_GameView { void RegisterScriptFunctions(ScriptInterface& ScriptInterface); DECLARE_BOOLEAN_SCRIPT_SETTING(Culling); DECLARE_BOOLEAN_SCRIPT_SETTING(LockCullCamera); DECLARE_BOOLEAN_SCRIPT_SETTING(ConstrainCamera); } #undef DECLARE_BOOLEAN_SCRIPT_SETTING #endif ================================================ FILE: fpsgame/JSInterface_GameView.cpp ================================================ /* Copyright (C) 2013 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "JSInterface_GameView.h" #include "graphics/GameView.h" #include "ps/Game.h" #include "ps/CLogger.h" #include "ps/Profile.h" #include "scriptinterface/ScriptInterface.h" #define IMPLEMENT_BOOLEAN_SCRIPT_SETTING(NAME) \ bool JSI_GameView::Get##NAME##Enabled(ScriptInterface::CxPrivate* UNUSED(pCxPrivate)) \ { \ if (!g_Game || !g_Game->GetView()) \ { \ LOGERROR("Trying to get a setting from GameView when it's not initialized!"); \ return false; \ } \ return g_Game->GetView()->Get##NAME##Enabled(); \ } \ \ void JSI_GameView::Set##NAME##Enabled(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), bool Enabled) \ { \ if (!g_Game || !g_Game->GetView()) \ { \ LOGERROR("Trying to set a setting of GameView when it's not initialized!"); \ return; \ } \ g_Game->GetView()->Set##NAME##Enabled(Enabled); \ } IMPLEMENT_BOOLEAN_SCRIPT_SETTING(Culling); IMPLEMENT_BOOLEAN_SCRIPT_SETTING(LockCullCamera); IMPLEMENT_BOOLEAN_SCRIPT_SETTING(ConstrainCamera); #undef IMPLEMENT_BOOLEAN_SCRIPT_SETTING #define REGISTER_BOOLEAN_SCRIPT_SETTING(NAME) \ scriptInterface.RegisterFunction("GameView_Get" #NAME "Enabled"); \ scriptInterface.RegisterFunction("GameView_Set" #NAME "Enabled"); void JSI_GameView::RegisterScriptFunctions(ScriptInterface& scriptInterface) { REGISTER_BOOLEAN_SCRIPT_SETTING(Culling); REGISTER_BOOLEAN_SCRIPT_SETTING(LockCullCamera); REGISTER_BOOLEAN_SCRIPT_SETTING(ConstrainCamera); } #undef REGISTER_BOOLEAN_SCRIPT_SETTING ================================================ FILE: fpsgame/Math.cpp ================================================ /* Copyright (C) 2009 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "Maths.h" #include "FCollada.h" void DumpMatrix(const FMMatrix44& m) { Log(LOG_INFO, "\n[%f %f %f %f]\n[%f %f %f %f]\n[%f %f %f %f]\n[%f %f %f %f]", m.m[0][0], m.m[0][1], m.m[0][2], m.m[0][3], m.m[1][0], m.m[1][1], m.m[1][2], m.m[1][3], m.m[2][0], m.m[2][1], m.m[2][2], m.m[2][3], m.m[3][0], m.m[3][1], m.m[3][2], m.m[3][3] ); } FMMatrix44 DecomposeToScaleMatrix(const FMMatrix44& m) { FMVector3 scale, rotation, translation; float inverted; m.Decompose(scale, rotation, translation, inverted); return FMMatrix44::ScaleMatrix(scale); } /* FMMatrix44 operator+ (const FMMatrix44& a, const FMMatrix44& b) { FMMatrix44 r; for (int x = 0; x < 4; ++x) for (int y = 0; y < 4; ++y) r[x][y] = a[x][y] + b[x][y]; return r; } FMMatrix44 operator/ (const FMMatrix44& a, const float b) { FMMatrix44 r; for (int x = 0; x < 4; ++x) for (int y = 0; y < 4; ++y) r[x][y] = a[x][y] / b; return r; } FMMatrix44 QuatToMatrix(float x, float y, float z, float w) { FMMatrix44 r; r[0][0] = 1.0f - (y*y*2 + z*z*2); r[1][0] = x*y*2 - w*z*2; r[2][0] = x*z*2 + w*y*2; r[3][0] = 0; r[0][1] = x*y*2 + w*z*2; r[1][1] = 1.0f - (x*x*2 + z*z*2); r[2][1] = y*z*2 - w*x*2; r[3][1] = 0; r[0][2] = x*z*2 - w*y*2; r[1][2] = y*z*2 + w*x*2; r[2][2] = 1.0f - (x*x*2 + y*y*2); r[3][2] = 0; r[0][3] = 0; r[1][3] = 0; r[2][3] = 0; r[3][3] = 1; return r; } */ ================================================ FILE: fpsgame/Math.h ================================================ #pragma once /* Copyright (C) 2009 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_MATHS #define INCLUDED_MATHS class FMMatrix44; extern void DumpMatrix(const FMMatrix44& m); extern FMMatrix44 DecomposeToScaleMatrix(const FMMatrix44& m); // (None of these are used any more) // extern FMMatrix44 operator+ (const FMMatrix44& a, const FMMatrix44& b); // extern FMMatrix44 operator/ (const FMMatrix44& a, const float b); // extern FMMatrix44 QuatToMatrix(float x, float y, float z, float w); #endif // INCLUDED_MATHS ================================================ FILE: fpsgame/PMDConvert.cpp ================================================ /* Copyright (C) 2012 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "PMDConvert.h" #include "CommonConvert.h" #include "FCollada.h" #include "FCDocument/FCDAsset.h" #include "FCDocument/FCDocument.h" #include "FCDocument/FCDocumentTools.h" #include "FCDocument/FCDController.h" #include "FCDocument/FCDControllerInstance.h" #include "FCDocument/FCDGeometry.h" #include "FCDocument/FCDGeometryMesh.h" #include "FCDocument/FCDGeometryPolygons.h" #include "FCDocument/FCDGeometryPolygonsInput.h" #include "FCDocument/FCDGeometryPolygonsTools.h" #include "FCDocument/FCDGeometrySource.h" #include "FCDocument/FCDSceneNode.h" #include "FCDocument/FCDSkinController.h" #include "StdSkeletons.h" #include "Decompose.h" #include "Maths.h" #include "GeomReindex.h" #include #include #include const size_t maxInfluences = 4; struct VertexBlend { uint8 bones[maxInfluences]; float weights[maxInfluences]; }; VertexBlend defaultInfluences = { { 0xFF, 0xFF, 0xFF, 0xFF }, { 0, 0, 0, 0 } }; struct PropPoint { std::string name; float translation[3]; float orientation[4]; uint8 bone; }; // Based on FMVector3::Normalize, but that function uses a static member // FMVector3::XAxis which causes irritating linker errors. Rather than trying // to make that XAxis work in a cross-platform way, just reimplement Normalize: static FMVector3 FMVector3_Normalize(const FMVector3& vec) { float l = vec.Length(); if (l > 0.0f) return FMVector3(vec.x/l, vec.y/l, vec.z/l); else return FMVector3(1.0f, 0.0f, 0.0f); } static void AddStaticPropPoints(std::vector &propPoints, const FMMatrix44& upAxisTransform, FCDSceneNode* node) { if (node->GetName().find("prop-") == 0 || node->GetName().find("prop_") == 0) { // Strip off the "prop-" from the name std::string propPointName (node->GetName().substr(5)); Log(LOG_INFO, "Adding prop point %s", propPointName.c_str()); // CalculateWorldTransform applies transformations recursively for all parents of this node // upAxisTransform transforms this node to right-handed Z_UP coordinates FMMatrix44 transform = upAxisTransform * node->CalculateWorldTransform(); HMatrix matrix; memcpy(matrix, transform.Transposed().m, sizeof(matrix)); AffineParts parts; decomp_affine(matrix, &parts); // Add prop point in game coordinates PropPoint p = { propPointName, // Flip translation across the x-axis by swapping y and z { parts.t.x, parts.t.z, parts.t.y }, // To convert the quaternions: imagine you're using the axis/angle // representation, then swap the y,z basis vectors and change the // direction of rotation by negating the angle ( => negating sin(angle) // => negating x,y,z => changing (x,y,z,w) to (-x,-z,-y,w) // but then (-x,-z,-y,w) == (x,z,y,-w) so do that instead) { parts.q.x, parts.q.z, parts.q.y, -parts.q.w }, 0xff }; propPoints.push_back(p); } // Search children for prop points for (size_t i = 0; i < node->GetChildrenCount(); ++i) AddStaticPropPoints(propPoints, upAxisTransform, node->GetChild(i)); } class PMDConvert { public: /** * Converts a COLLADA XML document into the PMD mesh format. * * @param input XML document to parse * @param output callback for writing the PMD data; called lots of times * with small strings * @param xmlErrors output - errors reported by the XML parser * @throws ColladaException on failure */ static void ColladaToPMD(const char* input, OutputCB& output, std::string& xmlErrors) { CommonConvert converter(input, xmlErrors); if (converter.GetInstance().GetEntity()->GetType() == FCDEntity::GEOMETRY) { Log(LOG_INFO, "Found static geometry"); FCDGeometryPolygons* polys = GetPolysFromGeometry((FCDGeometry*)converter.GetInstance().GetEntity()); // Convert the geometry into a suitable form for the game ReindexGeometry(polys); std::vector boneWeights; // unused std::vector boneTransforms; // unused std::vector propPoints; // Get the raw vertex data FCDGeometryPolygonsInput* inputPosition = polys->FindInput(FUDaeGeometryInput::POSITION); FCDGeometryPolygonsInput* inputNormal = polys->FindInput(FUDaeGeometryInput::NORMAL); const uint32* indicesCombined = inputPosition->GetIndices(); size_t indicesCombinedCount = inputPosition->GetIndexCount(); // (ReindexGeometry guarantees position/normal/texcoord have the same indexes) FCDGeometrySource* sourcePosition = inputPosition->GetSource(); FCDGeometrySource* sourceNormal = inputNormal ->GetSource(); FCDGeometrySourceList texcoordSources; polys->GetParent()->FindSourcesByType(FUDaeGeometryInput::TEXCOORD, texcoordSources); float* dataPosition = sourcePosition->GetData(); float* dataNormal = sourceNormal ->GetData(); size_t vertexCount = sourcePosition->GetDataCount() / 3; assert(sourcePosition->GetDataCount() == vertexCount*3); assert(sourceNormal ->GetDataCount() == vertexCount*3); std::vector dataTexcoords; for (size_t i = 0; i < texcoordSources.size(); ++i) { dataTexcoords.push_back(texcoordSources[i]->GetData()); } // Transform mesh coordinate system to game coordinates // (doesn't modify prop points) TransformStaticModel(dataPosition, dataNormal, vertexCount, converter.GetEntityTransform(), converter.IsYUp()); // Add static prop points // which are empty child nodes of the main parent // Default prop points are already given in game coordinates AddDefaultPropPoints(propPoints); // Calculate transform to convert from COLLADA-defined up_axis to Z-up because // it's relatively straightforward to convert that to game coordinates FMMatrix44 upAxisTransform = FMMatrix44_Identity; if (converter.IsYUp()) { // Prop points are rotated -90 degrees about the X-axis, reverse that rotation // (do this once now because it's easier than messing with quaternions later) upAxisTransform = FMMatrix44::XAxisRotationMatrix(1.57f); } AddStaticPropPoints(propPoints, upAxisTransform, converter.GetInstance().GetParent()); WritePMD(output, indicesCombined, indicesCombinedCount, dataPosition, dataNormal, dataTexcoords, vertexCount, boneWeights, boneTransforms, propPoints); } else if (converter.GetInstance().GetType() == FCDEntityInstance::CONTROLLER) { Log(LOG_INFO, "Found skinned geometry"); FCDControllerInstance& controllerInstance = static_cast(converter.GetInstance()); // (NB: GetType is deprecated and should be replaced with HasType, // except that has irritating linker errors when using a DLL, so don't // bother) assert(converter.GetInstance().GetEntity()->GetType() == FCDEntity::CONTROLLER); // assume this is always true? FCDController* controller = static_cast(converter.GetInstance().GetEntity()); FCDSkinController* skin = controller->GetSkinController(); REQUIRE(skin != NULL, "is skin controller"); FixSkeletonRoots(controllerInstance); // Data for joints is stored in two places - avoid overflows by limiting // to the minimum of the two sizes, and warn if they're different (which // happens in practice for slightly-broken meshes) size_t jointCount = std::min(skin->GetJointCount(), controllerInstance.GetJointCount()); if (skin->GetJointCount() != controllerInstance.GetJointCount()) { Log(LOG_WARNING, "Mismatched bone counts (skin has %d, skeleton has %d)", skin->GetJointCount(), controllerInstance.GetJointCount()); for (size_t i = 0; i < skin->GetJointCount(); ++i) Log(LOG_INFO, "Skin joint %d: %s", i, skin->GetJoint(i)->GetId().c_str()); for (size_t i = 0; i < controllerInstance.GetJointCount(); ++i) Log(LOG_INFO, "Skeleton joint %d: %s", i, controllerInstance.GetJoint(i)->GetName().c_str()); } // Get the skinned mesh for this entity FCDGeometry* baseGeometry = controller->GetBaseGeometry(); REQUIRE(baseGeometry != NULL, "controller has base geometry"); FCDGeometryPolygons* polys = GetPolysFromGeometry(baseGeometry); // Make sure it doesn't use more bones per vertex than the game can handle SkinReduceInfluences(skin, maxInfluences, 0.001f); // Convert the geometry into a suitable form for the game ReindexGeometry(polys, skin); const Skeleton& skeleton = FindSkeleton(controllerInstance); // Convert the bone influences into VertexBlend structures for the PMD: bool hasComplainedAboutNonexistentJoints = false; // because we want to emit a warning only once std::vector boneWeights; // one per vertex const FCDSkinControllerVertex* vertexInfluences = skin->GetVertexInfluences(); for (size_t i = 0; i < skin->GetInfluenceCount(); ++i) { VertexBlend influences = defaultInfluences; assert(vertexInfluences[i].GetPairCount() <= maxInfluences); // guaranteed by ReduceInfluences; necessary for avoiding // out-of-bounds writes to the VertexBlend if (vertexInfluences[i].GetPairCount() == 0) { // Blender exports some models with vertices that have no influences, // which I've not found details about in the COLLADA spec, however, // it seems to work OK to treat these vertices the same as if they // were only influenced by the bind-shape matrix (see comment below), // so we use the same special case here. influences.bones[0] = (uint8)jointCount; influences.weights[0] = 1.0f; } for (size_t j = 0; j < vertexInfluences[i].GetPairCount(); ++j) { if (vertexInfluences[i].GetPair(j)->jointIndex == -1) { // This is a special case we must handle, according to the COLLADA spec: // "An index of -1 into the array of joints refers to the bind shape" // // which basically means when skinning the vertex it's relative to the // bind-shape transform instead of an animated bone. Since our skinning // is in world space, we will have already applied the bind-shape transform, // so we don't have to worry about that, though we DO have to apply the // world space transform of the model for the indicated vertex. // // To indicate this special case, we use a bone ID set to the total number // of bones in the model, which will have a special "bone matrix" reserved // that contains the world space transform of the model during skinning. // (see http://trac.wildfiregames.com/ticket/1012) influences.bones[j] = (uint8)jointCount; influences.weights[j] = vertexInfluences[i].GetPair(j)->weight; } else { // Check for less than 254 joints because we store them in a u8, // 0xFF is a reserved value (no influence), and we reserve one slot // for the above special case. uint32 jointIdx = vertexInfluences[i].GetPair(j)->jointIndex; REQUIRE(jointIdx < 0xFE, "sensible number of joints (<254)"); // Find the joint on the skeleton, after checking it really exists FCDSceneNode* joint = NULL; if (jointIdx < controllerInstance.GetJointCount()) joint = controllerInstance.GetJoint(jointIdx); // Complain on error if (! joint) { if (! hasComplainedAboutNonexistentJoints) { Log(LOG_WARNING, "Vertexes influenced by nonexistent joint"); hasComplainedAboutNonexistentJoints = true; } continue; } // Store into the VertexBlend int boneId = skeleton.GetBoneID(joint->GetName().c_str()); if (boneId < 0) { // The relevant joint does exist, but it's not a recognised // bone in our chosen skeleton structure Log(LOG_ERROR, "Vertex influenced by unrecognised bone '%s'", joint->GetName().c_str()); continue; } influences.bones[j] = (uint8)boneId; influences.weights[j] = vertexInfluences[i].GetPair(j)->weight; } } boneWeights.push_back(influences); } // Convert the bind pose into BoneTransform structures for the PMD: BoneTransform boneDefault = { { 0, 0, 0 }, { 0, 0, 0, 1 } }; // identity transform std::vector boneTransforms (skeleton.GetBoneCount(), boneDefault); for (size_t i = 0; i < jointCount; ++i) { FCDSceneNode* joint = controllerInstance.GetJoint(i); int boneId = skeleton.GetRealBoneID(joint->GetName().c_str()); if (boneId < 0) { // unrecognised joint - it's probably just a prop point // or something, so ignore it continue; } FMMatrix44 bindPose = skin->GetJoint(i)->GetBindPoseInverse().Inverted(); HMatrix matrix; memcpy(matrix, bindPose.Transposed().m, sizeof(matrix)); // set matrix = bindPose^T, to match what decomp_affine wants AffineParts parts; decomp_affine(matrix, &parts); BoneTransform b = { { parts.t.x, parts.t.y, parts.t.z }, { parts.q.x, parts.q.y, parts.q.z, parts.q.w } }; boneTransforms[boneId] = b; } // Construct the list of prop points. // Currently takes all objects that are directly attached to a // standard bone, and whose name begins with "prop-" or "prop_". std::vector propPoints; AddDefaultPropPoints(propPoints); for (size_t i = 0; i < jointCount; ++i) { FCDSceneNode* joint = controllerInstance.GetJoint(i); int boneId = skeleton.GetBoneID(joint->GetName().c_str()); if (boneId < 0) { // unrecognised joint name - ignore, same as before continue; } // Check all the objects attached to this bone for (size_t j = 0; j < joint->GetChildrenCount(); ++j) { FCDSceneNode* child = joint->GetChild(j); if (child->GetName().find("prop-") != 0 && child->GetName().find("prop_") != 0) { // doesn't begin with "prop-", so skip it continue; } // Strip off the "prop-" from the name std::string propPointName (child->GetName().substr(5)); Log(LOG_INFO, "Adding prop point %s", propPointName.c_str()); // Get translation and orientation of local transform FMMatrix44 localTransform = child->ToMatrix(); HMatrix matrix; memcpy(matrix, localTransform.Transposed().m, sizeof(matrix)); AffineParts parts; decomp_affine(matrix, &parts); // Add prop point to list PropPoint p = { propPointName, { parts.t.x, parts.t.y, parts.t.z }, { parts.q.x, parts.q.y, parts.q.z, parts.q.w }, (uint8)boneId }; propPoints.push_back(p); } } // Get the raw vertex data FCDGeometryPolygonsInput* inputPosition = polys->FindInput(FUDaeGeometryInput::POSITION); FCDGeometryPolygonsInput* inputNormal = polys->FindInput(FUDaeGeometryInput::NORMAL); const uint32* indicesCombined = inputPosition->GetIndices(); size_t indicesCombinedCount = inputPosition->GetIndexCount(); // (ReindexGeometry guarantees position/normal/texcoord have the same indexes) FCDGeometrySource* sourcePosition = inputPosition->GetSource(); FCDGeometrySource* sourceNormal = inputNormal ->GetSource(); FCDGeometrySourceList texcoordSources; polys->GetParent()->FindSourcesByType(FUDaeGeometryInput::TEXCOORD, texcoordSources); float* dataPosition = sourcePosition->GetData(); float* dataNormal = sourceNormal ->GetData(); size_t vertexCount = sourcePosition->GetDataCount() / 3; assert(sourcePosition->GetDataCount() == vertexCount*3); assert(sourceNormal ->GetDataCount() == vertexCount*3); std::vector dataTexcoords; for (size_t i = 0; i < texcoordSources.size(); ++i) { dataTexcoords.push_back(texcoordSources[i]->GetData()); } // Transform model coordinate system to game coordinates TransformSkinnedModel(dataPosition, dataNormal, vertexCount, boneTransforms, propPoints, converter.GetEntityTransform(), skin->GetBindShapeTransform(), converter.IsYUp(), converter.IsXSI()); WritePMD(output, indicesCombined, indicesCombinedCount, dataPosition, dataNormal, dataTexcoords, vertexCount, boneWeights, boneTransforms, propPoints); } else { throw ColladaException("Unrecognised object type"); } } /** * Adds the default "root" prop-point. */ static void AddDefaultPropPoints(std::vector& propPoints) { PropPoint root; root.name = "root"; root.translation[0] = root.translation[1] = root.translation[2] = 0.0f; root.orientation[0] = root.orientation[1] = root.orientation[2] = 0.0f; root.orientation[3] = 1.0f; root.bone = 0xFF; propPoints.push_back(root); } /** * Writes the model data in the PMD format. */ static void WritePMD(OutputCB& output, const uint32* indices, size_t indexCount, const float* position, const float* normal, const std::vector& texcoords, size_t vertexCount, const std::vector& boneWeights, const std::vector& boneTransforms, const std::vector& propPoints) { static const VertexBlend noBlend = { { 0xFF, 0xFF, 0xFF, 0xFF }, { 0, 0, 0, 0 } }; size_t faceCount = indexCount/3; size_t boneCount = boneTransforms.size(); if (boneCount) assert(boneWeights.size() == vertexCount); size_t propPointsSize = 0; // can't calculate this statically, so loop over all the prop points for (size_t i = 0; i < propPoints.size(); ++i) { propPointsSize += 4 + propPoints[i].name.length(); propPointsSize += 3*4 + 4*4 + 1; } output("PSMD", 4); // magic number write(output, (uint32)4); // version number write(output, (uint32)( // for UVs, we add one uint32 (i.e. 4 bytes) per model that gives the number of // texcoord sets in the model, plus 2 floats per new UV // pair per vertex (i.e. 8 bytes * number of pairs * vertex count) 4 + 11*4*vertexCount + 4 + 8*texcoords.size()*vertexCount + // vertices 4 + 6*faceCount + // faces 4 + 7*4*boneCount + // bones 4 + propPointsSize // props )); // data size // Vertex data write(output, (uint32)vertexCount); write(output, (uint32)texcoords.size()); // UV pairs per vertex for (size_t i = 0; i < vertexCount; ++i) { output((char*)&position[i*3], 12); output((char*)&normal [i*3], 12); for (size_t s = 0; s < texcoords.size(); ++s) { output((char*)&texcoords[s][i*2], 8); } if (boneCount) write(output, boneWeights[i]); else write(output, noBlend); } // Face data write(output, (uint32)faceCount); for (size_t i = 0; i < indexCount; ++i) { write(output, (uint16)indices[i]); } // Bones data write(output, (uint32)boneCount); for (size_t i = 0; i < boneCount; ++i) { output((char*)&boneTransforms[i], 7*4); } // Prop points data write(output, (uint32)propPoints.size()); for (size_t i = 0; i < propPoints.size(); ++i) { uint32 nameLen = (uint32)propPoints[i].name.length(); write(output, nameLen); output(propPoints[i].name.c_str(), nameLen); write(output, propPoints[i].translation); write(output, propPoints[i].orientation); write(output, propPoints[i].bone); } } static FCDGeometryPolygons* GetPolysFromGeometry(FCDGeometry* geom) { REQUIRE(geom->IsMesh(), "geometry is mesh"); FCDGeometryMesh* mesh = geom->GetMesh(); if (! mesh->IsTriangles()) FCDGeometryPolygonsTools::Triangulate(mesh); REQUIRE(mesh->IsTriangles(), "mesh is made of triangles"); REQUIRE(mesh->GetPolygonsCount() == 1, "mesh has single set of polygons"); FCDGeometryPolygons* polys = mesh->GetPolygons(0); REQUIRE(polys->FindInput(FUDaeGeometryInput::POSITION) != NULL, "mesh has vertex positions"); REQUIRE(polys->FindInput(FUDaeGeometryInput::NORMAL) != NULL, "mesh has vertex normals"); REQUIRE(polys->FindInput(FUDaeGeometryInput::TEXCOORD) != NULL, "mesh has vertex tex coords"); return polys; } /** * Applies world-space transform to vertex data and transforms Collada's right-handed * Y-up / Z-up coordinates to the game's left-handed Y-up coordinate system * * TODO: Maybe we should use FCDocumentTools::StandardizeUpAxisAndLength in addition * to this, so we'd only have one up-axis case to worry about, but it doesn't seem to * correctly adjust the prop points in Y_UP models. */ static void TransformStaticModel(float* position, float* normal, size_t vertexCount, const FMMatrix44& transform, bool yUp) { for (size_t i = 0; i < vertexCount; ++i) { FMVector3 pos (&position[i*3], 0); FMVector3 norm (&normal[i*3], 0); // Apply the scene-node transforms pos = transform.TransformCoordinate(pos); norm = FMVector3_Normalize(transform.TransformVector(norm)); // Convert from right-handed Y_UP or Z_UP to the game's coordinate system (left-handed Y-up) if (yUp) { pos.z = -pos.z; norm.z = -norm.z; } else { std::swap(pos.y, pos.z); std::swap(norm.y, norm.z); } // Copy back to array position[i*3] = pos.x; position[i*3+1] = pos.y; position[i*3+2] = pos.z; normal[i*3] = norm.x; normal[i*3+1] = norm.y; normal[i*3+2] = norm.z; } } /** * Applies world-space transform to vertex data and transforms Collada's right-handed * Y-up / Z-up coordinates to the game's left-handed Y-up coordinate system * * TODO: Maybe we should use FCDocumentTools::StandardizeUpAxisAndLength in addition * to this, so we'd only have one up-axis case to worry about, but it doesn't seem to * correctly adjust the prop points in Y_UP models. */ static void TransformSkinnedModel(float* position, float* normal, size_t vertexCount, std::vector& bones, std::vector& propPoints, const FMMatrix44& transform, const FMMatrix44& bindTransform, bool yUp, bool isXSI) { FMMatrix44 scaledTransform; // for vertexes FMMatrix44 scaleMatrix; // for bones // HACK: see comment in PSAConvert::TransformVertices if (isXSI) { scaleMatrix = DecomposeToScaleMatrix(transform); scaledTransform = DecomposeToScaleMatrix(bindTransform) * transform; } else { scaleMatrix = FMMatrix44_Identity; scaledTransform = bindTransform; } // Update the vertex positions and normals for (size_t i = 0; i < vertexCount; ++i) { FMVector3 pos (&position[i*3], 0); FMVector3 norm (&normal[i*3], 0); // Apply the scene-node transforms pos = scaledTransform.TransformCoordinate(pos); norm = FMVector3_Normalize(scaledTransform.TransformVector(norm)); // Convert from right-handed Y_UP or Z_UP to the game's coordinate system (left-handed Y-up) if (yUp) { pos.z = -pos.z; norm.z = -norm.z; } else { std::swap(pos.y, pos.z); std::swap(norm.y, norm.z); } // and copy back into the original array position[i*3] = pos.x; position[i*3+1] = pos.y; position[i*3+2] = pos.z; normal[i*3] = norm.x; normal[i*3+1] = norm.y; normal[i*3+2] = norm.z; } TransformBones(bones, scaleMatrix, yUp); // And do the same for prop points for (size_t i = 0; i < propPoints.size(); ++i) { if (yUp) { propPoints[i].translation[0] = -propPoints[i].translation[0]; propPoints[i].orientation[0] = -propPoints[i].orientation[0]; propPoints[i].orientation[3] = -propPoints[i].orientation[3]; } else { // Flip translation across the x-axis by swapping y and z std::swap(propPoints[i].translation[1], propPoints[i].translation[2]); // To convert the quaternions: imagine you're using the axis/angle // representation, then swap the y,z basis vectors and change the // direction of rotation by negating the angle ( => negating sin(angle) // => negating x,y,z => changing (x,y,z,w) to (-x,-z,-y,w) // but then (-x,-z,-y,w) == (x,z,y,-w) so do that instead) std::swap(propPoints[i].orientation[1], propPoints[i].orientation[2]); propPoints[i].orientation[3] = -propPoints[i].orientation[3]; } } } }; // The above stuff is just in a class since I don't like having to bother // with forward declarations of functions - but provide the plain function // interface here: void ColladaToPMD(const char* input, OutputCB& output, std::string& xmlErrors) { PMDConvert::ColladaToPMD(input, output, xmlErrors); } ================================================ FILE: fpsgame/PMDConvert.h ================================================ /* Copyright (C) 2009 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_PMDCONVERT #define INCLUDED_PMDCONVERT #include struct OutputCB; void ColladaToPMD(const char* input, OutputCB& output, std::string& xmlErrors); #endif // INCLUDED_PMDCONVERT ================================================ FILE: fpsgame/PSAConvert.cpp ================================================ /* Copyright (C) 2013 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "PSAConvert.h" #include "CommonConvert.h" //#include "FCollada.h" //#include "FCDocument/FCDocument.h" //#include "FCDocument/FCDocumentTools.h" //#include "FCDocument/FCDAnimated.h" //#include "FCDocument/FCDAnimationCurve.h" //#include "FCDocument/FCDAnimationKey.h" //#include "FCDocument/FCDController.h" //#include "FCDocument/FCDControllerInstance.h" //#include "FCDocument/FCDExtra.h" //#include "FCDocument/FCDGeometry.h" //#include "FCDocument/FCDGeometryMesh.h" //#include "FCDocument/FCDGeometryPolygons.h" //#include "FCDocument/FCDGeometrySource.h" //#include "FCDocument/FCDSceneNode.h" #include "StdSkeletons.h" #include "Decompose.h" //#include "Maths.h" #include "GeomReindex.h" #include #include #include #include #include class PSAConvert { public: /** * Converts a COLLADA XML document into the PSA animation format. * * @param input XML document to parse * @param output callback for writing the PSA data; called lots of times * with small strings * @param xmlErrors output - errors reported by the XML parser * @throws ColladaException on failure */ static void ColladaToPSA(const char* input, OutputCB& output, std::string& xmlErrors) { CommonConvert converter(input, xmlErrors); if (converter.GetInstance().GetType() == FCDEntityInstance::CONTROLLER) { FCDControllerInstance& controllerInstance = static_cast(converter.GetInstance()); FixSkeletonRoots(controllerInstance); assert(converter.GetInstance().GetEntity()->GetType() == FCDEntity::CONTROLLER); // assume this is always true? FCDController* controller = static_cast(converter.GetInstance().GetEntity()); FCDSkinController* skin = controller->GetSkinController(); REQUIRE(skin != NULL, "is skin controller"); const Skeleton& skeleton = FindSkeleton(controllerInstance); float frameLength = 1.f / 30.f; // currently we always want to create PMDs at fixed 30fps // Find the extents of the animation: float timeStart = 0, timeEnd = 0; GetAnimationRange(converter.GetDocument(), skeleton, controllerInstance, timeStart, timeEnd); // To catch broken animations / skeletons.xml: REQUIRE(timeEnd > timeStart, "animation end frame must come after start frame"); // Count frames; don't include the last keyframe size_t frameCount = (size_t)((timeEnd - timeStart) / frameLength - 0.5f); REQUIRE(frameCount > 0, "animation must have frames"); // (TODO: sort out the timing/looping problems) size_t boneCount = skeleton.GetBoneCount(); std::vector boneTransforms; for (size_t frame = 0; frame < frameCount; ++frame) { float time = timeStart + frameLength * frame; BoneTransform boneDefault = { { 0, 0, 0 }, { 0, 0, 0, 1 } }; std::vector frameBoneTransforms (boneCount, boneDefault); // Move the model into the new animated pose // (We can't tell exactly which nodes should be animated, so // just update the entire world recursively) EvaluateAnimations(converter.GetRoot(), time); // Convert the pose into the form require by the game for (size_t i = 0; i < controllerInstance.GetJointCount(); ++i) { FCDSceneNode* joint = controllerInstance.GetJoint(i); int boneId = skeleton.GetRealBoneID(joint->GetName().c_str()); if (boneId < 0) continue; // not a recognised bone - ignore it, same as before FMMatrix44 worldTransform = joint->CalculateWorldTransform(); HMatrix matrix; memcpy(matrix, worldTransform.Transposed().m, sizeof(matrix)); AffineParts parts; decomp_affine(matrix, &parts); BoneTransform b = { { parts.t.x, parts.t.y, parts.t.z }, { parts.q.x, parts.q.y, parts.q.z, parts.q.w } }; frameBoneTransforms[boneId] = b; } // Push frameBoneTransforms onto the back of boneTransforms copy(frameBoneTransforms.begin(), frameBoneTransforms.end(), std::inserter(boneTransforms, boneTransforms.end())); } // Convert into game's coordinate space TransformVertices(boneTransforms, skin->GetBindShapeTransform(), converter.IsYUp(), converter.IsXSI()); // Write out the file WritePSA(output, frameCount, boneCount, boneTransforms); } else { throw ColladaException("Unrecognised object type"); } } /** * Writes the animation data in the PSA format. */ static void WritePSA(OutputCB& output, size_t frameCount, size_t boneCount, const std::vector& boneTransforms) { output("PSSA", 4); // magic number write(output, (uint32)1); // version number write(output, (uint32)( 4 + 0 + // name 4 + // frameLength 4 + 4 + // numBones, numFrames 7*4*boneCount*frameCount // boneStates )); // data size // Name write(output, (uint32)0); // Frame length write(output, 1000.f/30.f); write(output, (uint32)boneCount); write(output, (uint32)frameCount); for (size_t i = 0; i < boneCount*frameCount; ++i) { output((char*)&boneTransforms[i], 7*4); } } static void TransformVertices(std::vector& bones, const FMMatrix44& transform, bool yUp, bool isXSI) { // HACK: we want to handle scaling in XSI because that makes it easy // for artists to adjust the models to the right size. But this way // doesn't work in Max, and I can't see how to make it do so, so this // is only applied to models from XSI. if (isXSI) { TransformBones(bones, DecomposeToScaleMatrix(transform), yUp); } else { TransformBones(bones, FMMatrix44_Identity, yUp); } } static void GetAnimationRange(const FColladaDocument& doc, const Skeleton& skeleton, const FCDControllerInstance& controllerInstance, float& timeStart, float& timeEnd) { // FCollada tools export info in the scene to specify the start // and end times. // If that isn't available, we have to search for the earliest and latest // keyframes on any of the bones. if (doc.GetDocument()->HasStartTime() && doc.GetDocument()->HasEndTime()) { timeStart = doc.GetDocument()->GetStartTime(); timeEnd = doc.GetDocument()->GetEndTime(); return; } // XSI exports relevant information in // // (and 'end' and 'frameRate') so use those if (GetAnimationRange_XSI(doc, timeStart, timeEnd)) return; timeStart = std::numeric_limits::max(); timeEnd = -std::numeric_limits::max(); for (size_t i = 0; i < controllerInstance.GetJointCount(); ++i) { const FCDSceneNode* joint = controllerInstance.GetJoint(i); REQUIRE(joint != NULL, "joint exists"); int boneId = skeleton.GetBoneID(joint->GetName().c_str()); if (boneId < 0) { // unrecognised joint - it's probably just a prop point // or something, so ignore it continue; } // Skip unanimated joints if (joint->GetTransformCount() == 0) continue; for (size_t j = 0; j < joint->GetTransformCount(); ++j) { const FCDTransform* transform = joint->GetTransform(j); if (! transform->IsAnimated()) continue; // Iterate over all curves to find the earliest and latest keys const FCDAnimated* anim = transform->GetAnimated(); const FCDAnimationCurveListList& curvesList = anim->GetCurves(); for (size_t j = 0; j < curvesList.size(); ++j) { const FCDAnimationCurveTrackList& curves = curvesList[j]; for (size_t k = 0; k < curves.size(); ++k) { const FCDAnimationCurve* curve = curves[k]; timeStart = std::min(timeStart, curve->GetKeys()[0]->input); timeEnd = std::max(timeEnd, curve->GetKeys()[curve->GetKeyCount()-1]->input); } } } } } static bool GetAnimationRange_XSI(const FColladaDocument& doc, float& timeStart, float& timeEnd) { FCDExtra* extra = doc.GetExtra(); if (! extra) return false; FCDEType* type = extra->GetDefaultType(); if (! type) return false; FCDETechnique* technique = type->FindTechnique("XSI"); if (! technique) return false; FCDENode* scene = technique->FindChildNode("SI_Scene"); if (! scene) return false; float start = FLT_MAX, end = -FLT_MAX, framerate = 0.f; FCDENodeList paramNodes; scene->FindChildrenNodes("xsi_param", paramNodes); for (FCDENodeList::iterator it = paramNodes.begin(); it != paramNodes.end(); ++it) { if ((*it)->ReadAttribute("sid") == "start") start = FUStringConversion::ToFloat((*it)->GetContent()); else if ((*it)->ReadAttribute("sid") == "end") end = FUStringConversion::ToFloat((*it)->GetContent()); else if ((*it)->ReadAttribute("sid") == "frameRate") framerate = FUStringConversion::ToFloat((*it)->GetContent()); } if (framerate != 0.f && start != FLT_MAX && end != -FLT_MAX) { timeStart = start / framerate; timeEnd = end / framerate; return true; } return false; } static void EvaluateAnimations(FCDSceneNode& node, float time) { for (size_t i = 0; i < node.GetTransformCount(); ++i) { FCDTransform* transform = node.GetTransform(i); FCDAnimated* anim = transform->GetAnimated(); if (anim) anim->Evaluate(time); } for (size_t i = 0; i < node.GetChildrenCount(); ++i) EvaluateAnimations(*node.GetChild(i), time); } }; // The above stuff is just in a class since I don't like having to bother // with forward declarations of functions - but provide the plain function // interface here: void ColladaToPSA(const char* input, OutputCB& output, std::string& xmlErrors) { PSAConvert::ColladaToPSA(input, output, xmlErrors); } ================================================ FILE: fpsgame/PSAConvert.h ================================================ /* Copyright (C) 2009 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_PSACONVERT #define INCLUDED_PSACONVERT #include struct OutputCB; void ColladaToPSA(const char* input, OutputCB& output, std::string& xmlErrors); #endif // INCLUDED_PSACONVERT ================================================ FILE: fpsgame/Resource.h ================================================ //{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by fpsgame.rc // #define IDS_APP_TITLE 103 #define IDR_MAINFRAME 128 #define IDD_FPSGAME_DIALOG 102 #define IDD_ABOUTBOX 103 #define IDM_ABOUT 104 #define IDM_EXIT 105 #define IDI_FPSGAME 107 #define IDI_SMALL 108 #define IDC_FPSGAME 109 #define IDC_MYICON 2 #ifndef IDC_STATIC #define IDC_STATIC -1 #endif #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NO_MFC 130 #define _APS_NEXT_RESOURCE_VALUE 129 #define _APS_NEXT_COMMAND_VALUE 32771 #define _APS_NEXT_CONTROL_VALUE 1000 #define _APS_NEXT_SYMED_VALUE 110 #endif #endif ================================================ FILE: fpsgame/SkillSystem.cpp ================================================ #include "SkillSystem.h" #include "BulletSystem.h" #include "interface.h" #include "GameProcess.h" #include "ObjectParticles.h" CSkillSystem::CSkillSystem(CGameProcess* pGameProcess) { CBulletSystem::GetInstance()->SetCallback(MakeInterface(this, &CSkillSystem::OnSkillCallback)); m_pGameProcess = pGameProcess; AddListen(BR_PARTICLE_HIT, MakeMessageFunc(this, &CSkillSystem::OnParticleHit)); } CSkillSystem::~CSkillSystem(void) { CBulletSystem::ReleaseInstance(); } int CSkillSystem::OnSkillCallback(void* pVoid) { _BulletCallback* pBase = (_BulletCallback*)pVoid; switch (pBase->nMsgID) { case _BulletCallback::GetTargetPosition: { _Bullet_GetTargetPosition* pBullet = (_Bullet_GetTargetPosition*)pBase; CRoleBase* pRole = m_pGameProcess->GetRole(pBullet->nTarget);//test if (pRole) { pBullet->nRetState = 1; pBullet->vRetPos = pRole->GetPosition() + vec3(0.0f, 0.0f, 1.2f); pBullet->vRetDir = pRole->GetDirection(); } else { pBullet->nRetState = 0; } }break; case _BulletCallback::HitTarget: { _Bullet_HitTarget* pBullet = (_Bullet_HitTarget*)pBase; }break; case _BulletCallback::GetCollision: { _Bullet_GetCollision* pBullet = (_Bullet_GetCollision*)pBase; //test for (int i = 0; i < 20; i++) { CRoleBase* pRole = m_pGameProcess->GetRoleFormIndex(i); if (pRole) { if ((pRole->GetPosition() - pBullet->vPos).length() < pBullet->fRadius + pRole->GetRoleRadius()) { pBullet->nRetTarget = pRole->GetRoleID(); pBullet->vRetHitPoint = pRole->GetPosition() + vec3(0.0f, 0.0f, 1.2f); } } } }break; case _BulletCallback::Blast: { _Bullet_Blast* pBullet = (_Bullet_Blast*)pBase; }break; } return 1; } void CSkillSystem::Update(float ifps) { CBulletSystem::GetInstance()->Update(); } void CSkillSystem::Reset() { CBulletSystem::GetInstance()->Reset(); } int CSkillSystem::OnParticleHit(void* v1, void* v2, void* v3) { CObjectParticles* pPar = (CObjectParticles*)v1; CBRObject* pHitObject = (CBRObject*)v2; int nContact = *(int*)v3; int nParID = atoi(pPar->GetName()); int nObjID = atoi(pHitObject->GetName()); return 1; } ================================================ FILE: fpsgame/SkillSystem.h ================================================ #pragma once #include "MessageBase.h" class CInterfaceBase; class CGameProcess; class CSkillSystem : public CMessageBase //ϵͳ̳Ϣ { public: CSkillSystem(CGameProcess* pGameProcess); virtual ~CSkillSystem(void); public: void Update(float ifps); void Reset(); protected: int OnParticleHit(void* v1, void* v2, void* v3); int OnSkillCallback(void* pVoid); CGameProcess* m_pGameProcess; }; ================================================ FILE: fpsgame/Star2DControl.cpp ================================================ #include "Star2DControl.h" #include "Engine.h" using namespace MathLib; CStar2DControl::CStar2DControl(void) { m_pNormalTex = NULL; m_pClickTex = NULL; m_nEnabled = 1; m_nClickState = 0; m_fClickScale = 1.0f; m_vNormalColor = vec4_one; m_vClickColor = vec4_one; m_fNormalTexScale = 0.1f; m_fClickTexScale = 0.1f; Init(); } CStar2DControl::~CStar2DControl(void) { g_Engine.pGui->ReleaseTexture(m_pNormalTex); g_Engine.pGui->ReleaseTexture(m_pClickTex); } int CStar2DControl::Init() { return Load(NORMAL_NAME, CLICK_NAME); } void CStar2DControl::Update() { if (!IsEnable()) { return; } m_fClickScale += m_nClickState * g_Engine.pGame->GetIFps() * 5.5f; if (m_fClickScale <= 0.8f) { m_nClickState = 1; } else if (m_fClickScale >= 1.0f) { m_fClickScale = 1.0f; m_nClickState = 0; } if (m_pNormalTex && m_pNormalTex->GetTexture()) { CTexture *tex = m_pNormalTex->GetTexture(); float width = CMathCore::Itof(tex->GetWidth()); float height = CMathCore::Itof(tex->GetHeight()); if (m_fNormalTexScale != 0.0f) { float aspect = height / width; width = g_Engine.pGui->GetWidth() * m_fNormalTexScale; height = width * aspect; } float CenterX = g_Engine.pGui->GetWidth() / 2.0f; float CenterY = g_Engine.pGui->GetHeight() / 2.0f; float x0 = CenterX - width / 2.0f; float y0 = CenterY - height / 2.0f; float x1 = CenterX + width / 2.0f; float y1 = CenterY + height / 2.0f; g_Engine.pGui->RenderTexture(tex, vec4(x0, y0, x1, y1), m_vNormalColor); } if (m_pClickTex && m_pClickTex->GetTexture()) { CTexture *tex = m_pClickTex->GetTexture(); float width = CMathCore::Itof(tex->GetWidth()); float height = CMathCore::Itof(tex->GetHeight()); if (m_fClickTexScale != 0.0f) { float aspect = height / width; width = g_Engine.pGui->GetWidth() * m_fClickTexScale; height = width * aspect; } width *= m_fClickScale; height *= m_fClickScale; float CenterX = g_Engine.pGui->GetWidth() / 2.0f; float CenterY = g_Engine.pGui->GetHeight() / 2.0f; float x0 = CenterX - width / 2.0f; float y0 = CenterY - height / 2.0f; float x1 = CenterX + width / 2.0f; float y1 = CenterY + height / 2.0f; g_Engine.pGui->RenderTexture(tex, vec4(x0, y0, x1, y1), m_vClickColor); } } void CStar2DControl::Click() { if(m_nClickState != -1) m_nClickState = -1; } int CStar2DControl::Load(const char* strNormal, const char* strClick) { if (m_strNormalName != strNormal) { m_strNormalName = strNormal; g_Engine.pGui->ReleaseTexture(m_pNormalTex); m_pNormalTex = g_Engine.pGui->CreateTexture(strNormal); } if (m_strClickName != strClick) { m_strClickName = strClick; g_Engine.pGui->ReleaseTexture(m_pClickTex); m_pClickTex = g_Engine.pGui->CreateTexture(strClick); } return 0; } int CStar2DControl::IsEnable() { return m_nEnabled; } void CStar2DControl::SetEnable(int nEnable) { m_nEnabled = nEnable; } void CStar2DControl::SetNormalColor(const vec4& vColor) { m_vNormalColor = vColor; } const MathLib::vec4& CStar2DControl::GetNormalColor() const { return m_vNormalColor; } void CStar2DControl::SetClickColor(const vec4& vColor) { m_vClickColor = vColor; } const MathLib::vec4& CStar2DControl::GetClickColor() const { return m_vClickColor; } void CStar2DControl::SetNormalScale(float fScale) { m_fNormalTexScale = fScale; } float CStar2DControl::GetNormalScale() const { return m_fNormalTexScale; } void CStar2DControl::SetClickScale(float fScale) { m_fClickTexScale = fScale; } float CStar2DControl::GetClickScale() const { return m_fClickTexScale; } ================================================ FILE: fpsgame/Star2DControl.h ================================================ #pragma once #include "MathLib.h" #include "Singleton.h" #include "UtilStr.h" class CBRObject; class CTextureImage; class CStar2DControl : public CSingleton < CStar2DControl > { public: CStar2DControl(void); ~CStar2DControl(void); public: int Init(); void Update(); void Click(); int Load(const char* strNormal, const char* strClick); void SetEnable(int nEnable); int IsEnable(); void SetNormalColor(const MathLib::vec4& vColor); const MathLib::vec4& GetNormalColor() const; void SetClickColor(const MathLib::vec4& vColor); const MathLib::vec4& GetClickColor() const; //Ļռ 01ֵ void SetNormalScale(float fScale); float GetNormalScale() const; void SetClickScale(float fScale); float GetClickScale() const; protected: CUtilStr m_strNormalName; CUtilStr m_strClickName; CTextureImage *m_pNormalTex; CTextureImage *m_pClickTex; int m_nEnabled; int m_nClickState; float m_fClickScale; MathLib::vec4 m_vNormalColor; MathLib::vec4 m_vClickColor; float m_fNormalTexScale; float m_fClickTexScale; }; ================================================ FILE: fpsgame/StartControl.cpp ================================================ #include "StarControl.h" #include "ObjectParticles.h" #include "Engine.h" #include "Game.h" #include "Object.h" #include "Player.h" #include "Common.h" #include "App.h" CStarControl::CStarControl(void) { m_pStarNormal = NULL; m_pStarClick = NULL; m_fViewDistance = 0.5f; m_nClickState = 0; m_fClickScale = 1.0f; Init(); } CStarControl::~CStarControl(void) { g_Engine.pGame->RemoveNode(m_pStarNormal); g_Engine.pGame->RemoveNode(m_pStarClick); } int CStarControl::Init() { return Load("data/StarControl/Star_mesh.node", "data/StarControl/Star_click.node"); } void CStarControl::Update(const vec3& vPos, const vec3& vDir) { m_fClickScale += m_nClickState * g_Engine.pGame->GetIFps() * 5.5f; if (m_fClickScale <= 0.8f) { m_nClickState = 1; } else if (m_fClickScale >= 1.0f) { m_fClickScale = 1.0f; m_nClickState = 0; } float fViewDistance = m_fViewDistance; mat4 matStar = Translate(vPos + vDir * fViewDistance); CPlayer* pPlayer = g_Engine.pGame->GetPlayer(); if (pPlayer) { vec3 x = pPlayer->GetModelview().getRow3(0); vec3 y = pPlayer->GetModelview().getRow3(1); vec3 z = pPlayer->GetModelview().getRow3(2); matStar.setColumn3(0, x); matStar.setColumn3(1, -z); matStar.setColumn3(2, y); } vec4 pos = vec4(1.0f, 0.0f, 0.0f, 1.0f); float s = CCommon::CalcAxisScale(g_Engine.pGame->GetPlayer()->GetModelview(), g_Engine.pGame->GetPlayer()->GetFov(), vec4(matStar.getColumn3(3), 1.0f), 200.0f, CMathCore::Itof(g_Engine.pApp->GetHeight())); m_pStarNormal->SetWorldTransform(matStar * Scale(s, 1.0f, s)); m_pStarClick->SetWorldTransform(matStar * Scale(m_fClickScale, 1.0f, m_fClickScale) * Scale(s, 1.0f, s)); } void CStarControl::Click() { m_nClickState = -1; } int CStarControl::Load(const char* strNormal, const char* strClick) { if (m_pStarClick && m_pStarNormal) { g_Engine.pGame->RemoveNode(m_pStarNormal); g_Engine.pGame->RemoveNode(m_pStarClick); } m_pStarNormal = (CBRObject*)g_Engine.pGame->LoadNode(strNormal); m_pStarClick = (CBRObject*)g_Engine.pGame->LoadNode(strClick); if (m_pStarClick && m_pStarNormal) { return 1; } return 0; } int CStarControl::isEnable() { return m_pStarClick->IsEnabled(); } void CStarControl::SetEnable(int nEnable) { m_pStarClick->SetEnabled(nEnable); m_pStarNormal->SetEnabled(nEnable); } void CStarControl::SetColor(const vec4& vColor) { m_pStarNormal->SetObjectColor(vColor); } ================================================ FILE: fpsgame/StartControl.h ================================================ #pragma once #include "MathLib.h" #include "Singleton.h" class CBRObject; using namespace MathLib; class CStarControl : public CSingleton { public: CStarControl(void); ~CStarControl(void); public: int Init(); void Update(const vec3& vPos, const vec3& vDir); void Click(); int Load(const char* strNormal, const char* strClick); int isEnable(); void SetEnable(int nEnable); void SetViewDistance(float fViewDistance) { m_fViewDistance = fViewDistance; } void SetColor(const vec4& vColor); //ô4ά protected: CBRObject* m_pStarNormal; CBRObject* m_pStarClick; float m_fViewDistance; int m_nClickState; float m_fClickScale; }; ================================================ FILE: fpsgame/StdSkeletons.cpp ================================================ #include "precompiled.h" #include "StdSkeletons.h" #include "libxml/parser.h" #include "libxml/xmlerror.h" #include "CommonConvert.h" #include "FUtils/FUXmlParser.h" #include namespace { struct SkeletonMap : public std::map { SkeletonMap() { } ~SkeletonMap() { for (iterator it = begin(); it != end(); ++it) delete it->second; } }; SkeletonMap g_StandardSkeletons; SkeletonMap g_MappedSkeletons; struct Bone { std::string parent; std::string name; int targetId; int realTargetId; }; } const Skeleton* Skeleton::FindSkeleton(const std::string& name) { return g_MappedSkeletons[name]; } Skeleton::Skeleton() : m(new Skeleton_impl) { } Skeleton::~Skeleton() { } struct Skeleton_impl { std::string title; std::vector bones; const Skeleton* target; }; int Skeleton::GetBoneID(const std::string& name) const { for (size_t i = 0; i < m->bones.size(); ++i) if (m->bones[i].name == name) return m->bones[i].targetId; return -1; } int Skeleton::GetRealBoneID(const std::string& name) const { for (size_t i = 0; i < m->bones.size(); ++i) if (m->bones[i].name == name) return m->bones[i].realTargetId; return -1; } int Skeleton::GetBoneCount() const { return (int)m->target->m->bones.size(); } namespace { bool AlreadyUsedTargetBone(const std::vector& bones, int targetId) { for (size_t i = 0; i < bones.size(); ++i) if (bones[i].targetId == targetId) return true; return false; } // Recursive helper function used by LoadSkeletonData void LoadSkeletonBones(xmlNode* parent, std::vector& bones, const Skeleton* targetSkeleton, const std::string& targetName) { xmlNodeList boneNodes; FUXmlParser::FindChildrenByType(parent, "bone", boneNodes); for (xmlNodeList::iterator boneNode = boneNodes.begin(); boneNode != boneNodes.end(); ++boneNode) { std::string name(FUXmlParser::ReadNodeProperty(*boneNode, "name")); Bone b; b.name = name; std::string newTargetName = targetName; if (targetSkeleton) { xmlNode* targetNode = FUXmlParser::FindChildByType(*boneNode, "target"); if (targetNode) newTargetName = FUXmlParser::ReadNodeContentFull(targetNode); // else fall back to the parent node's target b.targetId = targetSkeleton->GetBoneID(newTargetName); REQUIRE(b.targetId != -1, "skeleton bone target matches some standard_skeleton bone name"); if (AlreadyUsedTargetBone(bones, b.targetId)) b.realTargetId = -1; else b.realTargetId = b.targetId; } else { // No target - this is a standard skeleton b.targetId = (int)bones.size(); b.realTargetId = b.targetId; } bones.push_back(b); LoadSkeletonBones(*boneNode, bones, targetSkeleton, newTargetName); } } void LoadSkeletonData(xmlNode* root) { xmlNodeList skeletonNodes; FUXmlParser::FindChildrenByType(root, "standard_skeleton", skeletonNodes); FUXmlParser::FindChildrenByType(root, "skeleton", skeletonNodes); for (xmlNodeList::iterator skeletonNode = skeletonNodes.begin(); skeletonNode != skeletonNodes.end(); ++skeletonNode) { std::unique_ptr skeleton(new Skeleton()); std::string title(FUXmlParser::ReadNodeProperty(*skeletonNode, "title")); skeleton->m->title = title; if (IsEquivalent((*skeletonNode)->name, "standard_skeleton")) { skeleton->m->target = NULL; LoadSkeletonBones(*skeletonNode, skeleton->m->bones, NULL, ""); std::string id(FUXmlParser::ReadNodeProperty(*skeletonNode, "id")); REQUIRE(!id.empty(), "standard_skeleton has id"); g_StandardSkeletons[id] = skeleton.release(); } else { // Non-standard skeletons need to choose a standard skeleton // as their target to be mapped onto std::string target(FUXmlParser::ReadNodeProperty(*skeletonNode, "target")); const Skeleton* targetSkeleton = g_StandardSkeletons[target]; REQUIRE(targetSkeleton != NULL, "skeleton target matches some standard_skeleton id"); skeleton->m->target = targetSkeleton; LoadSkeletonBones(*skeletonNode, skeleton->m->bones, targetSkeleton, ""); // Currently the only supported identifier is a precise name match, // so just look for that xmlNode* identifier = FUXmlParser::FindChildByType(*skeletonNode, "identifier"); REQUIRE(identifier != NULL, "skeleton has "); xmlNode* identRoot = FUXmlParser::FindChildByType(identifier, "root"); REQUIRE(identRoot != NULL, "skeleton identifier has "); std::string identRootName(FUXmlParser::ReadNodeContentFull(identRoot)); g_MappedSkeletons[identRootName] = skeleton.release(); } } } } void errorHandler(void* ctx, const char* msg, ...); void Skeleton::LoadSkeletonDataFromXml(const char* xmlData, size_t xmlLength, std::string& xmlErrors) { xmlDoc* doc = NULL; try { xmlSetGenericErrorFunc(&xmlErrors, &errorHandler); doc = xmlParseMemory(xmlData, (int)xmlLength); if (doc) { xmlNode* root = xmlDocGetRootElement(doc); LoadSkeletonData(root); xmlFreeDoc(doc); doc = NULL; } xmlSetGenericErrorFunc(NULL, NULL); } catch (const ColladaException&) { if (doc) xmlFreeDoc(doc); xmlSetGenericErrorFunc(NULL, NULL); throw; } if (!xmlErrors.empty()) throw ColladaException("XML parsing failed"); } ================================================ FILE: fpsgame/StdSkeletons.h ================================================ #pragma once #ifndef INCLUDED_STDSKELETONS #define INCLUDED_STDSKELETONS #include #include struct Skeleton_impl; class Sketeton { public: /** Default constructor - don't use; use FindSkeleton instead. */ Skeleton(); ~Skeleton(); /** * Returns the number of bones in the standard-skeleton which this current * skeleton is mapped onto. */ int GetBoneCount() const; /** * Tries to find a skeleton that matches the given root bone name. * Returns NULL if there is no match. */ static const Skeleton* FindSkeleton(const std::string& rootBoneName); int GetBoneID(const std::string& name) const; int GetRealBoneID(const std::string& name) const; static void LoadSkeletonDataFromXml(const char* xmlData, size_t xmlLength, std::string& xmlErrors); std::unique_ptr m; private: Skeleton(Skeleton&); }; #endif // INCLUDED_STDSKELETONS ================================================ FILE: fpsgame/XMLFix.cpp ================================================ /* Copyright (C) 2009 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "XMLFix.h" #include "CommonConvert.h" #include "FUtils/FUXmlParser.h" /* Things that are fixed here: ---- 3ds Max "file://" image URLs Identifier: /COLLADA/asset/contributor/authoring_tool = "FBX COLLADA exporter" Problem: /COLLADA/library_images/image/init_from = "file://" which crashes some versions of FCollada Fix: Delete the whole library_images subtree, since we never use it anyway. Then delete library_effects and library_materials too, to avoid broken references. ---- 3ds Max broken material references Identifier: /COLLADA/asset/contributor/authoring_tool = "FBX COLLADA exporter" Problem: /COLLADA/library_visual_scenes/.../instance_material/@target sometimes refers to non-existent material IDs. Fix: Delete the whole bind_material subtree, since we never use it anyway. ---- */ static xmlNode* findChildElement(xmlNode* node, const char* name) { for (xmlNode* child = node->children; child; child = child->next) { if (child->type == XML_ELEMENT_NODE && strcmp((const char*)child->name, name) == 0) return child; } return NULL; } static bool applyFBXFixesNode(xmlNode* node) { bool changed = false; for (xmlNode* child = node->children; child; child = child->next) { if (child->type == XML_ELEMENT_NODE) { if (strcmp((const char*)child->name, "node") == 0) { if (applyFBXFixesNode(child)) changed = true; } else if (strcmp((const char*)child->name, "instance_geometry") == 0) { xmlNode* bind_material = findChildElement(child, "bind_material"); if (! bind_material) continue; Log(LOG_INFO, "Found a bind_material to delete"); xmlUnlinkNode(bind_material); xmlFreeNode(bind_material); changed = true; } } } return changed; } static bool applyFBXFixes(xmlNode* root) { Log(LOG_INFO, "Applying fixes for 3ds Max exporter"); bool changed = false; xmlNode* library_images = findChildElement(root, "library_images"); if (library_images) { Log(LOG_INFO, "Found library_images to delete"); xmlUnlinkNode(library_images); xmlFreeNode(library_images); changed = true; } xmlNode* library_materials = findChildElement(root, "library_materials"); if (library_materials) { Log(LOG_INFO, "Found library_materials to delete"); xmlUnlinkNode(library_materials); xmlFreeNode(library_materials); changed = true; } xmlNode* library_effects = findChildElement(root, "library_effects"); if (library_effects) { Log(LOG_INFO, "Found library_effects to delete"); xmlUnlinkNode(library_effects); xmlFreeNode(library_effects); changed = true; } xmlNode* library_visual_scenes = findChildElement(root, "library_visual_scenes"); if (library_visual_scenes) // (Assume there's only one of these) { xmlNode* visual_scene = findChildElement(library_visual_scenes, "visual_scene"); if (visual_scene) // (Assume there's only one of these) { for (xmlNode* child = visual_scene->children; child; child = child->next) { if (child->type == XML_ELEMENT_NODE && strcmp((const char*)child->name, "node") == 0) if (applyFBXFixesNode(child)) changed = true; } } } return changed; } static bool processDocument(xmlNode* root) { xmlNode* asset = findChildElement(root, "asset"); if (! asset) return false; xmlNode* contributor = findChildElement(asset, "contributor"); if (! contributor) return false; xmlNode* authoring_tool = findChildElement(contributor, "authoring_tool"); if (! authoring_tool) return false; xmlNode* authoring_tool_text = authoring_tool->children; if (! authoring_tool_text) return false; if (authoring_tool_text->type != XML_TEXT_NODE) return false; xmlChar* toolname = authoring_tool_text->content; Log(LOG_INFO, "Authoring tool: %s", toolname); if (strcmp((const char*)toolname, "FBX COLLADA exporter") == 0) return applyFBXFixes(root); else return false; } void FixBrokenXML(const char* text, const char** out, size_t* outSize) { Log(LOG_INFO, "Running FixBrokenXML"); size_t textSize = strlen(text); xmlDocPtr doc = xmlParseMemory(text, (int)textSize); xmlNode* root = xmlDocGetRootElement(doc); if (root && processDocument(root)) { // Reserialising the document, then parsing it again inside FCollada, is a bit ugly; // but it's the only way I can see to make it work through FCollada's public API xmlChar* mem = NULL; int size = -1; xmlDocDumpFormatMemory(doc, &mem, &size, 0); *out = (const char*)mem; *outSize = size; } else { *out = text; *outSize = textSize; } xmlFreeDoc(doc); } ================================================ FILE: fpsgame/XMLFix.h ================================================ /* Copyright (C) 2009 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef XMLFIX_INCLUDED #define XMLFIX_INCLUDED /** * Fixes some errors in COLLADA XML files that would otherwise prevent * FCollada from loading them successfully. * 'out' is either a new XML document, which must be freed with xmlFree; * otherwise it is equal to 'text' if no changes were made. */ void FixBrokenXML(const char* text, const char** out, size_t* outSize); #endif // XMLFIX_INCLUDED ================================================ FILE: fpsgame/fpsgame.cpp ================================================ #include "stdafx.h" #include "fpsgame.h" #include #define MAX_LOADSTRING 100 ================================================ FILE: fpsgame/fpsgame.h ================================================ #pragma once #include "stdafx.h" ================================================ FILE: fpsgame/fpsgame.vcxproj ================================================  Debug Win32 Release Win32 Debug x64 Release x64 15.0 {057B5724-2848-4AB5-91E2-D9F26C9E9BB6} Win32Proj fpsgame 10.0.15063.0 Application true v141 Unicode Application false v141 true Unicode Application true v141 Unicode Application false v141 true Unicode true true false false Use Level3 Disabled WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) Windows NotUsing Level3 Disabled _DEBUG;_WINDOWS;%(PreprocessorDefinitions) Windows Level3 Use MaxSpeed true true WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) Windows true true Level3 Use MaxSpeed true true NDEBUG;_WINDOWS;%(PreprocessorDefinitions) Windows true true ================================================ FILE: fpsgame/fpsgame.vcxproj.user ================================================  ================================================ FILE: fpsgame/graphics/Camera.cpp ================================================ /* Copyright (C) 2010 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /* * CCamera holds a view and a projection matrix. It also has a frustum * which can be used to cull objects for rendering. */ #include "precompiled.h" #include "Camera.h" #include "graphics/HFTracer.h" #include "graphics/Terrain.h" #include "lib/ogl.h" #include "maths/MathUtil.h" #include "maths/Vector4D.h" #include "ps/Game.h" #include "ps/World.h" #include "renderer/Renderer.h" #include "renderer/WaterManager.h" CCamera::CCamera() { // set viewport to something anything should handle, but should be initialised // to window size before use m_ViewPort.m_X = 0; m_ViewPort.m_Y = 0; m_ViewPort.m_Width = 800; m_ViewPort.m_Height = 600; } CCamera::~CCamera() { } void CCamera::SetProjection(float nearp, float farp, float fov) { m_NearPlane = nearp; m_FarPlane = farp; m_FOV = fov; float aspect = (float)m_ViewPort.m_Width / (float)m_ViewPort.m_Height; float f = 1.0f / tanf(m_FOV / 2); m_ProjMat.SetZero(); m_ProjMat._11 = f / aspect; m_ProjMat._22 = f; m_ProjMat._33 = -(m_FarPlane + m_NearPlane) / (m_NearPlane - m_FarPlane); m_ProjMat._34 = 2 * m_FarPlane*m_NearPlane / (m_NearPlane - m_FarPlane); m_ProjMat._43 = 1.0f; } void CCamera::SetProjectionTile(int tiles, int tile_x, int tile_y) { float aspect = (float)m_ViewPort.m_Width / (float)m_ViewPort.m_Height; float f = 1.0f / tanf(m_FOV / 2); m_ProjMat._11 = tiles*f / aspect; m_ProjMat._22 = tiles*f; m_ProjMat._13 = -(1 - tiles + 2 * tile_x); m_ProjMat._23 = -(1 - tiles + 2 * tile_y); } //Updates the frustum planes. Should be called //everytime the view or projection matrices are //altered. void CCamera::UpdateFrustum(const CBoundingBoxAligned& scissor) { CMatrix3D MatFinal; CMatrix3D MatView; m_Orientation.GetInverse(MatView); MatFinal = m_ProjMat * MatView; m_ViewFrustum.SetNumPlanes(6); // get the RIGHT plane m_ViewFrustum.m_aPlanes[0].m_Norm.X = scissor[1].X*MatFinal._41 - MatFinal._11; m_ViewFrustum.m_aPlanes[0].m_Norm.Y = scissor[1].X*MatFinal._42 - MatFinal._12; m_ViewFrustum.m_aPlanes[0].m_Norm.Z = scissor[1].X*MatFinal._43 - MatFinal._13; m_ViewFrustum.m_aPlanes[0].m_Dist = scissor[1].X*MatFinal._44 - MatFinal._14; // get the LEFT plane m_ViewFrustum.m_aPlanes[1].m_Norm.X = -scissor[0].X*MatFinal._41 + MatFinal._11; m_ViewFrustum.m_aPlanes[1].m_Norm.Y = -scissor[0].X*MatFinal._42 + MatFinal._12; m_ViewFrustum.m_aPlanes[1].m_Norm.Z = -scissor[0].X*MatFinal._43 + MatFinal._13; m_ViewFrustum.m_aPlanes[1].m_Dist = -scissor[0].X*MatFinal._44 + MatFinal._14; // get the BOTTOM plane m_ViewFrustum.m_aPlanes[2].m_Norm.X = -scissor[0].Y*MatFinal._41 + MatFinal._21; m_ViewFrustum.m_aPlanes[2].m_Norm.Y = -scissor[0].Y*MatFinal._42 + MatFinal._22; m_ViewFrustum.m_aPlanes[2].m_Norm.Z = -scissor[0].Y*MatFinal._43 + MatFinal._23; m_ViewFrustum.m_aPlanes[2].m_Dist = -scissor[0].Y*MatFinal._44 + MatFinal._24; // get the TOP plane m_ViewFrustum.m_aPlanes[3].m_Norm.X = scissor[1].Y*MatFinal._41 - MatFinal._21; m_ViewFrustum.m_aPlanes[3].m_Norm.Y = scissor[1].Y*MatFinal._42 - MatFinal._22; m_ViewFrustum.m_aPlanes[3].m_Norm.Z = scissor[1].Y*MatFinal._43 - MatFinal._23; m_ViewFrustum.m_aPlanes[3].m_Dist = scissor[1].Y*MatFinal._44 - MatFinal._24; // get the FAR plane m_ViewFrustum.m_aPlanes[4].m_Norm.X = scissor[1].Z*MatFinal._41 - MatFinal._31; m_ViewFrustum.m_aPlanes[4].m_Norm.Y = scissor[1].Z*MatFinal._42 - MatFinal._32; m_ViewFrustum.m_aPlanes[4].m_Norm.Z = scissor[1].Z*MatFinal._43 - MatFinal._33; m_ViewFrustum.m_aPlanes[4].m_Dist = scissor[1].Z*MatFinal._44 - MatFinal._34; // get the NEAR plane m_ViewFrustum.m_aPlanes[5].m_Norm.X = -scissor[0].Z*MatFinal._41 + MatFinal._31; m_ViewFrustum.m_aPlanes[5].m_Norm.Y = -scissor[0].Z*MatFinal._42 + MatFinal._32; m_ViewFrustum.m_aPlanes[5].m_Norm.Z = -scissor[0].Z*MatFinal._43 + MatFinal._33; m_ViewFrustum.m_aPlanes[5].m_Dist = -scissor[0].Z*MatFinal._44 + MatFinal._34; for (size_t i = 0; i < 6; ++i) m_ViewFrustum.m_aPlanes[i].Normalize(); } void CCamera::ClipFrustum(const CPlane& clipPlane) { CPlane normClipPlane = clipPlane; normClipPlane.Normalize(); m_ViewFrustum.AddPlane(normClipPlane); } void CCamera::SetViewPort(const SViewPort& viewport) { m_ViewPort.m_X = viewport.m_X; m_ViewPort.m_Y = viewport.m_Y; m_ViewPort.m_Width = viewport.m_Width; m_ViewPort.m_Height = viewport.m_Height; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////// // GetCameraPlanePoints: return four points in camera space at given distance from camera void CCamera::GetCameraPlanePoints(float dist, CVector3D pts[4]) const { float aspect = float(m_ViewPort.m_Width) / float(m_ViewPort.m_Height); float x = dist*aspect*tanf(m_FOV*0.5f); float y = dist*tanf(m_FOV*0.5f); pts[0].X = -x; pts[0].Y = -y; pts[0].Z = dist; pts[1].X = x; pts[1].Y = -y; pts[1].Z = dist; pts[2].X = x; pts[2].Y = y; pts[2].Z = dist; pts[3].X = -x; pts[3].Y = y; pts[3].Z = dist; } void CCamera::BuildCameraRay(int px, int py, CVector3D& origin, CVector3D& dir) const { CVector3D cPts[4]; GetCameraPlanePoints(m_FarPlane, cPts); // transform to world space CVector3D wPts[4]; for (int i = 0; i < 4; i++) wPts[i] = m_Orientation.Transform(cPts[i]); // get world space position of mouse point float dx = (float)px / (float)g_Renderer.GetWidth(); float dz = 1 - (float)py / (float)g_Renderer.GetHeight(); CVector3D vdx = wPts[1] - wPts[0]; CVector3D vdz = wPts[3] - wPts[0]; CVector3D pt = wPts[0] + (vdx * dx) + (vdz * dz); // copy origin origin = m_Orientation.GetTranslation(); // build direction dir = pt - origin; dir.Normalize(); } void CCamera::GetScreenCoordinates(const CVector3D& world, float& x, float& y) const { CMatrix3D transform = m_ProjMat * m_Orientation.GetInverse(); CVector4D screenspace = transform.Transform(CVector4D(world.X, world.Y, world.Z, 1.0f)); x = screenspace.X / screenspace.W; y = screenspace.Y / screenspace.W; x = (x + 1) * 0.5f * g_Renderer.GetWidth(); y = (1 - y) * 0.5f * g_Renderer.GetHeight(); } CVector3D CCamera::GetWorldCoordinates(int px, int py, bool aboveWater) const { CHFTracer tracer(g_Game->GetWorld()->GetTerrain()); int x, z; CVector3D origin, dir, delta, terrainPoint, waterPoint; BuildCameraRay(px, py, origin, dir); bool gotTerrain = tracer.RayIntersect(origin, dir, x, z, terrainPoint); if (!aboveWater) { if (gotTerrain) return terrainPoint; // Off the edge of the world? // Work out where it /would/ hit, if the map were extended out to infinity with average height. return GetWorldCoordinates(px, py, 50.0f); } CPlane plane; plane.Set(CVector3D(0.f, 1.f, 0.f), // upwards normal CVector3D(0.f, g_Renderer.GetWaterManager()->m_WaterHeight, 0.f)); // passes through water plane bool gotWater = plane.FindRayIntersection(origin, dir, &waterPoint); // Clamp the water intersection to within the map's bounds, so that // we'll always return a valid position on the map ssize_t mapSize = g_Game->GetWorld()->GetTerrain()->GetVerticesPerSide(); if (gotWater) { waterPoint.X = clamp(waterPoint.X, 0.f, (float)((mapSize - 1)*TERRAIN_TILE_SIZE)); waterPoint.Z = clamp(waterPoint.Z, 0.f, (float)((mapSize - 1)*TERRAIN_TILE_SIZE)); } if (gotTerrain) { if (gotWater) { // Intersecting both heightmap and water plane; choose the closest of those if ((origin - terrainPoint).LengthSquared() < (origin - waterPoint).LengthSquared()) return terrainPoint; else return waterPoint; } else { // Intersecting heightmap but parallel to water plane return terrainPoint; } } else { if (gotWater) { // Only intersecting water plane return waterPoint; } else { // Not intersecting terrain or water; just return 0,0,0. return CVector3D(0.f, 0.f, 0.f); } } } CVector3D CCamera::GetWorldCoordinates(int px, int py, float h) const { CPlane plane; plane.Set(CVector3D(0.f, 1.f, 0.f), CVector3D(0.f, h, 0.f)); // upwards normal, passes through h CVector3D origin, dir, delta, currentTarget; BuildCameraRay(px, py, origin, dir); if (plane.FindRayIntersection(origin, dir, ¤tTarget)) return currentTarget; // No intersection with the infinite plane - nothing sensible can be returned, // so just choose an arbitrary point on the plane return CVector3D(0.f, h, 0.f); } CVector3D CCamera::GetFocus() const { // Basically the same as GetWorldCoordinates CHFTracer tracer(g_Game->GetWorld()->GetTerrain()); int x, z; CVector3D origin, dir, delta, terrainPoint, waterPoint; origin = m_Orientation.GetTranslation(); dir = m_Orientation.GetIn(); bool gotTerrain = tracer.RayIntersect(origin, dir, x, z, terrainPoint); CPlane plane; plane.Set(CVector3D(0.f, 1.f, 0.f), // upwards normal CVector3D(0.f, g_Renderer.GetWaterManager()->m_WaterHeight, 0.f)); // passes through water plane bool gotWater = plane.FindRayIntersection(origin, dir, &waterPoint); // Clamp the water intersection to within the map's bounds, so that // we'll always return a valid position on the map ssize_t mapSize = g_Game->GetWorld()->GetTerrain()->GetVerticesPerSide(); if (gotWater) { waterPoint.X = clamp(waterPoint.X, 0.f, (float)((mapSize - 1)*TERRAIN_TILE_SIZE)); waterPoint.Z = clamp(waterPoint.Z, 0.f, (float)((mapSize - 1)*TERRAIN_TILE_SIZE)); } if (gotTerrain) { if (gotWater) { // Intersecting both heightmap and water plane; choose the closest of those if ((origin - terrainPoint).LengthSquared() < (origin - waterPoint).LengthSquared()) return terrainPoint; else return waterPoint; } else { // Intersecting heightmap but parallel to water plane return terrainPoint; } } else { if (gotWater) { // Only intersecting water plane return waterPoint; } else { // Not intersecting terrain or water; just return 0,0,0. return CVector3D(0.f, 0.f, 0.f); } } } void CCamera::LookAt(const CVector3D& camera, const CVector3D& target, const CVector3D& up) { CVector3D delta = target - camera; LookAlong(camera, delta, up); } void CCamera::LookAlong(CVector3D camera, CVector3D orientation, CVector3D up) { orientation.Normalize(); up.Normalize(); CVector3D s = orientation.Cross(up); m_Orientation._11 = -s.X; m_Orientation._12 = up.X; m_Orientation._13 = orientation.X; m_Orientation._14 = camera.X; m_Orientation._21 = -s.Y; m_Orientation._22 = up.Y; m_Orientation._23 = orientation.Y; m_Orientation._24 = camera.Y; m_Orientation._31 = -s.Z; m_Orientation._32 = up.Z; m_Orientation._33 = orientation.Z; m_Orientation._34 = camera.Z; m_Orientation._41 = 0.0f; m_Orientation._42 = 0.0f; m_Orientation._43 = 0.0f; m_Orientation._44 = 1.0f; } /////////////////////////////////////////////////////////////////////////////////// // Render the camera's frustum void CCamera::Render(int intermediates) const { #if CONFIG2_GLES #warning TODO : implement camera frustum for GLES #else CVector3D nearPoints[4]; CVector3D farPoints[4]; GetCameraPlanePoints(m_NearPlane, nearPoints); GetCameraPlanePoints(m_FarPlane, farPoints); for (int i = 0; i < 4; i++) { nearPoints[i] = m_Orientation.Transform(nearPoints[i]); farPoints[i] = m_Orientation.Transform(farPoints[i]); } // near plane glBegin(GL_POLYGON); glVertex3fv(&nearPoints[0].X); glVertex3fv(&nearPoints[1].X); glVertex3fv(&nearPoints[2].X); glVertex3fv(&nearPoints[3].X); glEnd(); // far plane glBegin(GL_POLYGON); glVertex3fv(&farPoints[0].X); glVertex3fv(&farPoints[1].X); glVertex3fv(&farPoints[2].X); glVertex3fv(&farPoints[3].X); glEnd(); // connection lines glBegin(GL_QUAD_STRIP); glVertex3fv(&nearPoints[0].X); glVertex3fv(&farPoints[0].X); glVertex3fv(&nearPoints[1].X); glVertex3fv(&farPoints[1].X); glVertex3fv(&nearPoints[2].X); glVertex3fv(&farPoints[2].X); glVertex3fv(&nearPoints[3].X); glVertex3fv(&farPoints[3].X); glVertex3fv(&nearPoints[0].X); glVertex3fv(&farPoints[0].X); glEnd(); // intermediate planes CVector3D intermediatePoints[4]; for (int i = 0; i < intermediates; ++i) { float t = (i + 1.0) / (intermediates + 1.0); for (int j = 0; j < 4; ++j) intermediatePoints[j] = nearPoints[j] * t + farPoints[j] * (1.0 - t); glBegin(GL_POLYGON); glVertex3fv(&intermediatePoints[0].X); glVertex3fv(&intermediatePoints[1].X); glVertex3fv(&intermediatePoints[2].X); glVertex3fv(&intermediatePoints[3].X); glEnd(); } #endif } ================================================ FILE: fpsgame/graphics/Camera.h ================================================ /* Copyright (C) 2013 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /* * CCamera holds a view and a projection matrix. It also has a frustum * which can be used to cull objects for rendering. */ #ifndef INCLUDED_CAMERA #define INCLUDED_CAMERA #include "Frustum.h" #include "maths/BoundingBoxAligned.h" #include "maths/Matrix3D.h" // view port struct SViewPort { int m_X; int m_Y; int m_Width; int m_Height; }; class CCamera { public: CCamera(); ~CCamera(); // Methods for projection void SetProjection(float nearp, float farp, float fov); void SetProjection(const CMatrix3D& matrix) { m_ProjMat = matrix; } void SetProjectionTile(int tiles, int tile_x, int tile_y); CMatrix3D& GetProjection() { return m_ProjMat; } const CMatrix3D& GetProjection() const { return m_ProjMat; } CMatrix3D& GetOrientation() { return m_Orientation; } const CMatrix3D& GetOrientation() const { return m_Orientation; } CMatrix3D GetViewProjection() const { return m_ProjMat * m_Orientation.GetInverse(); } // Updates the frustum planes. Should be called // everytime the view or projection matrices are // altered. void UpdateFrustum(const CBoundingBoxAligned& scissor = CBoundingBoxAligned(CVector3D(-1.0f, -1.0f, -1.0f), CVector3D(1.0f, 1.0f, 1.0f))); void ClipFrustum(const CPlane& clipPlane); const CFrustum& GetFrustum() const { return m_ViewFrustum; } void SetViewPort(const SViewPort& viewport); const SViewPort& GetViewPort() const { return m_ViewPort; } // getters float GetNearPlane() const { return m_NearPlane; } float GetFarPlane() const { return m_FarPlane; } float GetFOV() const { return m_FOV; } // return four points in camera space at given distance from camera void GetCameraPlanePoints(float dist, CVector3D pts[4]) const; // Build a ray passing through the screen coordinate (px, py) and the camera ///////////////////////////////////////////////////////////////////////////////////////// // BuildCameraRay: calculate origin and ray direction of a ray through // the pixel (px,py) on the screen void BuildCameraRay(int px, int py, CVector3D& origin, CVector3D& dir) const; // General helpers that seem to fit here // Get the screen-space coordinates corresponding to a given world-space position void GetScreenCoordinates(const CVector3D& world, float& x, float& y) const; // Get the point on the terrain corresponding to pixel (px,py) (or the mouse coordinates) // The aboveWater parameter determines whether we want to stop at the water plane or also get underwater points CVector3D GetWorldCoordinates(int px, int py, bool aboveWater = false) const; // Get the point on the plane at height h corresponding to pixel (px,py) CVector3D GetWorldCoordinates(int px, int py, float h) const; // Get the point on the terrain (or water plane) the camera is pointing towards CVector3D GetFocus() const; // Build an orientation matrix from camera position, camera focus point, and up-vector void LookAt(const CVector3D& camera, const CVector3D& orientation, const CVector3D& up); // Build an orientation matrix from camera position, camera orientation, and up-vector void LookAlong(CVector3D camera, CVector3D focus, CVector3D up); /** * Render: Renders the camera's frustum in world space. * The caller should set the color using glColorXy before calling Render. * * @param intermediates determines how many intermediate distance planes should * be hinted at between the near and far planes */ void Render(int intermediates = 0) const; public: // This is the orientation matrix. The inverse of this // is the view matrix CMatrix3D m_Orientation; // Should not be tweaked externally if possible CMatrix3D m_ProjMat; private: float m_NearPlane; float m_FarPlane; float m_FOV; SViewPort m_ViewPort; CFrustum m_ViewFrustum; }; #endif ================================================ FILE: fpsgame/graphics/CinemaManager.cpp ================================================ /* Copyright (C) 2016 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include #include #include "graphics/Camera.h" #include "graphics/CinemaManager.h" #include "graphics/GameView.h" #include "gui/CGUI.h" #include "gui/GUIManager.h" #include "gui/IGUIObject.h" #include "lib/ogl.h" #include "maths/MathUtil.h" #include "maths/Quaternion.h" #include "maths/Vector3D.h" #include "maths/Vector4D.h" #include "ps/CLogger.h" #include "ps/CStr.h" #include "ps/Game.h" #include "ps/GameSetup/Config.h" #include "ps/Hotkey.h" #include "simulation2/components/ICmpOverlayRenderer.h" #include "simulation2/components/ICmpRangeManager.h" #include "simulation2/components/ICmpSelectable.h" #include "simulation2/components/ICmpTerritoryManager.h" #include "simulation2/MessageTypes.h" #include "simulation2/system/ComponentManager.h" #include "simulation2/Simulation2.h" #include "renderer/Renderer.h" CCinemaManager::CCinemaManager() : m_DrawPaths(false) { } void CCinemaManager::AddPath(const CStrW& name, const CCinemaPath& path) { if (m_CinematicSimulationData.m_Paths.find(name) != m_CinematicSimulationData.m_Paths.end()) { LOGWARNING("Path with name '%s' already exists", name.ToUTF8()); return; } m_CinematicSimulationData.m_Paths[name] = path; } void CCinemaManager::AddPathToQueue(const CStrW& name) { if (!HasPath(name)) { LOGWARNING("Path with name '%s' doesn't exist", name.ToUTF8()); return; } m_CinematicSimulationData.m_PathQueue.push_back(m_CinematicSimulationData.m_Paths[name]); } void CCinemaManager::ClearQueue() { m_CinematicSimulationData.m_PathQueue.clear(); } void CCinemaManager::SetAllPaths(const std::map& paths) { m_CinematicSimulationData.m_Paths = paths; } bool CCinemaManager::HasPath(const CStrW& name) const { return m_CinematicSimulationData.m_Paths.find(name) != m_CinematicSimulationData.m_Paths.end(); } void CCinemaManager::SetEnabled(bool enabled) { // TODO: maybe assert? if (m_CinematicSimulationData.m_PathQueue.empty() && enabled) { enabled = false; m_CinematicSimulationData.m_Paused = true; } if (m_CinematicSimulationData.m_Enabled == enabled) return; // TODO: Enabling/Disabling does not work if the session GUI page is not the top page. // This can happen in various situations, for example when the player wins/looses the game // while the cinematic is running (a message box is the top page in this case). // It might be better to disable the whole GUI during the cinematic instead of a specific // GUI object. // sn - session gui object IGUIObject *sn = g_GUI->FindObjectByName("sn"); CmpPtr cmpRangeManager(g_Game->GetSimulation2()->GetSimContext().GetSystemEntity()); CmpPtr cmpTerritoryManager(g_Game->GetSimulation2()->GetSimContext().GetSystemEntity()); // GUI visibility if (sn) { if (enabled) sn->SetSetting("hidden", L"true"); else sn->SetSetting("hidden", L"false"); } // Overlay visibility g_Renderer.SetOptionBool(CRenderer::Option::OPT_SILHOUETTES, !enabled); if (cmpRangeManager) { if (enabled) m_CinematicSimulationData.m_MapRevealed = cmpRangeManager->GetLosRevealAll(-1); // TODO: improve m_MapRevealed state and without fade in cmpRangeManager->SetLosRevealAll(-1, enabled); } if (cmpTerritoryManager) cmpTerritoryManager->SetVisibility(!enabled); ICmpSelectable::SetOverrideVisibility(!enabled); ICmpOverlayRenderer::SetOverrideVisibility(!enabled); m_CinematicSimulationData.m_Enabled = enabled; } void CCinemaManager::Play() { m_CinematicSimulationData.m_Paused = false; } void CCinemaManager::Stop() { m_CinematicSimulationData.m_PathQueue.clear(); } void CCinemaManager::Update(const float deltaRealTime) { if (g_Game->m_Paused != m_CinematicSimulationData.m_Paused) { m_CinematicSimulationData.m_Paused = g_Game->m_Paused; // sn - session gui object IGUIObject *sn = g_GUI->FindObjectByName("sn"); // GUI visibility if (sn) { if (m_CinematicSimulationData.m_Paused) sn->SetSetting("hidden", L"false"); else sn->SetSetting("hidden", L"true"); } } if (m_CinematicSimulationData.m_PathQueue.empty() || !m_CinematicSimulationData.m_Enabled || m_CinematicSimulationData.m_Paused) return; if (HotkeyIsPressed("leave")) { // TODO: implement skip } m_CinematicSimulationData.m_PathQueue.front().Play(deltaRealTime); } void CCinemaManager::Render() { if (GetEnabled()) { DrawBars(); return; } if (!m_DrawPaths) return; // draw all paths for (const std::pair& p : m_CinematicSimulationData.m_Paths) p.second.Draw(); } void CCinemaManager::DrawBars() const { int height = (float)g_xres / 2.39f; int shift = (g_yres - height) / 2; if (shift <= 0) return; #if CONFIG2_GLES #warning TODO : implement bars for GLES #else // Set up transform for GL bars glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); CMatrix3D transform; transform.SetOrtho(0.f, (float)g_xres, 0.f, (float)g_yres, -1.f, 1000.f); glLoadMatrixf(&transform._11); glColor4f(0.0f, 0.0f, 0.0f, 1.0f); glEnable(GL_BLEND); glDisable(GL_DEPTH_TEST); glBegin(GL_QUADS); glVertex2i(0, 0); glVertex2i(g_xres, 0); glVertex2i(g_xres, shift); glVertex2i(0, shift); glEnd(); glBegin(GL_QUADS); glVertex2i(0, g_yres - shift); glVertex2i(g_xres, g_yres - shift); glVertex2i(g_xres, g_yres); glVertex2i(0, g_yres); glEnd(); glDisable(GL_BLEND); glEnable(GL_DEPTH_TEST); // Restore transform glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); #endif } InReaction cinema_manager_handler(const SDL_Event_* ev) { // put any events that must be processed even if inactive here if (!g_Game || !g_Game->IsGameStarted()) return IN_PASS; CCinemaManager* pCinemaManager = g_Game->GetView()->GetCinema(); return pCinemaManager->HandleEvent(ev); } InReaction CCinemaManager::HandleEvent(const SDL_Event_* ev) { switch (ev->ev.type) { case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: if (GetEnabled() && !m_CinematicSimulationData.m_Paused) return IN_HANDLED; default: return IN_PASS; } } bool CCinemaManager::GetEnabled() const { return m_CinematicSimulationData.m_Enabled; } bool CCinemaManager::IsPlaying() const { return !m_CinematicSimulationData.m_Paused; } const std::map& CCinemaManager::GetAllPaths() { return m_CinematicSimulationData.m_Paths; } CinematicSimulationData* CCinemaManager::GetCinematicSimulationData() { return &m_CinematicSimulationData; } bool CCinemaManager::GetPathsDrawing() const { return m_DrawPaths; } void CCinemaManager::SetPathsDrawing(const bool drawPath) { m_DrawPaths = drawPath; } ================================================ FILE: fpsgame/graphics/CinemaManager.h ================================================ /* Copyright (C) 2015 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_CINEMAMANAGER #define INCLUDED_CINEMAMANAGER #include #include #include "lib/input.h" // InReaction - can't forward-declare enum #include "graphics/CinemaPath.h" #include "ps/CStr.h" /* desc: contains various functions used for cinematic camera paths See also: CinemaHandler.cpp, CinemaPath.cpp */ // Cinematic structure for data accessable from the simulation struct CinematicSimulationData { bool m_Enabled; bool m_Paused; std::map m_Paths; std::list m_PathQueue; // States before playing bool m_MapRevealed; fixed m_ElapsedTime, m_TotalTime, m_CurrentPathElapsedTime; CinematicSimulationData() : m_Enabled(false), m_Paused(true), m_MapRevealed(false), m_ElapsedTime(fixed::Zero()), m_TotalTime(fixed::Zero()), m_CurrentPathElapsedTime(fixed::Zero()) {} }; /** * Class for in game playing of cinematics. Should only be instantiated in CGameView. */ class CCinemaManager { public: CCinemaManager(); ~CCinemaManager() {} /** * Adds the path to the path list * @param name path name * @param CCinemaPath path data */ void AddPath(const CStrW& name, const CCinemaPath& path); /** * Adds the path to the playlist * @param name path name */ void AddPathToQueue(const CStrW& name); /** * Clears the playlist */ void ClearQueue(); /** * Checks the path name in the path list * @param name path name * @return true if path with that name exists, else false */ bool HasPath(const CStrW& name) const; const std::map& GetAllPaths(); void SetAllPaths(const std::map& tracks); /** * Starts play paths */ void Play(); void Stop(); bool IsPlaying() const; /** * Renders black bars and paths (if enabled) */ void Render(); void DrawBars() const; /** * Get current enable state of the cinema manager */ bool GetEnabled() const; /** * Sets enable state of the cinema manager (shows/hide gui, show/hide rings, etc) * @enable new state */ void SetEnabled(bool enabled); /** * Updates CCinemManager and current path * @param deltaRealTime Elapsed real time since the last frame. */ void Update(const float deltaRealTime); InReaction HandleEvent(const SDL_Event_* ev); CinematicSimulationData* GetCinematicSimulationData(); bool GetPathsDrawing() const; void SetPathsDrawing(const bool drawPath); private: bool m_DrawPaths; // Cinematic data is accessed from the simulation CinematicSimulationData m_CinematicSimulationData; }; extern InReaction cinema_manager_handler(const SDL_Event_* ev); #endif ================================================ FILE: fpsgame/graphics/CinimaPath.cpp ================================================ /* Copyright (C) 2016 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "CinemaPath.h" #include #include #include "Camera.h" #include "CinemaManager.h" #include "GameView.h" #include "gui/CGUI.h" #include "gui/GUIManager.h" #include "gui/IGUIObject.h" #include "lib/ogl.h" #include "maths/MathUtil.h" #include "maths/Quaternion.h" #include "maths/Vector3D.h" #include "maths/Vector4D.h" #include "ps/CLogger.h" #include "ps/CStr.h" #include "ps/Game.h" #include "renderer/Renderer.h" CCinemaPath::CCinemaPath(const CCinemaData& data, const TNSpline& spline, const TNSpline& targetSpline) : CCinemaData(data), TNSpline(spline), m_TargetSpline(targetSpline), m_TimeElapsed(0.f) { m_TimeElapsed = 0; // Calculate curves by nodes BuildSpline(); m_TargetSpline.BuildSpline(); if (m_Orientation == L"target") { m_LookAtTarget = true; ENSURE(!m_TargetSpline.GetAllNodes().empty()); } // Set distortion mode and style if (data.m_Mode == L"ease_in") DistModePtr = &CCinemaPath::EaseIn; else if (data.m_Mode == L"ease_out") DistModePtr = &CCinemaPath::EaseOut; else if (data.m_Mode == L"ease_inout") DistModePtr = &CCinemaPath::EaseInOut; else if (data.m_Mode == L"ease_outin") DistModePtr = &CCinemaPath::EaseOutIn; else { LOGWARNING("Cinematic mode not found for '%s'", data.m_Mode.ToUTF8().c_str()); DistModePtr = &CCinemaPath::EaseInOut; } if (data.m_Style == L"default") DistStylePtr = &CCinemaPath::EaseDefault; else if (data.m_Style == L"growth") DistStylePtr = &CCinemaPath::EaseGrowth; else if (data.m_Style == L"expo") DistStylePtr = &CCinemaPath::EaseExpo; else if (data.m_Style == L"circle") DistStylePtr = &CCinemaPath::EaseCircle; else if (data.m_Style == L"sine") DistStylePtr = &CCinemaPath::EaseSine; else { LOGWARNING("Cinematic style not found for '%s'", data.m_Style.ToUTF8().c_str()); DistStylePtr = &CCinemaPath::EaseDefault; } } void CCinemaPath::Draw() const { DrawSpline(*this, CVector4D(0.2f, 0.2f, 1.f, 0.5f), 100, true); DrawNodes(*this, CVector4D(0.5f, 1.0f, 0.f, 0.5f)); if (!m_LookAtTarget) return; DrawSpline(m_TargetSpline, CVector4D(1.0f, 0.2f, 0.2f, 0.5f), 100, true); DrawNodes(m_TargetSpline, CVector4D(1.0f, 0.5f, 0.f, 0.5f)); } void CCinemaPath::DrawSpline(const RNSpline& spline, const CVector4D& RGBA, int smoothness, bool lines) const { if (spline.NodeCount < 2 || DistModePtr == NULL) return; if (spline.NodeCount == 2 && lines) smoothness = 2; float start = spline.MaxDistance.ToFloat() / smoothness; float time = 0; #if CONFIG2_GLES #warning TODO : do something about CCinemaPath on GLES #else glColor4f(RGBA.X, RGBA.Y, RGBA.Z, RGBA.W); if (lines) { glLineWidth(1.8f); glEnable(GL_LINE_SMOOTH); glBegin(GL_LINE_STRIP); for (int i = 0; i <= smoothness; ++i) { // Find distorted time time = start*i / spline.MaxDistance.ToFloat(); CVector3D tmp = spline.GetPosition(time); glVertex3f(tmp.X, tmp.Y, tmp.Z); } glEnd(); glDisable(GL_LINE_SMOOTH); glLineWidth(1.0f); } else { smoothness /= 2; start = spline.MaxDistance.ToFloat() / smoothness; glEnable(GL_POINT_SMOOTH); glPointSize(3.0f); glBegin(GL_POINTS); for (int i = 0; i <= smoothness; ++i) { // Find distorted time time = (this->*DistModePtr)(start*i / spline.MaxDistance.ToFloat()); CVector3D tmp = spline.GetPosition(time); glVertex3f(tmp.X, tmp.Y, tmp.Z); } glColor3f(1.0f, 1.0f, 0.0f); // yellow for (size_t i = 0; i < spline.GetAllNodes().size(); ++i) glVertex3f( spline.GetAllNodes()[i].Position.X.ToFloat(), spline.GetAllNodes()[i].Position.Y.ToFloat(), spline.GetAllNodes()[i].Position.Z.ToFloat() ); glEnd(); glPointSize(1.0f); glDisable(GL_POINT_SMOOTH); } #endif } void CCinemaPath::DrawNodes(const RNSpline& spline, const CVector4D& RGBA) const { #if CONFIG2_GLES #warning TODO : do something about CCinemaPath on GLES #else glEnable(GL_POINT_SMOOTH); glPointSize(5.0f); glColor4f(RGBA.X, RGBA.Y, RGBA.Z, RGBA.W); glBegin(GL_POINTS); const std::vector& nodes = spline.GetAllNodes(); for (size_t i = 0; i < nodes.size(); ++i) glVertex3f(nodes[i].Position.X.ToFloat(), nodes[i].Position.Y.ToFloat(), nodes[i].Position.Z.ToFloat()); glEnd(); glPointSize(1.0f); glDisable(GL_POINT_SMOOTH); #endif } CVector3D CCinemaPath::GetNodePosition(const int index) const { return Node[index].Position; } fixed CCinemaPath::GetNodeDuration(const int index) const { return Node[index].Distance; } fixed CCinemaPath::GetDuration() const { return MaxDistance; } float CCinemaPath::GetNodeFraction() const { return (m_TimeElapsed - m_PreviousNodeTime) / Node[m_CurrentNode].Distance.ToFloat(); } float CCinemaPath::GetElapsedTime() const { return m_TimeElapsed; } CStrW CCinemaPath::GetName() const { return m_Name; } void CCinemaPath::SetTimescale(fixed scale) { m_Timescale = scale; } void CCinemaPath::MoveToPointAt(float t, float nodet, const CVector3D& startRotation) { CCamera *camera = g_Game->GetView()->GetCamera(); t = (this->*DistModePtr)(t); CVector3D pos = GetPosition(t); if (m_LookAtTarget) { if (m_TimeElapsed <= m_TargetSpline.MaxDistance.ToFloat()) camera->LookAt(pos, m_TargetSpline.GetPosition(m_TimeElapsed / m_TargetSpline.MaxDistance.ToFloat()), CVector3D(0, 1, 0)); else camera->LookAt(pos, m_TargetSpline.GetAllNodes().back().Position, CVector3D(0, 1, 0)); } else { CVector3D nodeRotation = Node[m_CurrentNode + 1].Rotation; CQuaternion start, end; start.FromEulerAngles(DEGTORAD(startRotation.X), DEGTORAD(startRotation.Y), DEGTORAD(startRotation.Z)); end.FromEulerAngles(DEGTORAD(nodeRotation.X), DEGTORAD(nodeRotation.Y), DEGTORAD(nodeRotation.Z)); start.Slerp(start, end, nodet); camera->m_Orientation.SetIdentity(); camera->m_Orientation.Rotate(start); camera->m_Orientation.Translate(pos); } camera->UpdateFrustum(); } // Distortion mode functions float CCinemaPath::EaseIn(float t) const { return (this->*DistStylePtr)(t); } float CCinemaPath::EaseOut(float t) const { return 1.0f - EaseIn(1.0f - t); } float CCinemaPath::EaseInOut(float t) const { if (t < m_Switch) return EaseIn(1.0f / m_Switch * t) * m_Switch; return EaseOut(1.0f / m_Switch * (t - m_Switch)) * m_Switch + m_Switch; } float CCinemaPath::EaseOutIn(float t) const { if (t < m_Switch) return EaseOut(1.0f / m_Switch * t) * m_Switch; return EaseIn(1.0f / m_Switch * (t - m_Switch)) * m_Switch + m_Switch; } // Distortion style functions float CCinemaPath::EaseDefault(float t) const { return t; } float CCinemaPath::EaseGrowth(float t) const { return pow(t, m_Growth); } float CCinemaPath::EaseExpo(float t) const { if (t == 0) return t; return powf(m_Growth, 10 * (t - 1.0f)); } float CCinemaPath::EaseCircle(float t) const { t = -(sqrt(1.0f - t*t) - 1.0f); if (m_GrowthCount > 1.0f) { --m_GrowthCount; return (this->*DistStylePtr)(t); } return t; } float CCinemaPath::EaseSine(float t) const { t = 1.0f - cos(t * (float)M_PI / 2); if (m_GrowthCount > 1.0f) { --m_GrowthCount; return (this->*DistStylePtr)(t); } return t; } const CCinemaData* CCinemaPath::GetData() const { return CCinemaData::GetData(); } bool CCinemaPath::Validate() { if (m_TimeElapsed > GetDuration().ToFloat() || m_TimeElapsed < 0.0f) return false; // Find current node and past "node time" float previousTime = 0.0f, cumulation = 0.0f; // Ignore the last node, since it is a blank (node time values are shifted down one from interface) for (size_t i = 0; i < Node.size() - 1; ++i) { cumulation += Node[i].Distance.ToFloat(); if (m_TimeElapsed <= cumulation) { m_PreviousNodeTime = previousTime; m_PreviousRotation = Node[i].Rotation; m_CurrentNode = i; // We're moving toward this next node, so use its rotation return true; } previousTime += Node[i].Distance.ToFloat(); } debug_warn("validation of cinema path is wrong\n"); return false; } bool CCinemaPath::Play(const float deltaRealTime) { m_TimeElapsed += m_Timescale.ToFloat() * deltaRealTime; if (!Validate()) return false; MoveToPointAt(m_TimeElapsed / GetDuration().ToFloat(), GetNodeFraction(), m_PreviousRotation); return true; } bool CCinemaPath::Empty() const { return Node.empty(); } void CCinemaPath::Reset() { m_TimeElapsed = 0.0f; } fixed CCinemaPath::GetTimescale() const { return m_Timescale; } const TNSpline& CCinemaPath::GetTargetSpline() const { return m_TargetSpline; } ================================================ FILE: fpsgame/graphics/CinimaPath.h ================================================ #pragma once /* Copyright (C) 2016 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_CINEMAPATH #define INCLUDED_CINEMAPATH #include "maths/NUSpline.h" #include "ps/CStr.h" class CVector3D; class CVector4D; class CCamera; // For loading data class CCinemaData { public: CCinemaData() : m_LookAtTarget(false), m_GrowthCount(0), m_Growth(1), m_Switch(1), m_Timescale(fixed::FromInt(1)) {} virtual ~CCinemaData() {} const CCinemaData* GetData() const { return this; } CStrW m_Name; CStrW m_Orientation; CStrW m_Mode; CStrW m_Style; bool m_LookAtTarget; fixed m_Timescale; // a negative timescale results in backwards play // Distortion variables mutable float m_GrowthCount; float m_Growth; float m_Switch; }; // Once the data is part of the path, it shouldn't be changeable, so use private inheritance. // This class encompasses the spline and the information which determines how the path will operate // and also provides the functionality for doing so class CCinemaPath : private CCinemaData, public TNSpline { public: CCinemaPath() : m_TimeElapsed(0), m_PreviousNodeTime(0) {} CCinemaPath(const CCinemaData& data, const TNSpline& spline, const TNSpline& targetSpline); // Sets camera position to calculated point on spline void MoveToPointAt(float t, float nodet, const CVector3D&); // Distortion mode functions-change how ratio is passed to distortion style functions float EaseIn(float t) const; float EaseOut(float t) const; float EaseInOut(float t) const; float EaseOutIn(float t) const; // Distortion style functions float EaseDefault(float t) const; float EaseGrowth(float t) const; float EaseExpo(float t) const; float EaseCircle(float t) const; float EaseSine(float t) const; float (CCinemaPath::*DistStylePtr)(float ratio) const; float (CCinemaPath::*DistModePtr)(float ratio) const; const CCinemaData* GetData() const; public: void Draw() const; void DrawSpline(const RNSpline& spline, const CVector4D& RGBA, int smoothness, bool lines) const; void DrawNodes(const RNSpline& spline, const CVector4D& RGBA) const; CVector3D GetNodePosition(const int index) const; fixed GetNodeDuration(const int index) const; fixed GetDuration() const; float GetNodeFraction() const; float GetElapsedTime() const; CStrW GetName() const; void SetTimescale(fixed scale); float m_TimeElapsed; float m_PreviousNodeTime; // How much time has passed before the current node size_t m_CurrentNode; CVector3D m_PreviousRotation; public: /** * Returns false if finished. * @param deltaRealTime Elapsed real time since the last frame. */ bool Play(const float deltaRealTime); /** * Validate the path * @return true if the path is valid */ bool Validate(); /** * Returns true if path doesn't contain nodes */ bool Empty() const; /** * Resets the path state */ void Reset(); fixed GetTimescale() const; const TNSpline& GetTargetSpline() const; private: TNSpline m_TargetSpline; }; #endif // INCLUDED_CINEMAPATH ================================================ FILE: fpsgame/graphics/ColladaManager.cpp ================================================ /* Copyright (C) 2015 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "ColladaManager.h" #include #include "graphics/ModelDef.h" #include "lib/fnv_hash.h" #include "maths/MD5.h" #include "ps/CacheLoader.h" #include "ps/CLogger.h" #include "ps/CStr.h" #include "ps/DllLoader.h" #include "ps/Filesystem.h" namespace Collada { #include "collada/DLL.h" } namespace { void ColladaLog(void* cb_data, int severity, const char* text) { VfsPath* path = static_cast(cb_data); if (severity == LOG_INFO) LOGMESSAGE("%s: %s", path->string8(), text); else if (severity == LOG_WARNING) LOGWARNING("%s: %s", path->string8(), text); else LOGERROR("%s: %s", path->string8(), text); } void ColladaOutput(void* cb_data, const char* data, unsigned int length) { WriteBuffer* writeBuffer = static_cast(cb_data); writeBuffer->Append(data, (size_t)length); } } class CColladaManagerImpl { DllLoader dll; void(*set_logger)(Collada::LogFn logger, void* cb_data); int(*set_skeleton_definitions)(const char* xml, int length); int(*convert_dae_to_pmd)(const char* dae, Collada::OutputFn pmd_writer, void* cb_data); int(*convert_dae_to_psa)(const char* dae, Collada::OutputFn psa_writer, void* cb_data); public: CColladaManagerImpl(const PIVFS& vfs) : dll("Collada"), m_VFS(vfs), m_skeletonHashInvalidated(true) { // Support hotloading RegisterFileReloadFunc(ReloadChangedFileCB, this); } ~CColladaManagerImpl() { if (dll.IsLoaded()) set_logger(NULL, NULL); // unregister the log handler UnregisterFileReloadFunc(ReloadChangedFileCB, this); } Status ReloadChangedFile(const VfsPath& path) { // Ignore files that aren't in the right path if (!boost::algorithm::starts_with(path.string(), L"art/skeletons/")) return INFO::OK; if (path.Extension() != L".xml") return INFO::OK; m_skeletonHashInvalidated = true; // If the file doesn't exist (e.g. it was deleted), don't bother reloading // or 'unloading' since that isn't possible if (!VfsFileExists(path)) return INFO::OK; if (!dll.IsLoaded() && !TryLoadDLL()) return ERR::FAIL; LOGMESSAGE("Hotloading skeleton definitions from '%s'", path.string8()); // Set the filename for the logger to report set_logger(ColladaLog, const_cast(static_cast(&path))); CVFSFile skeletonFile; if (skeletonFile.Load(m_VFS, path) != PSRETURN_OK) { LOGERROR("Failed to read skeleton defintions from '%s'", path.string8()); return ERR::FAIL; } int ok = set_skeleton_definitions((const char*)skeletonFile.GetBuffer(), (int)skeletonFile.GetBufferSize()); if (ok < 0) { LOGERROR("Failed to load skeleton definitions from '%s'", path.string8()); return ERR::FAIL; } return INFO::OK; } static Status ReloadChangedFileCB(void* param, const VfsPath& path) { return static_cast(param)->ReloadChangedFile(path); } bool Convert(const VfsPath& daeFilename, const VfsPath& pmdFilename, CColladaManager::FileType type) { // To avoid always loading the DLL when it's usually not going to be // used (and to do the same on Linux where delay-loading won't help), // and to avoid compile-time dependencies (because it's a minor pain // to get all the right libraries to build the COLLADA DLL), we load // it dynamically when it is required, instead of using the exported // functions and binding at link-time. if (!dll.IsLoaded()) { if (!TryLoadDLL()) return false; if (!LoadSkeletonDefinitions()) { dll.Unload(); // Error should have been logged already return false; } } // Set the filename for the logger to report set_logger(ColladaLog, const_cast(static_cast(&daeFilename))); // We need to null-terminate the buffer, so do it (possibly inefficiently) // by converting to a CStr CStr daeData; { CVFSFile daeFile; if (daeFile.Load(m_VFS, daeFilename) != PSRETURN_OK) return false; daeData = daeFile.GetAsString(); } // Do the conversion into a memory buffer // We need to check the result, as archive builder needs to know if the source dae // was sucessfully converted to .pmd/psa int result = -1; WriteBuffer writeBuffer; switch (type) { case CColladaManager::PMD: result = convert_dae_to_pmd(daeData.c_str(), ColladaOutput, &writeBuffer); break; case CColladaManager::PSA: result = convert_dae_to_psa(daeData.c_str(), ColladaOutput, &writeBuffer); break; } // don't create zero-length files (as happens in test_invalid_dae when // we deliberately pass invalid XML data) because the VFS caching // logic warns when asked to load such. if (writeBuffer.Size()) { Status ret = m_VFS->CreateFile(pmdFilename, writeBuffer.Data(), writeBuffer.Size()); ENSURE(ret == INFO::OK); } return (result == 0); } bool TryLoadDLL() { if (!dll.LoadDLL()) { LOGERROR("Failed to load COLLADA conversion DLL"); return false; } try { dll.LoadSymbol("set_logger", set_logger); dll.LoadSymbol("set_skeleton_definitions", set_skeleton_definitions); dll.LoadSymbol("convert_dae_to_pmd", convert_dae_to_pmd); dll.LoadSymbol("convert_dae_to_psa", convert_dae_to_psa); } catch (PSERROR_DllLoader&) { LOGERROR("Failed to load symbols from COLLADA conversion DLL"); dll.Unload(); return false; } return true; } bool LoadSkeletonDefinitions() { VfsPaths pathnames; if (vfs::GetPathnames(m_VFS, L"art/skeletons/", L"*.xml", pathnames) < 0) { LOGERROR("No skeleton definition files present"); return false; } bool loaded = false; for (const VfsPath& path : pathnames) { LOGMESSAGE("Loading skeleton definitions from '%s'", path.string8()); // Set the filename for the logger to report set_logger(ColladaLog, const_cast(static_cast(&path))); CVFSFile skeletonFile; if (skeletonFile.Load(m_VFS, path) != PSRETURN_OK) { LOGERROR("Failed to read skeleton defintions from '%s'", path.string8()); continue; } int ok = set_skeleton_definitions((const char*)skeletonFile.GetBuffer(), (int)skeletonFile.GetBufferSize()); if (ok < 0) { LOGERROR("Failed to load skeleton definitions from '%s'", path.string8()); continue; } loaded = true; } if (!loaded) LOGERROR("Failed to load any skeleton definitions"); return loaded; } /** * Creates MD5 hash key from skeletons.xml info and COLLADA converter version, * used to invalidate cached .pmd/psas * * @param[out] hash resulting MD5 hash * @param[out] version version passed to CCacheLoader, used if code change should force * cache invalidation */ void PrepareCacheKey(MD5& hash, u32& version) { // Add converter version to the hash version = COLLADA_CONVERTER_VERSION; // Cache the skeleton files hash data if (m_skeletonHashInvalidated) { VfsPaths paths; if (vfs::GetPathnames(m_VFS, L"art/skeletons/", L"*.xml", paths) != INFO::OK) { LOGWARNING("Failed to load skeleton definitions"); return; } // Sort the paths to not invalidate the cache if mods are mounted in different order // (No need to stable_sort as the VFS gurantees that we have no duplicates) std::sort(paths.begin(), paths.end()); // We need two u64s per file m_skeletonHashes.clear(); m_skeletonHashes.reserve(paths.size() * 2); CFileInfo fileInfo; for (const VfsPath& path : paths) { // This will cause an assertion failure if *it doesn't exist, // because fileinfo is not a NULL pointer, which is annoying but that // should never happen, unless there really is a problem if (m_VFS->GetFileInfo(path, &fileInfo) != INFO::OK) { LOGERROR("Failed to stat '%s' for DAE caching", path.string8()); } else { m_skeletonHashes.push_back((u64)fileInfo.MTime() & ~1); //skip lowest bit, since zip and FAT don't preserve it m_skeletonHashes.push_back((u64)fileInfo.Size()); } } // Check if we were able to load any skeleton files if (m_skeletonHashes.empty()) LOGERROR("Failed to stat any skeleton definitions for DAE caching"); // We can continue, something else will break if we try loading a skeletal model m_skeletonHashInvalidated = false; } for (const u64& h : m_skeletonHashes) hash.Update((const u8*)&h, sizeof(h)); } private: PIVFS m_VFS; bool m_skeletonHashInvalidated; std::vector m_skeletonHashes; }; CColladaManager::CColladaManager(const PIVFS& vfs) : m(new CColladaManagerImpl(vfs)), m_VFS(vfs) { } CColladaManager::~CColladaManager() { delete m; } VfsPath CColladaManager::GetLoadablePath(const VfsPath& pathnameNoExtension, FileType type) { std::wstring extn; switch (type) { case PMD: extn = L".pmd"; break; case PSA: extn = L".psa"; break; // no other alternatives } /* Algorithm: * Calculate hash of skeletons.xml and converter version. * Use CCacheLoader to check for archived or loose cached .pmd/psa. * If cached version exists: * Return pathname of cached .pmd/psa. * Else, if source .dae for this model exists: * Convert it to cached .pmd/psa. * If converter succeeded: * Return pathname of cached .pmd/psa. * Else, fail (return empty path). * Else, if uncached .pmd/psa exists: * Return pathname of uncached .pmd/psa. * Else, fail (return empty path). Since we use CCacheLoader which automatically hashes file size and mtime, and handles archived files and loose cache, when preparing the cache key we add converter version number (so updates of the converter cause regeneration of the .pmd/psa) and the global skeletons.xml file size and mtime, as modelers frequently change the contents of skeletons.xml and get perplexed if the in-game models haven't updated as expected (we don't know which models were affected by the skeletons.xml change, if any, so we just regenerate all of them) TODO (maybe): The .dae -> .pmd/psa conversion may fail (e.g. if the .dae is invalid or unsupported), but it may take a long time to start the conversion then realise it's not going to work. That will delay the loading of the game every time, which is annoying, so maybe it should cache the error message until the .dae is updated and fixed. (Alternatively, avoid having that many broken .daes in the game.) */ // Now we're looking for cached files CCacheLoader cacheLoader(m_VFS, extn); MD5 hash; u32 version; m->PrepareCacheKey(hash, version); VfsPath cachePath; VfsPath sourcePath = pathnameNoExtension.ChangeExtension(L".dae"); Status ret = cacheLoader.TryLoadingCached(sourcePath, hash, version, cachePath); if (ret == INFO::OK) // Found a valid cached version return cachePath; // No valid cached version, check if we have a source .dae if (ret != INFO::SKIPPED) { // No valid cached version was found, and no source .dae exists ENSURE(ret < 0); // Check if source (uncached) .pmd/psa exists sourcePath = pathnameNoExtension.ChangeExtension(extn); if (m_VFS->GetFileInfo(sourcePath, NULL) != INFO::OK) { // Broken reference, the caller will need to handle this return L""; } else { return sourcePath; } } // No valid cached version was found - but source .dae exists // We'll try converting it // We have a source .dae and invalid cached version, so regenerate cached version if (!m->Convert(sourcePath, cachePath, type)) { // The COLLADA converter failed for some reason, this will need to be handled // by the caller return L""; } return cachePath; } bool CColladaManager::GenerateCachedFile(const VfsPath& sourcePath, FileType type, VfsPath& archiveCachePath) { std::wstring extn; switch (type) { case PMD: extn = L".pmd"; break; case PSA: extn = L".psa"; break; // no other alternatives } CCacheLoader cacheLoader(m_VFS, extn); archiveCachePath = cacheLoader.ArchiveCachePath(sourcePath); return m->Convert(sourcePath, VfsPath("cache") / archiveCachePath, type); } ================================================ FILE: fpsgame/graphics/ColladaManager.h ================================================ /* Copyright (C) 2013 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_COLLADAMANAGER #define INCLUDED_COLLADAMANAGER #include "lib/file/vfs/vfs.h" class CStr8; class CColladaManagerImpl; class MD5; class CColladaManager { NONCOPYABLE(CColladaManager); public: enum FileType { PMD, PSA }; CColladaManager(const PIVFS& vfs); ~CColladaManager(); /** * Returns the VFS path to a PMD/PSA file for the given source file. * Performs a (cached) conversion from COLLADA if necessary. * * @param pathnameNoExtension path and name, minus extension, of file to load. * One of either "sourceName.pmd" or "sourceName.dae" should exist. * @param type FileType, .pmd or .psa * * @return full VFS path (including extension) of file to load; or empty * string if there was a problem and it could not be loaded. Doesn't knowingly * return an invalid path. */ VfsPath GetLoadablePath(const VfsPath& pathnameNoExtension, FileType type); /** * Converts DAE to archive cached .pmd/psa and outputs the resulting path * (used by archive builder) * * @param[in] sourcePath path of the .dae to load * @param[in] type FileType, .pmd or .psa * @param[out] archiveCachePath output path of the cached file * * @return true if COLLADA converter completed successfully; or false if it failed */ bool GenerateCachedFile(const VfsPath& sourcePath, FileType type, VfsPath& archiveCachePath); private: CColladaManagerImpl* m; PIVFS m_VFS; }; #endif // INCLUDED_COLLADAMANAGER ================================================ FILE: fpsgame/graphics/Color.cpp ================================================ #ifndef INCLUDED_COLOR #define INCLUDED_COLOR // #include "SColor.h" #include "maths/Vector3D.h" #include "maths/Vector4D.h" // simple defines for 3 and 4 component floating point colors - just map to // corresponding vector types typedef CVector3D RGBColor; typedef CVector4D RGBAColor; // exposed as function pointer because it is set at init-time to // one of several implementations depending on CPU caps. extern SColor4ub(*ConvertRGBColorTo4ub)(const RGBColor& src); // call once ia32_Init has run; detects CPU caps and activates the best // possible codepath. extern void ColorActivateFastImpl(); #endif ================================================ FILE: fpsgame/graphics/Color.h ================================================ #ifndef INCLUDED_COLOR #define INCLUDED_COLOR = =#include "SColor.h" #include "maths/Vector3D.h" #include "maths/Vector4D.h" // simple defines for 3 and 4 component floating point colors - just map to // corresponding vector types typedef CVector3D RGBColor; typedef CVector4D RGBAColor; // exposed as function pointer because it is set at init-time to // one of several implementations depending on CPU caps. extern SColor4ub(*ConvertRGBColorTo4ub)(const RGBColor& src); // call once ia32_Init has run; detects CPU caps and activates the best // possible codepath. extern void ColorActivateFastImpl(); #endif ================================================ FILE: fpsgame/graphics/Decal.cpp ================================================ /* Copyright (C) 2013 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "Decal.h" #include "graphics/Terrain.h" #include "maths/MathUtil.h" CModelAbstract* CModelDecal::Clone() const { CModelDecal* clone = new CModelDecal(m_Terrain, m_Decal); return clone; } void CModelDecal::CalcVertexExtents(ssize_t& i0, ssize_t& j0, ssize_t& i1, ssize_t& j1) { CVector3D corner0(m_Decal.m_OffsetX + m_Decal.m_SizeX/2, 0, m_Decal.m_OffsetZ + m_Decal.m_SizeZ/2); CVector3D corner1(m_Decal.m_OffsetX + m_Decal.m_SizeX/2, 0, m_Decal.m_OffsetZ - m_Decal.m_SizeZ/2); CVector3D corner2(m_Decal.m_OffsetX - m_Decal.m_SizeX/2, 0, m_Decal.m_OffsetZ - m_Decal.m_SizeZ/2); CVector3D corner3(m_Decal.m_OffsetX - m_Decal.m_SizeX/2, 0, m_Decal.m_OffsetZ + m_Decal.m_SizeZ/2); corner0 = GetTransform().Transform(corner0); corner1 = GetTransform().Transform(corner1); corner2 = GetTransform().Transform(corner2); corner3 = GetTransform().Transform(corner3); i0 = floor(std::min(std::min(corner0.X, corner1.X), std::min(corner2.X, corner3.X)) / TERRAIN_TILE_SIZE); j0 = floor(std::min(std::min(corner0.Z, corner1.Z), std::min(corner2.Z, corner3.Z)) / TERRAIN_TILE_SIZE); i1 = ceil(std::max(std::max(corner0.X, corner1.X), std::max(corner2.X, corner3.X)) / TERRAIN_TILE_SIZE); j1 = ceil(std::max(std::max(corner0.Z, corner1.Z), std::max(corner2.Z, corner3.Z)) / TERRAIN_TILE_SIZE); i0 = clamp(i0, (ssize_t)0, m_Terrain->GetVerticesPerSide()-1); j0 = clamp(j0, (ssize_t)0, m_Terrain->GetVerticesPerSide()-1); i1 = clamp(i1, (ssize_t)0, m_Terrain->GetVerticesPerSide()-1); j1 = clamp(j1, (ssize_t)0, m_Terrain->GetVerticesPerSide()-1); } void CModelDecal::CalcBounds() { ssize_t i0, j0, i1, j1; CalcVertexExtents(i0, j0, i1, j1); m_WorldBounds = m_Terrain->GetVertexesBound(i0, j0, i1, j1); } void CModelDecal::SetTerrainDirty(ssize_t i0, ssize_t j0, ssize_t i1, ssize_t j1) { // Check if there's no intersection between the dirty range and this decal ssize_t bi0, bj0, bi1, bj1; CalcVertexExtents(bi0, bj0, bi1, bj1); if (bi1 < i0 || bi0 > i1 || bj1 < j0 || bj0 > j1) return; SetDirty(RENDERDATA_UPDATE_VERTICES); } void CModelDecal::InvalidatePosition() { m_PositionValid = false; } void CModelDecal::ValidatePosition() { if (m_PositionValid) { ENSURE(!m_Parent || m_Parent->m_PositionValid); return; } if (m_Parent && !m_Parent->m_PositionValid) { // Make sure we don't base our calculations on // a parent animation state that is out of date. m_Parent->ValidatePosition(); // Parent will recursively call our validation. ENSURE(m_PositionValid); return; } m_PositionValid = true; } void CModelDecal::SetTransform(const CMatrix3D& transform) { // Since decals are assumed to be horizontal and projected downwards // onto the terrain, use just the Y-axis rotation and the translation CMatrix3D newTransform; newTransform.SetYRotation(transform.GetYRotation() + m_Decal.m_Angle); newTransform.Translate(transform.GetTranslation()); CRenderableObject::SetTransform(newTransform); InvalidatePosition(); } void CModelDecal::RemoveShadows() { m_Decal.m_Material.AddShaderDefine(str_DISABLE_RECEIVE_SHADOWS, str_1); m_Decal.m_Material.RecomputeCombinedShaderDefines(); } ================================================ FILE: fpsgame/graphics/Decal.h ================================================ /* Copyright (C) 2013 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_DECAL #define INCLUDED_DECAL #include "graphics/Material.h" #include "graphics/ModelAbstract.h" #include "graphics/Texture.h" class CTerrain; /** * Terrain decal definition. * Decals are rectangular textures that are projected vertically downwards * onto the terrain. */ struct SDecal { SDecal(const CMaterial& material, float sizeX, float sizeZ, float angle, float offsetX, float offsetZ, bool floating) : m_Material(material), m_SizeX(sizeX), m_SizeZ(sizeZ), m_Angle(angle), m_OffsetX(offsetX), m_OffsetZ(offsetZ), m_Floating(floating) { } CMaterial m_Material; float m_SizeX; float m_SizeZ; float m_Angle; float m_OffsetX; float m_OffsetZ; bool m_Floating; }; class CModelDecal : public CModelAbstract { public: CModelDecal(CTerrain* terrain, const SDecal& decal) : m_Terrain(terrain), m_Decal(decal) { ENSURE(terrain != NULL); } /// Dynamic cast virtual CModelDecal* ToCModelDecal() { return this; } virtual CModelAbstract* Clone() const; virtual void SetDirtyRec(int dirtyflags) { SetDirty(dirtyflags); } virtual void SetTerrainDirty(ssize_t i0, ssize_t j0, ssize_t i1, ssize_t j1); virtual void CalcBounds(); virtual void ValidatePosition(); virtual void InvalidatePosition(); virtual void SetTransform(const CMatrix3D& transform); // remove shadow receiving void RemoveShadows(); /** * Compute the terrain vertex indexes that bound the decal's * projection onto the terrain. * The returned indexes are clamped to the terrain size. */ void CalcVertexExtents(ssize_t& i0, ssize_t& j0, ssize_t& i1, ssize_t& j1); CTerrain* m_Terrain; SDecal m_Decal; }; #endif // INCLUDED_DECAL ================================================ FILE: fpsgame/graphics/Entity.h ================================================ /* Copyright (C) 2015 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_RMS_ENTITY #define INCLUDED_RMS_ENTITY #include "maths/FixedVector3D.h" // Struct for parsing random map data struct Entity { std::wstring templateName; u16 entityID; u16 playerID; CFixedVector3D position; CFixedVector3D rotation; }; #endif ================================================ FILE: fpsgame/graphics/Font.cpp ================================================ /* Copyright (C) 2013 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "Font.h" #include "graphics/FontManager.h" #include "ps/Filesystem.h" #include "ps/CLogger.h" #include "renderer/Renderer.h" #include #include CFont::GlyphMap::GlyphMap() { memset(m_Data, 0, sizeof(m_Data)); } CFont::GlyphMap::~GlyphMap() { for (size_t i = 0; i < ARRAY_SIZE(m_Data); i++) delete[] m_Data[i]; } void CFont::GlyphMap::set(u16 i, const GlyphData& val) { if (!m_Data[i >> 8]) m_Data[i >> 8] = new GlyphData[256](); m_Data[i >> 8][i & 0xff] = val; m_Data[i >> 8][i & 0xff].defined = 1; } int CFont::GetCharacterWidth(wchar_t c) const { const GlyphData* g = m_Glyphs.get(c); if (!g) g = m_Glyphs.get(0xFFFD); // Use the missing glyph symbol if (!g) return 0; return g->xadvance; } void CFont::CalculateStringSize(const wchar_t* string, int& width, int& height) const { width = 0; height = m_Height; for (const wchar_t* c = string; *c != '\0'; c++) { const GlyphData* g = m_Glyphs.get(*c); if (!g) g = m_Glyphs.get(0xFFFD); // Use the missing glyph symbol if (g) width += g->xadvance; // Add the character's advance distance } } ================================================ FILE: fpsgame/graphics/Font.h ================================================ /* Copyright (C) 2013 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_FONT #define INCLUDED_FONT #include "graphics/Texture.h" #include "lib/res/handle.h" #include class CStrW; struct UnifontGlyphData; /** * Storage for a bitmap font. Loaded by CFontManager. */ class CFont { friend class CFontManager; CFont() {} public: struct GlyphData { float u0, v0, u1, v1; i16 x0, y0, x1, y1; i16 xadvance; u8 defined; }; /** * Relatively efficient lookup of GlyphData from 16-bit Unicode codepoint. * This is stored as a sparse 2D array, exploiting the knowledge that a font * typically only supports a small number of 256-codepoint blocks, so most * elements of m_Data will be NULL. */ class GlyphMap { NONCOPYABLE(GlyphMap); public: GlyphMap(); ~GlyphMap(); void set(u16 i, const GlyphData& val); const GlyphData* get(u16 i) const { if (!m_Data[i >> 8]) return NULL; if (!m_Data[i >> 8][i & 0xff].defined) return NULL; return &m_Data[i >> 8][i & 0xff]; } private: GlyphData* m_Data[256]; }; bool HasRGB() const { return m_HasRGB; } int GetLineSpacing() const { return m_LineSpacing; } int GetHeight() const { return m_Height; } int GetCharacterWidth(wchar_t c) const; void CalculateStringSize(const wchar_t* string, int& w, int& h) const; void GetGlyphBounds(float& x0, float& y0, float& x1, float& y1) const { x0 = m_BoundsX0; y0 = m_BoundsY0; x1 = m_BoundsX1; y1 = m_BoundsY1; } const GlyphMap& GetGlyphs() const { return m_Glyphs; } CTexturePtr GetTexture() const { return m_Texture; } private: CTexturePtr m_Texture; bool m_HasRGB; // true if RGBA, false if ALPHA GlyphMap m_Glyphs; int m_LineSpacing; int m_Height; // height of a capital letter, roughly // Bounding box of all glyphs float m_BoundsX0; float m_BoundsY0; float m_BoundsX1; float m_BoundsY1; }; #endif // INCLUDED_FONT ================================================ FILE: fpsgame/graphics/FontManager.cpp ================================================ /* Copyright (C) 2013 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "FontManager.h" #include "graphics/Font.h" #include "graphics/TextureManager.h" #include "ps/CLogger.h" #include "ps/CStr.h" #include "ps/Filesystem.h" #include "renderer/Renderer.h" #include shared_ptr CFontManager::LoadFont(CStrIntern fontName) { FontsMap::iterator it = m_Fonts.find(fontName); if (it != m_Fonts.end()) return it->second; shared_ptr font(new CFont()); if (!ReadFont(font.get(), fontName)) { // Fall back to default font (unless this is the default font) if (fontName == str_sans_10) font.reset(); else font = LoadFont(str_sans_10); } m_Fonts[fontName] = font; return font; } bool CFontManager::ReadFont(CFont* font, CStrIntern fontName) { const VfsPath path(L"fonts/"); // Read font definition file into a stringstream shared_ptr buf; size_t size; const VfsPath fntName(fontName.string() + ".fnt"); if (g_VFS->LoadFile(path / fntName, buf, size) < 0) { LOGERROR("Failed to open font file %s", (path / fntName).string8()); return false; } std::istringstream FNTStream(std::string((const char*)buf.get(), size)); int Version; FNTStream >> Version; if (Version != 101) // Make sure this is from a recent version of the font builder { LOGERROR("Font %s has invalid version", fontName.c_str()); return 0; } int TextureWidth, TextureHeight; FNTStream >> TextureWidth >> TextureHeight; std::string Format; FNTStream >> Format; if (Format == "rgba") font->m_HasRGB = true; else if (Format == "a") font->m_HasRGB = false; else debug_warn(L"Invalid .fnt format string"); int NumGlyphs; FNTStream >> NumGlyphs; FNTStream >> font->m_LineSpacing; FNTStream >> font->m_Height; font->m_BoundsX0 = FLT_MAX; font->m_BoundsY0 = FLT_MAX; font->m_BoundsX1 = -FLT_MAX; font->m_BoundsY1 = -FLT_MAX; for (int i = 0; i < NumGlyphs; ++i) { int Codepoint, TextureX, TextureY, Width, Height, OffsetX, OffsetY, Advance; FNTStream >> Codepoint>>TextureX>>TextureY>>Width>>Height>>OffsetX>>OffsetY>>Advance; if (Codepoint < 0 || Codepoint > 0xFFFF) { LOGWARNING("Font %s has invalid codepoint 0x%x", fontName.c_str(), Codepoint); continue; } float u = (float)TextureX / (float)TextureWidth; float v = (float)TextureY / (float)TextureHeight; float w = (float)Width / (float)TextureWidth; float h = (float)Height / (float)TextureHeight; CFont::GlyphData g = { u, -v, u+w, -v+h, (i16)OffsetX, (i16)-OffsetY, (i16)(OffsetX+Width), (i16)(-OffsetY+Height), (i16)Advance }; font->m_Glyphs.set((u16)Codepoint, g); font->m_BoundsX0 = std::min(font->m_BoundsX0, (float)g.x0); font->m_BoundsY0 = std::min(font->m_BoundsY0, (float)g.y0); font->m_BoundsX1 = std::max(font->m_BoundsX1, (float)g.x1); font->m_BoundsY1 = std::max(font->m_BoundsY1, (float)g.y1); } ENSURE(font->m_Height); // Ensure the height has been found (which should always happen if the font includes an 'I') // Load glyph texture const VfsPath imgName(fontName.string() + ".png"); CTextureProperties textureProps(path / imgName); textureProps.SetFilter(GL_NEAREST); if (!font->m_HasRGB) textureProps.SetFormatOverride(GL_ALPHA); font->m_Texture = g_Renderer.GetTextureManager().CreateTexture(textureProps); return true; } ================================================ FILE: fpsgame/graphics/FontManager.h ================================================ /* Copyright (C) 2013 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_FONTMANAGER #define INCLUDED_FONTMANAGER #include class CFont; class CStrIntern; /** * Font manager: loads and caches bitmap fonts. */ class CFontManager { public: shared_ptr LoadFont(CStrIntern fontName); private: bool ReadFont(CFont* font, CStrIntern fontName); typedef boost::unordered_map > FontsMap; FontsMap m_Fonts; }; #endif // INCLUDED_FONTMANAGER ================================================ FILE: fpsgame/graphics/FontMetrics.cpp ================================================ /* Copyright (C) 2013 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "FontMetrics.h" #include "graphics/Font.h" #include "graphics/FontManager.h" #include "ps/Filesystem.h" #include "ps/CLogger.h" #include "renderer/Renderer.h" CFontMetrics::CFontMetrics(CStrIntern font) { m_Font = g_Renderer.GetFontManager().LoadFont(font); } int CFontMetrics::GetLineSpacing() const { // Return some arbitrary default if the font failed to load, so that the // user of CFontMetrics doesn't have to care about failures if (!m_Font) return 12; return m_Font->GetLineSpacing(); } int CFontMetrics::GetHeight() const { if (!m_Font) return 6; return m_Font->GetHeight(); } int CFontMetrics::GetCharacterWidth(wchar_t c) const { if (!m_Font) return 6; return m_Font->GetCharacterWidth(c); } void CFontMetrics::CalculateStringSize(const wchar_t* string, int& w, int& h) const { if (!m_Font) w = h = 0; else m_Font->CalculateStringSize(string, w, h); } ================================================ FILE: fpsgame/graphics/FontMetrics.h ================================================ /* Copyright (C) 2013 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_FONTMETRICS #define INCLUDED_FONTMETRICS class CFont; class CStrIntern; /** * Helper class for measuring sizes of text. * This will load the font when necessary, and will return plausible values * if loading fails (since misrendering is better than crashing). */ class CFontMetrics { public: CFontMetrics(CStrIntern font); int GetLineSpacing() const; int GetHeight() const; int GetCharacterWidth(wchar_t c) const; void CalculateStringSize(const wchar_t* string, int& w, int& h) const; private: shared_ptr m_Font; }; #endif // INCLUDED_FONTMETRICS ================================================ FILE: fpsgame/graphics/Frustum.cpp ================================================ /* Copyright (C) 2009 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /* * CFrustum is a collection of planes which define a viewing space. */ /* Usually associated with the camera, there are 6 planes which define the view pyramid. But we allow more planes per frustum which may be used for portal rendering, where a portal may have 3 or more edges. */ #include "precompiled.h" #include "Frustum.h" #include "maths/BoundingBoxAligned.h" #include "maths/MathUtil.h" #include "maths/Matrix3D.h" CFrustum::CFrustum () { m_NumPlanes = 0; } CFrustum::~CFrustum () { } void CFrustum::SetNumPlanes (size_t num) { m_NumPlanes = num; //clip it if (m_NumPlanes >= MAX_NUM_FRUSTUM_PLANES) { debug_warn(L"CFrustum::SetNumPlanes: Too many planes"); m_NumPlanes = MAX_NUM_FRUSTUM_PLANES-1; } } void CFrustum::AddPlane(const CPlane& plane) { if (m_NumPlanes >= MAX_NUM_FRUSTUM_PLANES) { debug_warn(L"CFrustum::AddPlane: Too many planes"); return; } m_aPlanes[m_NumPlanes++] = plane; } void CFrustum::Transform(CMatrix3D& m) { for (size_t i = 0; i < m_NumPlanes; ++i) { CVector3D n = m.Rotate(m_aPlanes[i].m_Norm); CVector3D p = m.Transform(m_aPlanes[i].m_Norm * -m_aPlanes[i].m_Dist); m_aPlanes[i].Set(n, p); m_aPlanes[i].Normalize(); } } bool CFrustum::IsPointVisible(const CVector3D& point) const { for (size_t i=0; i radius) return false; } return true; } bool CFrustum::IsBoxVisible(const CVector3D& position, const CBoundingBoxAligned& bounds) const { //basically for every plane we calculate the furthest point //in the box to that plane. If that point is beyond the plane //then the box is not visible CVector3D FarPoint; CVector3D Min = position+bounds[0]; CVector3D Max = position+bounds[1]; for (size_t i=0; i 0.0f ? Max.X : Min.X; FarPoint.Y = m_aPlanes[i].m_Norm.Y > 0.0f ? Max.Y : Min.Y; FarPoint.Z = m_aPlanes[i].m_Norm.Z > 0.0f ? Max.Z : Min.Z; if (m_aPlanes[i].IsPointOnBackSide(FarPoint)) return false; } return true; } bool CFrustum::IsBoxVisible(const CBoundingBoxAligned& bounds) const { //Same as the previous one, but with the position = (0, 0, 0) CVector3D FarPoint; for (size_t i=0; i 0.0f ? bounds[1].X : bounds[0].X; FarPoint.Y = m_aPlanes[i].m_Norm.Y > 0.0f ? bounds[1].Y : bounds[0].Y; FarPoint.Z = m_aPlanes[i].m_Norm.Z > 0.0f ? bounds[1].Z : bounds[0].Z; if (m_aPlanes[i].IsPointOnBackSide(FarPoint)) return false; } return true; } ================================================ FILE: fpsgame/graphics/Frustum.h ================================================ /* Copyright (C) 2009 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /* * CFrustum is a collection of planes which define a viewing space. */ /* Usually associated with the camera, there are 6 planes which define the view pyramid. But we allow more planes per frustum which may be used for portal rendering, where a portal may have 3 or more edges. */ #ifndef INCLUDED_FRUSTUM #define INCLUDED_FRUSTUM #include "maths/Plane.h" //10 planes should be enough #define MAX_NUM_FRUSTUM_PLANES (10) class CBoundingBoxAligned; class CMatrix3D; class CFrustum { public: CFrustum (); ~CFrustum (); //Set the number of planes to use for //calculations. This is clipped to //[0,MAX_NUM_FRUSTUM_PLANES] void SetNumPlanes (size_t num); size_t GetNumPlanes() const { return m_NumPlanes; } void AddPlane (const CPlane& plane); void Transform(CMatrix3D& m); //The following methods return true if the shape is //partially or completely in front of the frustum planes bool IsPointVisible(const CVector3D& point) const; bool DoesSegmentIntersect(const CVector3D& start, const CVector3D& end) const; bool IsSphereVisible(const CVector3D& center, float radius) const; bool IsBoxVisible(const CVector3D& position, const CBoundingBoxAligned& bounds) const; bool IsBoxVisible(const CBoundingBoxAligned& bounds) const; CPlane& operator[](size_t idx) { return m_aPlanes[idx]; } const CPlane& operator[](size_t idx) const { return m_aPlanes[idx]; } public: //make the planes public for ease of use CPlane m_aPlanes[MAX_NUM_FRUSTUM_PLANES]; private: size_t m_NumPlanes; }; #endif ================================================ FILE: fpsgame/graphics/GameView.cpp ================================================ /* Copyright (C) 2015 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "GameView.h" #include "graphics/Camera.h" #include "graphics/CinemaManager.h" #include "graphics/ColladaManager.h" #include "graphics/HFTracer.h" #include "graphics/LOSTexture.h" #include "graphics/LightEnv.h" #include "graphics/Model.h" #include "graphics/ObjectManager.h" #include "graphics/Patch.h" #include "graphics/SkeletonAnimManager.h" #include "graphics/Terrain.h" #include "graphics/TerrainTextureManager.h" #include "graphics/TerritoryTexture.h" #include "graphics/Unit.h" #include "graphics/UnitManager.h" #include "graphics/scripting/JSInterface_GameView.h" #include "lib/input.h" #include "lib/timer.h" #include "lobby/IXmppClient.h" #include "maths/BoundingBoxAligned.h" #include "maths/MathUtil.h" #include "maths/Matrix3D.h" #include "maths/Quaternion.h" #include "ps/ConfigDB.h" #include "ps/Filesystem.h" #include "ps/Game.h" #include "ps/Globals.h" #include "ps/Hotkey.h" #include "ps/Joystick.h" #include "ps/Loader.h" #include "ps/LoaderThunks.h" #include "ps/Profile.h" #include "ps/Pyrogenesis.h" #include "ps/TouchInput.h" #include "ps/World.h" #include "renderer/Renderer.h" #include "renderer/WaterManager.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpPosition.h" #include "simulation2/components/ICmpRangeManager.h" extern int g_xres, g_yres; // Maximum distance outside the edge of the map that the camera's // focus point can be moved static const float CAMERA_EDGE_MARGIN = 2.0f*TERRAIN_TILE_SIZE; /** * A value with exponential decay towards the target value. */ class CSmoothedValue { public: CSmoothedValue(float value, float smoothness, float minDelta) : m_Target(value), m_Current(value), m_Smoothness(smoothness), m_MinDelta(minDelta) { } float GetSmoothedValue() { return m_Current; } void SetValueSmoothly(float value) { m_Target = value; } void AddSmoothly(float value) { m_Target += value; } void Add(float value) { m_Target += value; m_Current += value; } float GetValue() { return m_Target; } void SetValue(float value) { m_Target = value; m_Current = value; } float Update(float time) { if (fabs(m_Target - m_Current) < m_MinDelta) return 0.0f; double p = pow((double)m_Smoothness, 10.0 * (double)time); // (add the factor of 10 so that smoothnesses don't have to be tiny numbers) double delta = (m_Target - m_Current) * (1.0 - p); m_Current += delta; return (float)delta; } void ClampSmoothly(float min, float max) { m_Target = Clamp(m_Target, (double)min, (double)max); } // Wrap so 'target' is in the range [min, max] void Wrap(float min, float max) { double t = fmod(m_Target - min, (double)(max - min)); if (t < 0) t += max - min; t += min; m_Current += t - m_Target; m_Target = t; } private: double m_Target; // the value which m_Current is tending towards double m_Current; // (We use double because the extra precision is worthwhile here) float m_MinDelta; // cutoff where we stop moving (to avoid ugly shimmering effects) public: float m_Smoothness; }; class CGameViewImpl { NONCOPYABLE(CGameViewImpl); public: CGameViewImpl(CGame* game) : Game(game), ColladaManager(g_VFS), MeshManager(ColladaManager), SkeletonAnimManager(ColladaManager), ObjectManager(MeshManager, SkeletonAnimManager, *game->GetSimulation2()), LOSTexture(*game->GetSimulation2()), TerritoryTexture(*game->GetSimulation2()), ViewCamera(), CullCamera(), LockCullCamera(false), ConstrainCamera(true), Culling(true), FollowEntity(INVALID_ENTITY), FollowFirstPerson(false), // Dummy values (these will be filled in by the config file) ViewScrollSpeed(0), ViewScrollSpeedModifier(1), ViewRotateXSpeed(0), ViewRotateXMin(0), ViewRotateXMax(0), ViewRotateXDefault(0), ViewRotateYSpeed(0), ViewRotateYSpeedWheel(0), ViewRotateYDefault(0), ViewRotateSpeedModifier(1), ViewDragSpeed(0), ViewZoomSpeed(0), ViewZoomSpeedWheel(0), ViewZoomMin(0), ViewZoomMax(0), ViewZoomDefault(0), ViewZoomSpeedModifier(1), ViewFOV(DEGTORAD(45.f)), ViewNear(2.f), ViewFar(4096.f), JoystickPanX(-1), JoystickPanY(-1), JoystickRotateX(-1), JoystickRotateY(-1), JoystickZoomIn(-1), JoystickZoomOut(-1), HeightSmoothness(0.5f), HeightMin(16.f), PosX(0, 0, 0.01f), PosY(0, 0, 0.01f), PosZ(0, 0, 0.01f), Zoom(0, 0, 0.1f), RotateX(0, 0, 0.001f), RotateY(0, 0, 0.001f) { } CGame* Game; CColladaManager ColladaManager; CMeshManager MeshManager; CSkeletonAnimManager SkeletonAnimManager; CObjectManager ObjectManager; CLOSTexture LOSTexture; CTerritoryTexture TerritoryTexture; /** * this camera controls the eye position when rendering */ CCamera ViewCamera; /** * this camera controls the frustum that is used for culling * and shadow calculations * * Note that all code that works with camera movements should only change * m_ViewCamera. The render functions automatically sync the cull camera to * the view camera depending on the value of m_LockCullCamera. */ CCamera CullCamera; /** * When @c true, the cull camera is locked in place. * When @c false, the cull camera follows the view camera. * * Exposed to JS as gameView.lockCullCamera */ bool LockCullCamera; /** * When @c true, culling is enabled so that only models that have a chance of * being visible are sent to the renderer. * Otherwise, the entire world is sent to the renderer. * * Exposed to JS as gameView.culling */ bool Culling; /** * Whether the camera movement should be constrained by min/max limits * and terrain avoidance. */ bool ConstrainCamera; /** * Cache global lighting environment. This is used to check whether the * environment has changed during the last frame, so that vertex data can be updated etc. */ CLightEnv CachedLightEnv; CCinemaManager CinemaManager; /** * Entity for the camera to follow, or INVALID_ENTITY if none. */ entity_id_t FollowEntity; /** * Whether to follow FollowEntity in first-person mode. */ bool FollowFirstPerson; //////////////////////////////////////// // Settings float ViewScrollSpeed; float ViewScrollSpeedModifier; float ViewRotateXSpeed; float ViewRotateXMin; float ViewRotateXMax; float ViewRotateXDefault; float ViewRotateYSpeed; float ViewRotateYSpeedWheel; float ViewRotateYDefault; float ViewRotateSpeedModifier; float ViewDragSpeed; float ViewZoomSpeed; float ViewZoomSpeedWheel; float ViewZoomMin; float ViewZoomMax; float ViewZoomDefault; float ViewZoomSpeedModifier; float ViewFOV; float ViewNear; float ViewFar; int JoystickPanX; int JoystickPanY; int JoystickRotateX; int JoystickRotateY; int JoystickZoomIn; int JoystickZoomOut; float HeightSmoothness; float HeightMin; //////////////////////////////////////// // Camera Controls State CSmoothedValue PosX; CSmoothedValue PosY; CSmoothedValue PosZ; CSmoothedValue Zoom; CSmoothedValue RotateX; // inclination around x axis (relative to camera) CSmoothedValue RotateY; // rotation around y (vertical) axis }; #define IMPLEMENT_BOOLEAN_SETTING(NAME) \ bool CGameView::Get##NAME##Enabled() \ { \ return m->NAME; \ } \ \ void CGameView::Set##NAME##Enabled(bool Enabled) \ { \ m->NAME = Enabled; \ } IMPLEMENT_BOOLEAN_SETTING(Culling); IMPLEMENT_BOOLEAN_SETTING(LockCullCamera); IMPLEMENT_BOOLEAN_SETTING(ConstrainCamera); #undef IMPLEMENT_BOOLEAN_SETTING static void SetupCameraMatrixSmooth(CGameViewImpl* m, CMatrix3D* orientation) { orientation->SetIdentity(); orientation->RotateX(m->RotateX.GetSmoothedValue()); orientation->RotateY(m->RotateY.GetSmoothedValue()); orientation->Translate(m->PosX.GetSmoothedValue(), m->PosY.GetSmoothedValue(), m->PosZ.GetSmoothedValue()); } static void SetupCameraMatrixSmoothRot(CGameViewImpl* m, CMatrix3D* orientation) { orientation->SetIdentity(); orientation->RotateX(m->RotateX.GetSmoothedValue()); orientation->RotateY(m->RotateY.GetSmoothedValue()); orientation->Translate(m->PosX.GetValue(), m->PosY.GetValue(), m->PosZ.GetValue()); } static void SetupCameraMatrixNonSmooth(CGameViewImpl* m, CMatrix3D* orientation) { orientation->SetIdentity(); orientation->RotateX(m->RotateX.GetValue()); orientation->RotateY(m->RotateY.GetValue()); orientation->Translate(m->PosX.GetValue(), m->PosY.GetValue(), m->PosZ.GetValue()); } CGameView::CGameView(CGame *pGame): m(new CGameViewImpl(pGame)) { SViewPort vp; vp.m_X=0; vp.m_Y=0; vp.m_Width=g_xres; vp.m_Height=g_yres; m->ViewCamera.SetViewPort(vp); m->ViewCamera.SetProjection(m->ViewNear, m->ViewFar, m->ViewFOV); SetupCameraMatrixSmooth(m, &m->ViewCamera.m_Orientation); m->ViewCamera.UpdateFrustum(); m->CullCamera = m->ViewCamera; g_Renderer.SetSceneCamera(m->ViewCamera, m->CullCamera); } CGameView::~CGameView() { UnloadResources(); delete m; } void CGameView::SetViewport(const SViewPort& vp) { m->ViewCamera.SetViewPort(vp); m->ViewCamera.SetProjection(m->ViewNear, m->ViewFar, m->ViewFOV); } CObjectManager& CGameView::GetObjectManager() const { return m->ObjectManager; } CCamera* CGameView::GetCamera() { return &m->ViewCamera; } CCinemaManager* CGameView::GetCinema() { return &m->CinemaManager; }; CLOSTexture& CGameView::GetLOSTexture() { return m->LOSTexture; } CTerritoryTexture& CGameView::GetTerritoryTexture() { return m->TerritoryTexture; } int CGameView::Initialize() { CFG_GET_VAL("view.scroll.speed", m->ViewScrollSpeed); CFG_GET_VAL("view.scroll.speed.modifier", m->ViewScrollSpeedModifier); CFG_GET_VAL("view.rotate.x.speed", m->ViewRotateXSpeed); CFG_GET_VAL("view.rotate.x.min", m->ViewRotateXMin); CFG_GET_VAL("view.rotate.x.max", m->ViewRotateXMax); CFG_GET_VAL("view.rotate.x.default", m->ViewRotateXDefault); CFG_GET_VAL("view.rotate.y.speed", m->ViewRotateYSpeed); CFG_GET_VAL("view.rotate.y.speed.wheel", m->ViewRotateYSpeedWheel); CFG_GET_VAL("view.rotate.y.default", m->ViewRotateYDefault); CFG_GET_VAL("view.rotate.speed.modifier", m->ViewRotateSpeedModifier); CFG_GET_VAL("view.drag.speed", m->ViewDragSpeed); CFG_GET_VAL("view.zoom.speed", m->ViewZoomSpeed); CFG_GET_VAL("view.zoom.speed.wheel", m->ViewZoomSpeedWheel); CFG_GET_VAL("view.zoom.min", m->ViewZoomMin); CFG_GET_VAL("view.zoom.max", m->ViewZoomMax); CFG_GET_VAL("view.zoom.default", m->ViewZoomDefault); CFG_GET_VAL("view.zoom.speed.modifier", m->ViewZoomSpeedModifier); CFG_GET_VAL("joystick.camera.pan.x", m->JoystickPanX); CFG_GET_VAL("joystick.camera.pan.y", m->JoystickPanY); CFG_GET_VAL("joystick.camera.rotate.x", m->JoystickRotateX); CFG_GET_VAL("joystick.camera.rotate.y", m->JoystickRotateY); CFG_GET_VAL("joystick.camera.zoom.in", m->JoystickZoomIn); CFG_GET_VAL("joystick.camera.zoom.out", m->JoystickZoomOut); CFG_GET_VAL("view.height.smoothness", m->HeightSmoothness); CFG_GET_VAL("view.height.min", m->HeightMin); CFG_GET_VAL("view.pos.smoothness", m->PosX.m_Smoothness); CFG_GET_VAL("view.pos.smoothness", m->PosY.m_Smoothness); CFG_GET_VAL("view.pos.smoothness", m->PosZ.m_Smoothness); CFG_GET_VAL("view.zoom.smoothness", m->Zoom.m_Smoothness); CFG_GET_VAL("view.rotate.x.smoothness", m->RotateX.m_Smoothness); CFG_GET_VAL("view.rotate.y.smoothness", m->RotateY.m_Smoothness); CFG_GET_VAL("view.near", m->ViewNear); CFG_GET_VAL("view.far", m->ViewFar); CFG_GET_VAL("view.fov", m->ViewFOV); // Convert to radians m->RotateX.SetValue(DEGTORAD(m->ViewRotateXDefault)); m->RotateY.SetValue(DEGTORAD(m->ViewRotateYDefault)); m->ViewFOV = DEGTORAD(m->ViewFOV); return 0; } void CGameView::RegisterInit() { // CGameView init RegMemFun(this, &CGameView::Initialize, L"CGameView init", 1); // previously done by CGameView::InitResources RegMemFun(g_TexMan.GetSingletonPtr(), &CTerrainTextureManager::LoadTerrainTextures, L"LoadTerrainTextures", 60); RegMemFun(g_Renderer.GetSingletonPtr(), &CRenderer::LoadAlphaMaps, L"LoadAlphaMaps", 5); } void CGameView::BeginFrame() { if (m->LockCullCamera == false) { // Set up cull camera m->CullCamera = m->ViewCamera; } g_Renderer.SetSceneCamera(m->ViewCamera, m->CullCamera); CheckLightEnv(); m->Game->CachePlayerColors(); } void CGameView::Render() { g_Renderer.RenderScene(*this); } /////////////////////////////////////////////////////////// // This callback is part of the Scene interface // Submit all objects visible in the given frustum void CGameView::EnumerateObjects(const CFrustum& frustum, SceneCollector* c) { { PROFILE3("submit terrain"); CTerrain* pTerrain = m->Game->GetWorld()->GetTerrain(); float waterHeight = g_Renderer.GetWaterManager()->m_WaterHeight + 0.001f; const ssize_t patchesPerSide = pTerrain->GetPatchesPerSide(); // find out which patches will be drawn for (ssize_t j=0; jGetPatch(i,j); // can't fail // If the patch is underwater, calculate a bounding box that also contains the water plane CBoundingBoxAligned bounds = patch->GetWorldBounds(); if(bounds[1].Y < waterHeight) bounds[1].Y = waterHeight; if (!m->Culling || frustum.IsBoxVisible(bounds)) c->Submit(patch); } } } m->Game->GetSimulation2()->RenderSubmit(*c, frustum, m->Culling); } void CGameView::CheckLightEnv() { if (m->CachedLightEnv == g_LightEnv) return; if (m->CachedLightEnv.GetLightingModel() != g_LightEnv.GetLightingModel()) g_Renderer.MakeShadersDirty(); m->CachedLightEnv = g_LightEnv; CTerrain* pTerrain = m->Game->GetWorld()->GetTerrain(); if (!pTerrain) return; PROFILE("update light env"); pTerrain->MakeDirty(RENDERDATA_UPDATE_COLOR); const std::vector& units = m->Game->GetWorld()->GetUnitManager().GetUnits(); for (size_t i = 0; i < units.size(); ++i) units[i]->GetModel().SetDirtyRec(RENDERDATA_UPDATE_COLOR); } void CGameView::UnloadResources() { g_TexMan.UnloadTerrainTextures(); g_Renderer.UnloadAlphaMaps(); g_Renderer.GetWaterManager()->UnloadWaterTextures(); } static void FocusHeight(CGameViewImpl* m, bool smooth) { /* The camera pivot height is moved towards ground level. To prevent excessive zoom when looking over a cliff, the target ground level is the maximum of the ground level at the camera's near and pivot points. The ground levels are filtered to achieve smooth camera movement. The filter radius is proportional to the zoom level. The camera height is clamped to prevent map penetration. */ if (!m->ConstrainCamera) return; CCamera targetCam = m->ViewCamera; SetupCameraMatrixSmoothRot(m, &targetCam.m_Orientation); const CVector3D position = targetCam.m_Orientation.GetTranslation(); const CVector3D forwards = targetCam.m_Orientation.GetIn(); // horizontal view radius const float radius = sqrtf(forwards.X * forwards.X + forwards.Z * forwards.Z) * m->Zoom.GetSmoothedValue(); const float near_radius = radius * m->HeightSmoothness; const float pivot_radius = radius * m->HeightSmoothness; const CVector3D nearPoint = position + forwards * m->ViewNear; const CVector3D pivotPoint = position + forwards * m->Zoom.GetSmoothedValue(); const float ground = m->Game->GetWorld()->GetTerrain()->GetExactGroundLevel(nearPoint.X, nearPoint.Z); // filter ground levels for smooth camera movement const float filtered_near_ground = m->Game->GetWorld()->GetTerrain()->GetFilteredGroundLevel(nearPoint.X, nearPoint.Z, near_radius); const float filtered_pivot_ground = m->Game->GetWorld()->GetTerrain()->GetFilteredGroundLevel(pivotPoint.X, pivotPoint.Z, pivot_radius); // filtered maximum visible ground level in view const float filtered_ground = std::max(filtered_near_ground, filtered_pivot_ground); // target camera height above pivot point const float pivot_height = -forwards.Y * (m->Zoom.GetSmoothedValue() - m->ViewNear); // minimum camera height above filtered ground level const float min_height = (m->HeightMin + ground - filtered_ground); const float target_height = std::max(pivot_height, min_height); const float height = (nearPoint.Y - filtered_ground); const float diff = target_height - height; if (fabsf(diff) < 0.0001f) return; if (smooth) { m->PosY.AddSmoothly(diff); } else { m->PosY.Add(diff); } } CVector3D CGameView::GetSmoothPivot(CCamera& camera) const { return camera.m_Orientation.GetTranslation() + camera.m_Orientation.GetIn() * m->Zoom.GetSmoothedValue(); } void CGameView::Update(const float deltaRealTime) { // If camera movement is being handled by the touch-input system, // then we should stop to avoid conflicting with it if (g_TouchInput.IsEnabled()) return; if (!g_app_has_focus) return; if (m->CinemaManager.GetEnabled()) { m->CinemaManager.Update(deltaRealTime); return; } // Calculate mouse movement static int mouse_last_x = 0; static int mouse_last_y = 0; int mouse_dx = g_mouse_x - mouse_last_x; int mouse_dy = g_mouse_y - mouse_last_y; mouse_last_x = g_mouse_x; mouse_last_y = g_mouse_y; if (HotkeyIsPressed("camera.rotate.cw")) m->RotateY.AddSmoothly(m->ViewRotateYSpeed * deltaRealTime); if (HotkeyIsPressed("camera.rotate.ccw")) m->RotateY.AddSmoothly(-m->ViewRotateYSpeed * deltaRealTime); if (HotkeyIsPressed("camera.rotate.up")) m->RotateX.AddSmoothly(-m->ViewRotateXSpeed * deltaRealTime); if (HotkeyIsPressed("camera.rotate.down")) m->RotateX.AddSmoothly(m->ViewRotateXSpeed * deltaRealTime); float moveRightward = 0.f; float moveForward = 0.f; if (HotkeyIsPressed("camera.pan")) { moveRightward += m->ViewDragSpeed * mouse_dx; moveForward += m->ViewDragSpeed * -mouse_dy; } if (g_mouse_active) { if (g_mouse_x >= g_xres - 2 && g_mouse_x < g_xres) moveRightward += m->ViewScrollSpeed * deltaRealTime; else if (g_mouse_x <= 3 && g_mouse_x >= 0) moveRightward -= m->ViewScrollSpeed * deltaRealTime; if (g_mouse_y >= g_yres - 2 && g_mouse_y < g_yres) moveForward -= m->ViewScrollSpeed * deltaRealTime; else if (g_mouse_y <= 3 && g_mouse_y >= 0) moveForward += m->ViewScrollSpeed * deltaRealTime; } if (HotkeyIsPressed("camera.right")) moveRightward += m->ViewScrollSpeed * deltaRealTime; if (HotkeyIsPressed("camera.left")) moveRightward -= m->ViewScrollSpeed * deltaRealTime; if (HotkeyIsPressed("camera.up")) moveForward += m->ViewScrollSpeed * deltaRealTime; if (HotkeyIsPressed("camera.down")) moveForward -= m->ViewScrollSpeed * deltaRealTime; if (g_Joystick.IsEnabled()) { // This could all be improved with extra speed and sensitivity settings // (maybe use pow to allow finer control?), and inversion settings moveRightward += g_Joystick.GetAxisValue(m->JoystickPanX) * m->ViewScrollSpeed * deltaRealTime; moveForward -= g_Joystick.GetAxisValue(m->JoystickPanY) * m->ViewScrollSpeed * deltaRealTime; m->RotateX.AddSmoothly(g_Joystick.GetAxisValue(m->JoystickRotateX) * m->ViewRotateXSpeed * deltaRealTime); m->RotateY.AddSmoothly(-g_Joystick.GetAxisValue(m->JoystickRotateY) * m->ViewRotateYSpeed * deltaRealTime); // Use a +1 bias for zoom because I want this to work with trigger buttons that default to -1 m->Zoom.AddSmoothly((g_Joystick.GetAxisValue(m->JoystickZoomIn) + 1.0f) / 2.0f * m->ViewZoomSpeed * deltaRealTime); m->Zoom.AddSmoothly(-(g_Joystick.GetAxisValue(m->JoystickZoomOut) + 1.0f) / 2.0f * m->ViewZoomSpeed * deltaRealTime); } if (moveRightward || moveForward) { // Break out of following mode when the user starts scrolling m->FollowEntity = INVALID_ENTITY; float s = sin(m->RotateY.GetSmoothedValue()); float c = cos(m->RotateY.GetSmoothedValue()); m->PosX.AddSmoothly(c * moveRightward); m->PosZ.AddSmoothly(-s * moveRightward); m->PosX.AddSmoothly(s * moveForward); m->PosZ.AddSmoothly(c * moveForward); } if (m->FollowEntity) { CmpPtr cmpPosition(*(m->Game->GetSimulation2()), m->FollowEntity); CmpPtr cmpRangeManager(*(m->Game->GetSimulation2()), SYSTEM_ENTITY); if (cmpPosition && cmpPosition->IsInWorld() && cmpRangeManager && cmpRangeManager->GetLosVisibility(m->FollowEntity, m->Game->GetViewedPlayerID()) == ICmpRangeManager::VIS_VISIBLE) { // Get the most recent interpolated position float frameOffset = m->Game->GetSimulation2()->GetLastFrameOffset(); CMatrix3D transform = cmpPosition->GetInterpolatedTransform(frameOffset); CVector3D pos = transform.GetTranslation(); if (m->FollowFirstPerson) { float x, z, angle; cmpPosition->GetInterpolatedPosition2D(frameOffset, x, z, angle); float height = 4.f; m->ViewCamera.m_Orientation.SetIdentity(); m->ViewCamera.m_Orientation.RotateX((float)M_PI/24.f); m->ViewCamera.m_Orientation.RotateY(angle); m->ViewCamera.m_Orientation.Translate(pos.X, pos.Y + height, pos.Z); m->ViewCamera.UpdateFrustum(); return; } else { // Move the camera to match the unit CCamera targetCam = m->ViewCamera; SetupCameraMatrixSmoothRot(m, &targetCam.m_Orientation); CVector3D pivot = GetSmoothPivot(targetCam); CVector3D delta = pos - pivot; m->PosX.AddSmoothly(delta.X); m->PosY.AddSmoothly(delta.Y); m->PosZ.AddSmoothly(delta.Z); } } else { // The unit disappeared (died or garrisoned etc), so stop following it m->FollowEntity = INVALID_ENTITY; } } if (HotkeyIsPressed("camera.zoom.in")) m->Zoom.AddSmoothly(-m->ViewZoomSpeed * deltaRealTime); if (HotkeyIsPressed("camera.zoom.out")) m->Zoom.AddSmoothly(m->ViewZoomSpeed * deltaRealTime); if (m->ConstrainCamera) m->Zoom.ClampSmoothly(m->ViewZoomMin, m->ViewZoomMax); float zoomDelta = -m->Zoom.Update(deltaRealTime); if (zoomDelta) { CVector3D forwards = m->ViewCamera.m_Orientation.GetIn(); m->PosX.AddSmoothly(forwards.X * zoomDelta); m->PosY.AddSmoothly(forwards.Y * zoomDelta); m->PosZ.AddSmoothly(forwards.Z * zoomDelta); } if (m->ConstrainCamera) m->RotateX.ClampSmoothly(DEGTORAD(m->ViewRotateXMin), DEGTORAD(m->ViewRotateXMax)); FocusHeight(m, true); // Ensure the ViewCamera focus is inside the map with the chosen margins // if not so - apply margins to the camera if (m->ConstrainCamera) { CCamera targetCam = m->ViewCamera; SetupCameraMatrixSmoothRot(m, &targetCam.m_Orientation); CTerrain* pTerrain = m->Game->GetWorld()->GetTerrain(); CVector3D pivot = GetSmoothPivot(targetCam); CVector3D delta = targetCam.m_Orientation.GetTranslation() - pivot; CVector3D desiredPivot = pivot; CmpPtr cmpRangeManager(*(m->Game->GetSimulation2()), SYSTEM_ENTITY); if (cmpRangeManager && cmpRangeManager->GetLosCircular()) { // Clamp to a circular region around the center of the map float r = pTerrain->GetMaxX() / 2; CVector3D center(r, desiredPivot.Y, r); float dist = (desiredPivot - center).Length(); if (dist > r - CAMERA_EDGE_MARGIN) desiredPivot = center + (desiredPivot - center).Normalized() * (r - CAMERA_EDGE_MARGIN); } else { // Clamp to the square edges of the map desiredPivot.X = Clamp(desiredPivot.X, pTerrain->GetMinX() + CAMERA_EDGE_MARGIN, pTerrain->GetMaxX() - CAMERA_EDGE_MARGIN); desiredPivot.Z = Clamp(desiredPivot.Z, pTerrain->GetMinZ() + CAMERA_EDGE_MARGIN, pTerrain->GetMaxZ() - CAMERA_EDGE_MARGIN); } // Update the position so that pivot is within the margin m->PosX.SetValueSmoothly(desiredPivot.X + delta.X); m->PosZ.SetValueSmoothly(desiredPivot.Z + delta.Z); } m->PosX.Update(deltaRealTime); m->PosY.Update(deltaRealTime); m->PosZ.Update(deltaRealTime); // Handle rotation around the Y (vertical) axis { CCamera targetCam = m->ViewCamera; SetupCameraMatrixSmooth(m, &targetCam.m_Orientation); float rotateYDelta = m->RotateY.Update(deltaRealTime); if (rotateYDelta) { // We've updated RotateY, and need to adjust Pos so that it's still // facing towards the original focus point (the terrain in the center // of the screen). CVector3D upwards(0.0f, 1.0f, 0.0f); CVector3D pivot = GetSmoothPivot(targetCam); CVector3D delta = targetCam.m_Orientation.GetTranslation() - pivot; CQuaternion q; q.FromAxisAngle(upwards, rotateYDelta); CVector3D d = q.Rotate(delta) - delta; m->PosX.Add(d.X); m->PosY.Add(d.Y); m->PosZ.Add(d.Z); } } // Handle rotation around the X (sideways, relative to camera) axis { CCamera targetCam = m->ViewCamera; SetupCameraMatrixSmooth(m, &targetCam.m_Orientation); float rotateXDelta = m->RotateX.Update(deltaRealTime); if (rotateXDelta) { CVector3D rightwards = targetCam.m_Orientation.GetLeft() * -1.0f; CVector3D pivot = GetSmoothPivot(targetCam); CVector3D delta = targetCam.m_Orientation.GetTranslation() - pivot; CQuaternion q; q.FromAxisAngle(rightwards, rotateXDelta); CVector3D d = q.Rotate(delta) - delta; m->PosX.Add(d.X); m->PosY.Add(d.Y); m->PosZ.Add(d.Z); } } /* This is disabled since it doesn't seem necessary: // Ensure the camera's near point is never inside the terrain if (m->ConstrainCamera) { CMatrix3D target; target.SetIdentity(); target.RotateX(m->RotateX.GetValue()); target.RotateY(m->RotateY.GetValue()); target.Translate(m->PosX.GetValue(), m->PosY.GetValue(), m->PosZ.GetValue()); CVector3D nearPoint = target.GetTranslation() + target.GetIn() * defaultNear; float ground = m->Game->GetWorld()->GetTerrain()->GetExactGroundLevel(nearPoint.X, nearPoint.Z); float limit = ground + 16.f; if (nearPoint.Y < limit) m->PosY.AddSmoothly(limit - nearPoint.Y); } */ m->RotateY.Wrap(-(float)M_PI, (float)M_PI); // Update the camera matrix m->ViewCamera.SetProjection(m->ViewNear, m->ViewFar, m->ViewFOV); SetupCameraMatrixSmooth(m, &m->ViewCamera.m_Orientation); m->ViewCamera.UpdateFrustum(); } float CGameView::GetCameraX() { CCamera targetCam = m->ViewCamera; CVector3D pivot = GetSmoothPivot(targetCam); return pivot.X; } float CGameView::GetCameraZ() { CCamera targetCam = m->ViewCamera; CVector3D pivot = GetSmoothPivot(targetCam); return pivot.Z; } float CGameView::GetCameraPosX() { return m->PosX.GetValue(); } float CGameView::GetCameraPosY() { return m->PosY.GetValue(); } float CGameView::GetCameraPosZ() { return m->PosZ.GetValue(); } float CGameView::GetCameraRotX() { return m->RotateX.GetValue(); } float CGameView::GetCameraRotY() { return m->RotateY.GetValue(); } float CGameView::GetCameraZoom() { return m->Zoom.GetValue(); } void CGameView::SetCamera(CVector3D Pos, float RotX, float RotY, float zoom) { m->PosX.SetValue(Pos.X); m->PosY.SetValue(Pos.Y); m->PosZ.SetValue(Pos.Z); m->RotateX.SetValue(RotX); m->RotateY.SetValue(RotY); m->Zoom.SetValue(zoom); FocusHeight(m, false); SetupCameraMatrixNonSmooth(m, &m->ViewCamera.m_Orientation); m->ViewCamera.UpdateFrustum(); // Break out of following mode so the camera really moves to the target m->FollowEntity = INVALID_ENTITY; } void CGameView::MoveCameraTarget(const CVector3D& target) { // Maintain the same orientation and level of zoom, if we can // (do this by working out the point the camera is looking at, saving // the difference between that position and the camera point, and restoring // that difference to our new target) CCamera targetCam = m->ViewCamera; SetupCameraMatrixNonSmooth(m, &targetCam.m_Orientation); CVector3D pivot = GetSmoothPivot(targetCam); CVector3D delta = target - pivot; m->PosX.SetValueSmoothly(delta.X + m->PosX.GetValue()); m->PosZ.SetValueSmoothly(delta.Z + m->PosZ.GetValue()); FocusHeight(m, false); // Break out of following mode so the camera really moves to the target m->FollowEntity = INVALID_ENTITY; } void CGameView::ResetCameraTarget(const CVector3D& target) { CMatrix3D orientation; orientation.SetIdentity(); orientation.RotateX(DEGTORAD(m->ViewRotateXDefault)); orientation.RotateY(DEGTORAD(m->ViewRotateYDefault)); CVector3D delta = orientation.GetIn() * m->ViewZoomDefault; m->PosX.SetValue(target.X - delta.X); m->PosY.SetValue(target.Y - delta.Y); m->PosZ.SetValue(target.Z - delta.Z); m->RotateX.SetValue(DEGTORAD(m->ViewRotateXDefault)); m->RotateY.SetValue(DEGTORAD(m->ViewRotateYDefault)); m->Zoom.SetValue(m->ViewZoomDefault); FocusHeight(m, false); SetupCameraMatrixSmooth(m, &m->ViewCamera.m_Orientation); m->ViewCamera.UpdateFrustum(); // Break out of following mode so the camera really moves to the target m->FollowEntity = INVALID_ENTITY; } void CGameView::ResetCameraAngleZoom() { CCamera targetCam = m->ViewCamera; SetupCameraMatrixNonSmooth(m, &targetCam.m_Orientation); // Compute the zoom adjustment to get us back to the default CVector3D forwards = targetCam.m_Orientation.GetIn(); CVector3D pivot = GetSmoothPivot(targetCam); CVector3D delta = pivot - targetCam.m_Orientation.GetTranslation(); float dist = delta.Dot(forwards); m->Zoom.AddSmoothly(m->ViewZoomDefault - dist); // Reset orientations to default m->RotateX.SetValueSmoothly(DEGTORAD(m->ViewRotateXDefault)); m->RotateY.SetValueSmoothly(DEGTORAD(m->ViewRotateYDefault)); } void CGameView::CameraFollow(entity_id_t entity, bool firstPerson) { m->FollowEntity = entity; m->FollowFirstPerson = firstPerson; } entity_id_t CGameView::GetFollowedEntity() { return m->FollowEntity; } float CGameView::GetNear() const { return m->ViewNear; } float CGameView::GetFar() const { return m->ViewFar; } float CGameView::GetFOV() const { return m->ViewFOV; } void CGameView::SetCameraProjection() { m->ViewCamera.SetProjection(m->ViewNear, m->ViewFar, m->ViewFOV); } InReaction game_view_handler(const SDL_Event_* ev) { // put any events that must be processed even if inactive here if(!g_app_has_focus || !g_Game || !g_Game->IsGameStarted()) return IN_PASS; CGameView *pView=g_Game->GetView(); return pView->HandleEvent(ev); } InReaction CGameView::HandleEvent(const SDL_Event_* ev) { switch(ev->ev.type) { case SDL_HOTKEYDOWN: std::string hotkey = static_cast(ev->ev.user.data1); if (hotkey == "wireframe") { if (g_XmppClient && g_rankedGame == true) break; else if (g_Renderer.GetModelRenderMode() == SOLID) { g_Renderer.SetTerrainRenderMode(EDGED_FACES); g_Renderer.SetModelRenderMode(EDGED_FACES); } else if (g_Renderer.GetModelRenderMode() == EDGED_FACES) { g_Renderer.SetTerrainRenderMode(WIREFRAME); g_Renderer.SetModelRenderMode(WIREFRAME); } else { g_Renderer.SetTerrainRenderMode(SOLID); g_Renderer.SetModelRenderMode(SOLID); } return IN_HANDLED; } // Mouse wheel must be treated using events instead of polling, // because SDL auto-generates a sequence of mousedown/mouseup events // and we never get to see the "down" state inside Update(). else if (hotkey == "camera.zoom.wheel.in") { m->Zoom.AddSmoothly(-m->ViewZoomSpeedWheel); return IN_HANDLED; } else if (hotkey == "camera.zoom.wheel.out") { m->Zoom.AddSmoothly(m->ViewZoomSpeedWheel); return IN_HANDLED; } else if (hotkey == "camera.rotate.wheel.cw") { m->RotateY.AddSmoothly(m->ViewRotateYSpeedWheel); return IN_HANDLED; } else if (hotkey == "camera.rotate.wheel.ccw") { m->RotateY.AddSmoothly(-m->ViewRotateYSpeedWheel); return IN_HANDLED; } else if (hotkey == "camera.scroll.speed.increase") { m->ViewScrollSpeed *= m->ViewScrollSpeedModifier; return IN_HANDLED; } else if (hotkey == "camera.scroll.speed.decrease") { m->ViewScrollSpeed /= m->ViewScrollSpeedModifier; return IN_HANDLED; } else if (hotkey == "camera.rotate.speed.increase") { m->ViewRotateXSpeed *= m->ViewRotateSpeedModifier; m->ViewRotateYSpeed *= m->ViewRotateSpeedModifier; return IN_HANDLED; } else if (hotkey == "camera.rotate.speed.decrease") { m->ViewRotateXSpeed /= m->ViewRotateSpeedModifier; m->ViewRotateYSpeed /= m->ViewRotateSpeedModifier; return IN_HANDLED; } else if (hotkey == "camera.zoom.speed.increase") { m->ViewZoomSpeed *= m->ViewZoomSpeedModifier; return IN_HANDLED; } else if (hotkey == "camera.zoom.speed.decrease") { m->ViewZoomSpeed /= m->ViewZoomSpeedModifier; return IN_HANDLED; } else if (hotkey == "camera.reset") { ResetCameraAngleZoom(); return IN_HANDLED; } } return IN_PASS; } ================================================ FILE: fpsgame/graphics/GameView.h ================================================ /* Copyright (C) 2010 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_GAMEVIEW #define INCLUDED_GAMEVIEW #include "renderer/Scene.h" #include "simulation2/system/Entity.h" #include "lib/input.h" // InReaction - can't forward-declare enum class CGame; class CObjectManager; class CCamera; class CCinemaManager; class CVector3D; struct SViewPort; class JSObject; class CGameViewImpl; class CGameView : private Scene { NONCOPYABLE(CGameView); private: CGameViewImpl* m; // Check whether lighting environment has changed and update vertex data if necessary void CheckLightEnv(); public: //BEGIN: Implementation of Scene virtual void EnumerateObjects(const CFrustum& frustum, SceneCollector* c); virtual CLOSTexture& GetLOSTexture(); virtual CTerritoryTexture& GetTerritoryTexture(); //END: Implementation of Scene private: // InitResources(): Load all graphics resources (textures, actor objects and // alpha maps) required by the game //void InitResources(); // UnloadResources(): Unload all graphics resources loaded by InitResources void UnloadResources(); public: CGameView(CGame *pGame); ~CGameView(); void SetViewport(const SViewPort& vp); void RegisterInit(); int Initialize(); CObjectManager& GetObjectManager() const; /** * Updates all the view information (i.e. rotate camera, scroll, whatever). This will *not* change any * World information - only the *presentation*. * * @param deltaRealTime Elapsed real time since the last frame. */ void Update(const float deltaRealTime); void BeginFrame(); void Render(); InReaction HandleEvent(const SDL_Event_* ev); float GetCameraX(); float GetCameraZ(); float GetCameraPosX(); float GetCameraPosY(); float GetCameraPosZ(); float GetCameraRotX(); float GetCameraRotY(); float GetCameraZoom(); void SetCamera(CVector3D Pos, float RotX, float RotY, float Zoom); void MoveCameraTarget(const CVector3D& target); void ResetCameraTarget(const CVector3D& target); void ResetCameraAngleZoom(); void CameraFollow(entity_id_t entity, bool firstPerson); entity_id_t GetFollowedEntity(); CVector3D GetSmoothPivot(CCamera &camera) const; float GetNear() const; float GetFar() const; float GetFOV() const; #define DECLARE_BOOLEAN_SETTING(NAME) \ bool Get##NAME##Enabled(); \ void Set##NAME##Enabled(bool Enabled); DECLARE_BOOLEAN_SETTING(Culling); DECLARE_BOOLEAN_SETTING(LockCullCamera); DECLARE_BOOLEAN_SETTING(ConstrainCamera); #undef DECLARE_BOOLEAN_SETTING // Set projection of current camera using near, far, and FOV values void SetCameraProjection(); CCamera *GetCamera(); CCinemaManager* GetCinema(); JSObject* GetScript(); }; extern InReaction game_view_handler(const SDL_Event_* ev); #endif ================================================ FILE: fpsgame/graphics/HFTracer.cpp ================================================ /* Copyright (C) 2014 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /* * Determine intersection of rays with a heightfield. */ #include "precompiled.h" #include "HFTracer.h" #include "graphics/Patch.h" #include "graphics/Terrain.h" #include "maths/BoundingBoxAligned.h" #include "maths/MathUtil.h" #include "maths/Vector3D.h" #include // To cope well with points that are slightly off the edge of the map, // we act as if there's an N-tile margin around the edges of the heightfield. // (N shouldn't be too huge else it'll hurt performance a little when // RayIntersect loops through it all.) // CTerrain::CalcPosition implements clamp-to-edge behaviour so the tracer // will have that behaviour. static const int MARGIN_SIZE = 64; /////////////////////////////////////////////////////////////////////////////// // CHFTracer constructor CHFTracer::CHFTracer(CTerrain *pTerrain): m_pTerrain(pTerrain), m_Heightfield(m_pTerrain->GetHeightMap()), m_MapSize(m_pTerrain->GetVerticesPerSide()), m_CellSize((float)TERRAIN_TILE_SIZE), m_HeightScale(HEIGHT_SCALE) { } /////////////////////////////////////////////////////////////////////////////// // RayTriIntersect: intersect a ray with triangle defined by vertices // v0,v1,v2; return true if ray hits triangle at distance less than dist, // or false otherwise static bool RayTriIntersect(const CVector3D& v0, const CVector3D& v1, const CVector3D& v2, const CVector3D& origin, const CVector3D& dir, float& dist) { const float EPSILON=0.00001f; // calculate edge vectors CVector3D edge0=v1-v0; CVector3D edge1=v2-v0; // begin calculating determinant - also used to calculate U parameter CVector3D pvec=dir.Cross(edge1); // if determinant is near zero, ray lies in plane of triangle float det = edge0.Dot(pvec); if (fabs(det)1.01f) return false; // prepare to test V parameter CVector3D qvec=tvec.Cross(edge0); // calculate V parameter and test bounds float v=dir.Dot(qvec)*inv_det; if (v<0.0f || u+v>1.0f) return false; // calculate distance to intersection point from ray origin float d=edge1.Dot(qvec)*inv_det; if (d>=0 && dCalcPosition(cx,cz,vpos[0]); m_pTerrain->CalcPosition(cx+1,cz,vpos[1]); m_pTerrain->CalcPosition(cx+1,cz+1,vpos[2]); m_pTerrain->CalcPosition(cx,cz+1,vpos[3]); dist=1.0e30f; if (RayTriIntersect(vpos[0],vpos[1],vpos[2],origin,dir,dist)) { res=true; } if (RayTriIntersect(vpos[0],vpos[2],vpos[3],origin,dir,dist)) { res=true; } return res; } /////////////////////////////////////////////////////////////////////////////// // RayIntersect: intersect ray with this heightfield; return true if // intersection occurs (and fill in grid coordinates of intersection), or false // otherwise bool CHFTracer::RayIntersect(const CVector3D& origin, const CVector3D& dir, int& x, int& z, CVector3D& ipt) const { // If the map is empty (which should never happen), // return early before we crash when reading zero-sized heightmaps if (!m_MapSize) { debug_warn(L"CHFTracer::RayIntersect called with zero-size map"); return false; } // intersect first against bounding box CBoundingBoxAligned bound; bound[0] = CVector3D(-MARGIN_SIZE * m_CellSize, 0, -MARGIN_SIZE * m_CellSize); bound[1] = CVector3D((m_MapSize + MARGIN_SIZE) * m_CellSize, 65535 * m_HeightScale, (m_MapSize + MARGIN_SIZE) * m_CellSize); float tmin,tmax; if (!bound.RayIntersect(origin,dir,tmin,tmax)) { // ray missed world bounds; no intersection return false; } // project origin onto grid, if necessary, to get starting point for traversal CVector3D traversalPt; if (tmin>0) { traversalPt=origin+dir*tmin; } else { traversalPt=origin; } // setup traversal variables int sx=dir.X<0 ? -1 : 1; int sz=dir.Z<0 ? -1 : 1; float invCellSize=1.0f/float(m_CellSize); float fcx=traversalPt.X*invCellSize; int cx=(int)floor(fcx); float fcz=traversalPt.Z*invCellSize; int cz=(int)floor(fcz); float invdx = 1.0e20f; float invdz = 1.0e20f; if (fabs(dir.X) > 1.0e-20) invdx = float(1.0/fabs(dir.X)); if (fabs(dir.Z) > 1.0e-20) invdz = float(1.0/fabs(dir.Z)); do { // test current cell if (cx >= -MARGIN_SIZE && cx < int(m_MapSize + MARGIN_SIZE - 1) && cz >= -MARGIN_SIZE && cz < int(m_MapSize + MARGIN_SIZE - 1)) { float dist; if (CellIntersect(cx,cz,origin,dir,dist)) { x=cx; z=cz; ipt=origin+dir*dist; return true; } } else { // Degenerate case: y close to zero // catch travelling off the map if ((cx < -MARGIN_SIZE) && (sx < 0)) return false; if ((cx >= (int)(m_MapSize + MARGIN_SIZE - 1)) && (sx > 0)) return false; if ((cz < -MARGIN_SIZE) && (sz < 0)) return false; if ((cz >= (int)(m_MapSize + MARGIN_SIZE - 1)) && (sz > 0)) return false; } // get coords of current cell fcx=traversalPt.X*invCellSize; fcz=traversalPt.Z*invCellSize; // get distance to next cell in x,z float dx=(sx==-1) ? fcx-float(cx) : 1-(fcx-float(cx)); dx*=invdx; float dz=(sz==-1) ? fcz-float(cz) : 1-(fcz-float(cz)); dz*=invdz; // advance .. float dist; if (dx=0); // fell off end of heightmap with no intersection; return a miss return false; } static bool TestTile(u16* heightmap, int stride, int i, int j, const CVector3D& pos, const CVector3D& dir, CVector3D& isct) { u16 y00 = heightmap[i + j*stride]; u16 y10 = heightmap[i+1 + j*stride]; u16 y01 = heightmap[i + (j+1)*stride]; u16 y11 = heightmap[i+1 + (j+1)*stride]; CVector3D p00( i * TERRAIN_TILE_SIZE, y00 * HEIGHT_SCALE, j * TERRAIN_TILE_SIZE); CVector3D p10((i+1) * TERRAIN_TILE_SIZE, y10 * HEIGHT_SCALE, j * TERRAIN_TILE_SIZE); CVector3D p01( i * TERRAIN_TILE_SIZE, y01 * HEIGHT_SCALE, (j+1) * TERRAIN_TILE_SIZE); CVector3D p11((i+1) * TERRAIN_TILE_SIZE, y11 * HEIGHT_SCALE, (j+1) * TERRAIN_TILE_SIZE); int mid1 = y00+y11; int mid2 = y01+y10; int triDir = (mid1 < mid2); float dist = FLT_MAX; if (triDir) { if (RayTriIntersect(p00, p10, p01, pos, dir, dist) || // lower-left triangle RayTriIntersect(p11, p01, p10, pos, dir, dist)) // upper-right triangle { isct = pos + dir * dist; return true; } } else { if (RayTriIntersect(p00, p11, p01, pos, dir, dist) || // upper-left triangle RayTriIntersect(p00, p10, p11, pos, dir, dist)) // lower-right triangle { isct = pos + dir * dist; return true; } } return false; } bool CHFTracer::PatchRayIntersect(CPatch* patch, const CVector3D& origin, const CVector3D& dir, CVector3D* out) { // (TODO: This largely duplicates RayIntersect - some refactoring might be // nice in the future.) // General approach: // Given the ray defined by origin + dir * t, we increase t until it // enters the patch's bounding box. The x,z coordinates identify which // tile it is currently above/below. Do an intersection test vs the tile's // two triangles. If it doesn't hit, do a 2D line rasterisation to find // the next tiles the ray will pass through, and test each of them. // Start by jumping to the point where the ray enters the bounding box CBoundingBoxAligned bound = patch->GetWorldBounds(); float tmin, tmax; if (!bound.RayIntersect(origin, dir, tmin, tmax)) { // Ray missed patch; no intersection return false; } int heightmapStride = patch->m_Parent->GetVerticesPerSide(); // Get heightmap, offset to start at this patch u16* heightmap = patch->m_Parent->GetHeightMap() + patch->m_X * PATCH_SIZE + patch->m_Z * PATCH_SIZE * heightmapStride; // Get patch-space position of ray origin and bbox entry point CVector3D patchPos( patch->m_X * PATCH_SIZE * TERRAIN_TILE_SIZE, 0.0f, patch->m_Z * PATCH_SIZE * TERRAIN_TILE_SIZE); CVector3D originPatch = origin - patchPos; CVector3D entryPatch = originPatch + dir * tmin; // We want to do a simple 2D line rasterisation (with the 3D ray projected // down onto the Y plane). That will tell us which cells are intersected // in 2D dimensions, then we can do a more precise 3D intersection test. // // WLOG, assume the ray has direction dir.x > 0, dir.z > 0, and starts in // cell (i,j). The next cell intersecting the line must be either (i+1,j) // or (i,j+1). To tell which, just check whether the point (i+1,j+1) is // above or below the ray. Advance into that cell and repeat. // // (If the ray passes precisely through (i+1,j+1), we can pick either. // If the ray is parallel to Y, only the first cell matters, then we can // carry on rasterising in any direction (a bit of a waste of time but // should be extremely rare, and it's safe and simple).) // Work out which tile we're starting in int i = clamp((int)(entryPatch.X / TERRAIN_TILE_SIZE), 0, (int)PATCH_SIZE-1); int j = clamp((int)(entryPatch.Z / TERRAIN_TILE_SIZE), 0, (int)PATCH_SIZE-1); // Work out which direction the ray is going in int di = (dir.X >= 0 ? 1 : 0); int dj = (dir.Z >= 0 ? 1 : 0); do { CVector3D isct; if (TestTile(heightmap, heightmapStride, i, j, originPatch, dir, isct)) { if (out) *out = isct + patchPos; return true; } // Get the vertex between the two possible next cells float nx = (i + di) * (int)TERRAIN_TILE_SIZE; float nz = (j + dj) * (int)TERRAIN_TILE_SIZE; // Test which side of the ray the vertex is on, and advance into the // appropriate cell, using a test that works for all 4 combinations // of di,dj float dot = dir.Z * (nx - originPatch.X) - dir.X * (nz - originPatch.Z); if ((di == dj) == (dot > 0.0f)) j += dj*2-1; else i += di*2-1; } while (i >= 0 && j >= 0 && i < PATCH_SIZE && j < PATCH_SIZE); // Ran off the edge of the patch, so no intersection return false; } ================================================ FILE: fpsgame/graphics/HFTracer.h ================================================ /* Copyright (C) 2014 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /* * Determine intersection of rays with a heightfield. */ #ifndef INCLUDED_HFTRACER #define INCLUDED_HFTRACER class CPatch; class CVector3D; class CTerrain; /////////////////////////////////////////////////////////////////////////////// // CHFTracer: a class for determining ray intersections with a heightfield class CHFTracer { public: // constructor; setup data CHFTracer(CTerrain *pTerrain); // intersect ray with this heightfield; return true if intersection // occurs (and fill in grid coordinates and point of intersection), or false otherwise bool RayIntersect(const CVector3D& origin, const CVector3D& dir, int& x, int& z, CVector3D& ipt) const; /** * Intersects ray with a single patch. * The ray is a half-infinite line starting at @p origin with direction @p dir * (not required to be a unit vector).. The patch is treated as a collection * of two-sided triangles, corresponding to the terrain tiles. * * If there is an intersection, returns true; and if @p out is not NULL, it * is set to the intersection point. This is guaranteed to be the earliest * tile intersected (starting at @p origin), but not necessarily the earlier * triangle inside that tile. * * This partly duplicates RayIntersect, but it only operates on a single * patch, and it's more precise (it uses the same tile triangulation as the * renderer), and tries to be more numerically robust. */ static bool PatchRayIntersect(CPatch* patch, const CVector3D& origin, const CVector3D& dir, CVector3D* out); private: // test if ray intersects either of the triangles in the given bool CellIntersect(int cx, int cz, const CVector3D& origin, const CVector3D& dir, float& dist) const; // The terrain we're operating on CTerrain *m_pTerrain; // the heightfield were tracing const u16* m_Heightfield; // size of the heightfield size_t m_MapSize; // cell size - size of each cell in x and z float m_CellSize; // vertical scale - size of each cell in y float m_HeightScale; }; #endif ================================================ FILE: fpsgame/graphics/HeightMipmap.cpp ================================================ /* Copyright (C) 2012 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "HeightMipmap.h" #include "lib/bits.h" #include "lib/timer.h" #include "lib/allocators/shared_ptr.h" #include "lib/tex/tex.h" #include "maths/MathUtil.h" #include "ps/Filesystem.h" #include CHeightMipmap::CHeightMipmap() { } CHeightMipmap::~CHeightMipmap() { ReleaseData(); } void CHeightMipmap::ReleaseData() { for (size_t i = 0; i < m_Mipmap.size(); ++i) { delete[] m_Mipmap[i].m_Heightmap; m_Mipmap[i].m_MapSize = 0; } m_Mipmap.clear(); } void CHeightMipmap::Update(const u16* ptr) { ENSURE(ptr != 0); Update(ptr, 0, 0, m_MapSize, m_MapSize); } void CHeightMipmap::Update(const u16* ptr, size_t left, size_t bottom, size_t right, size_t top) { ENSURE(ptr != 0); size_t mapSize = m_MapSize; for (size_t i = 0; i < m_Mipmap.size(); ++i) { // update window left = clamp((size_t)floorf((float)left / mapSize * m_Mipmap[i].m_MapSize), 0, m_Mipmap[i].m_MapSize - 1); bottom = clamp((size_t)floorf((float)bottom / mapSize * m_Mipmap[i].m_MapSize), 0, m_Mipmap[i].m_MapSize - 1); right = clamp((size_t)ceilf((float)right / mapSize * m_Mipmap[i].m_MapSize), 0, m_Mipmap[i].m_MapSize); top = clamp((size_t)ceilf((float)top / mapSize * m_Mipmap[i].m_MapSize), 0, m_Mipmap[i].m_MapSize); // TODO: should verify that the bounds calculations are actually correct // update mipmap BilinearUpdate(m_Mipmap[i], mapSize, ptr, left, bottom, right, top); mapSize = m_Mipmap[i].m_MapSize; ptr = m_Mipmap[i].m_Heightmap; } } void CHeightMipmap::Initialize(size_t mapSize, const u16* ptr) { ENSURE(ptr != 0); ENSURE(mapSize > 0); ReleaseData(); m_MapSize = mapSize; size_t mipmapSize = round_down_to_pow2(mapSize); while (mipmapSize > 1) { m_Mipmap.push_back(SMipmap(mipmapSize, new u16[mipmapSize*mipmapSize])); mipmapSize >>= 1; }; Update(ptr); } float CHeightMipmap::GetTrilinearGroundLevel(float x, float z, float radius) const { float y; if (radius <= 0.0f) // avoid logf of non-positive value y = 0.0f; else y = clamp(logf(radius * m_Mipmap[0].m_MapSize) / logf(2), 0, m_Mipmap.size()); const size_t iy = (size_t)clamp((ssize_t)floorf(y), 0, m_Mipmap.size() - 2); const float fy = y - iy; const float h0 = BilinearFilter(m_Mipmap[iy], x, z); const float h1 = BilinearFilter(m_Mipmap[iy + 1], x, z); return (1 - fy) * h0 + fy * h1; } float CHeightMipmap::BilinearFilter(const SMipmap &mipmap, float x, float z) const { x *= mipmap.m_MapSize; z *= mipmap.m_MapSize; const size_t xi = (size_t)clamp((ssize_t)floor(x), 0, mipmap.m_MapSize - 2); const size_t zi = (size_t)clamp((ssize_t)floor(z), 0, mipmap.m_MapSize - 2); const float xf = clamp(x-xi, 0.0f, 1.0f); const float zf = clamp(z-zi, 0.0f, 1.0f); const float h00 = mipmap.m_Heightmap[zi*mipmap.m_MapSize + xi]; const float h01 = mipmap.m_Heightmap[(zi+1)*mipmap.m_MapSize + xi]; const float h10 = mipmap.m_Heightmap[zi*mipmap.m_MapSize + (xi+1)]; const float h11 = mipmap.m_Heightmap[(zi+1)*mipmap.m_MapSize + (xi+1)]; return (1.f - xf) * (1.f - zf) * h00 + xf * (1.f - zf) * h10 + (1.f - xf) * zf * h01 + xf * zf * h11; } void CHeightMipmap::HalfResizeUpdate(SMipmap &out_mipmap, size_t mapSize, const u16* ptr, size_t left, size_t bottom, size_t right, size_t top) { // specialized, faster version of BilinearUpdate for powers of 2 ENSURE(out_mipmap.m_MapSize != 0); if (out_mipmap.m_MapSize * 2 != mapSize) debug_warn(L"wrong size"); // valid update window ENSURE(left < out_mipmap.m_MapSize); ENSURE(bottom < out_mipmap.m_MapSize); ENSURE(right > left && right <= out_mipmap.m_MapSize); ENSURE(top > bottom && top <= out_mipmap.m_MapSize); for (size_t dstZ = bottom; dstZ < top; ++dstZ) { for (size_t dstX = left; dstX < right; ++dstX) { size_t srcX = dstX << 1; size_t srcZ = dstZ << 1; u16 h00 = ptr[srcX + 0 + srcZ * mapSize]; u16 h10 = ptr[srcX + 1 + srcZ * mapSize]; u16 h01 = ptr[srcX + 0 + (srcZ + 1) * mapSize]; u16 h11 = ptr[srcX + 1 + (srcZ + 1) * mapSize]; out_mipmap.m_Heightmap[dstX + dstZ * out_mipmap.m_MapSize] = (h00 + h10 + h01 + h11) / 4; } } } void CHeightMipmap::BilinearUpdate(SMipmap &out_mipmap, size_t mapSize, const u16* ptr, size_t left, size_t bottom, size_t right, size_t top) { ENSURE(out_mipmap.m_MapSize != 0); // filter should have full coverage ENSURE(out_mipmap.m_MapSize <= mapSize && out_mipmap.m_MapSize * 2 >= mapSize); // valid update window ENSURE(left < out_mipmap.m_MapSize); ENSURE(bottom < out_mipmap.m_MapSize); ENSURE(right > left && right <= out_mipmap.m_MapSize); ENSURE(top > bottom && top <= out_mipmap.m_MapSize); if (out_mipmap.m_MapSize * 2 == mapSize) { // optimized for powers of 2 HalfResizeUpdate(out_mipmap, mapSize, ptr, left, bottom, right, top); } else { for (size_t dstZ = bottom; dstZ < top; ++dstZ) { for (size_t dstX = left; dstX < right; ++dstX) { const float x = ((float)dstX / (float)out_mipmap.m_MapSize) * mapSize; const float z = ((float)dstZ / (float)out_mipmap.m_MapSize) * mapSize; const size_t srcX = clamp((size_t)x, 0, mapSize - 2); const size_t srcZ = clamp((size_t)z, 0, mapSize - 2); const float fx = clamp(x - srcX, 0.0f, 1.0f); const float fz = clamp(z - srcZ, 0.0f, 1.0f); const float h00 = ptr[srcX + 0 + srcZ * mapSize]; const float h10 = ptr[srcX + 1 + srcZ * mapSize]; const float h01 = ptr[srcX + 0 + (srcZ + 1) * mapSize]; const float h11 = ptr[srcX + 1 + (srcZ + 1) * mapSize]; out_mipmap.m_Heightmap[dstX + dstZ * out_mipmap.m_MapSize] = (u16) ((1.f - fx) * (1.f - fz) * h00 + fx * (1.f - fz) * h10 + (1.f - fx) * fz * h01 + fx * fz * h11); } } } } void CHeightMipmap::DumpToDisk(const VfsPath& filename) const { const size_t w = m_MapSize; const size_t h = m_MapSize * 2; const size_t bpp = 8; int flags = TEX_GREY|TEX_TOP_DOWN; const size_t img_size = w * h * bpp/8; const size_t hdr_size = tex_hdr_size(filename); shared_ptr buf; AllocateAligned(buf, hdr_size+img_size, maxSectorSize); void* img = buf.get() + hdr_size; Tex t; WARN_IF_ERR(t.wrap(w, h, bpp, flags, buf, hdr_size)); memset(img, 0x00, img_size); size_t yoff = 0; for (size_t i = 0; i < m_Mipmap.size(); ++i) { size_t size = m_Mipmap[i].m_MapSize; u16* heightmap = m_Mipmap[i].m_Heightmap; ENSURE(size+yoff <= h); for (size_t y = 0; y < size; ++y) { for (size_t x = 0; x < size; ++x) { u16 val = heightmap[x + y*size]; ((u8*)img)[x + (y+yoff)*w] = val >> 8; } } yoff += size; } DynArray da; WARN_IF_ERR(t.encode(filename.Extension(), &da)); g_VFS->CreateFile(filename, DummySharedPtr(da.base), da.pos); (void)da_free(&da); } ================================================ FILE: fpsgame/graphics/HeightMipmap.h ================================================ /* Copyright (C) 2012 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /* * Describes ground using heightmap mipmaps * Used for camera movement */ #ifndef INCLUDED_HEIGHTMIPMAP #define INCLUDED_HEIGHTMIPMAP #include "lib/file/vfs/vfs_path.h" struct SMipmap { SMipmap() : m_MapSize(0), m_Heightmap(0) { } SMipmap(size_t MapSize, u16* Heightmap) : m_MapSize(MapSize), m_Heightmap(Heightmap) { } size_t m_MapSize; u16* m_Heightmap; }; class CHeightMipmap { NONCOPYABLE(CHeightMipmap); public: CHeightMipmap(); ~CHeightMipmap(); void Initialize(size_t mapSize, const u16* ptr); void ReleaseData(); // update the heightmap mipmaps void Update(const u16* ptr); // update a section of the heightmap mipmaps // (coordinates are heightmap cells, inclusive of lower bounds, // exclusive of upper bounds) void Update(const u16* ptr, size_t left, size_t bottom, size_t right, size_t top); float GetTrilinearGroundLevel(float x, float z, float radius) const; void DumpToDisk(const VfsPath& path) const; private: // get bilinear filtered height from mipmap float BilinearFilter(const SMipmap &mipmap, float x, float z) const; // update rectangle of the output mipmap by bilinear interpolating an input mipmap of exactly twice its size void HalfResizeUpdate(SMipmap &out_mipmap, size_t mapSize, const u16* ptr, size_t left, size_t bottom, size_t right, size_t top); // update rectangle of the output mipmap by bilinear interpolating the input mipmap void BilinearUpdate(SMipmap &out_mipmap, size_t mapSize, const u16* ptr, size_t left, size_t bottom, size_t right, size_t top); // size of this map in each direction size_t m_MapSize; // mipmap list std::vector m_Mipmap; }; #endif ================================================ FILE: fpsgame/graphics/LOSTexture.cpp ================================================ /* Copyright (C) 2014 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "LOSTexture.h" #include "graphics/ShaderManager.h" #include "graphics/Terrain.h" #include "lib/bits.h" #include "lib/config2.h" #include "ps/CLogger.h" #include "ps/Game.h" #include "ps/Profile.h" #include "renderer/Renderer.h" #include "renderer/TimeManager.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpRangeManager.h" #include "simulation2/components/ICmpTerrain.h" /* The LOS bitmap is computed with one value per map vertex, based on CCmpRangeManager's visibility information. The bitmap is then blurred using an NxN filter (in particular a 7-tap Binomial filter as an efficient integral approximation of a Gaussian). To implement the blur efficiently without using extra memory for a second copy of the bitmap, we generate the bitmap with (N-1)/2 pixels of padding on each side, then the blur shifts the image back into the corner. The blurred bitmap is then uploaded into a GL texture for use by the renderer. */ // Blur with a NxN filter, where N = g_BlurSize must be an odd number. static const size_t g_BlurSize = 7; // Alignment (in bytes) of the pixel data passed into glTexSubImage2D. // This must be a multiple of GL_UNPACK_ALIGNMENT, which ought to be 1 (since // that's what we set it to) but in some weird cases appears to have a different // value. (See Trac #2594). Multiples of 4 are possibly good for performance anyway. static const size_t g_SubTextureAlignment = 4; CLOSTexture::CLOSTexture(CSimulation2& simulation) : m_Simulation(simulation), m_Dirty(true), m_ShaderInitialized(false), m_Texture(0), m_TextureSmooth1(0), m_TextureSmooth2(0), m_smoothFbo(0), m_MapSize(0), m_TextureSize(0), whichTex(true) { if (CRenderer::IsInitialised() && g_Renderer.m_Options.m_SmoothLOS) CreateShader(); } CLOSTexture::~CLOSTexture() { if (m_Texture) DeleteTexture(); } // Create the LOS texture engine. Should be ran only once. bool CLOSTexture::CreateShader() { m_smoothShader = g_Renderer.GetShaderManager().LoadEffect(str_los_interp); CShaderProgramPtr shader = m_smoothShader->GetShader(); m_ShaderInitialized = m_smoothShader && shader; if (!m_ShaderInitialized) { LOGERROR("Failed to load SmoothLOS shader, disabling."); g_Renderer.m_Options.m_SmoothLOS = false; return false; } pglGenFramebuffersEXT(1, &m_smoothFbo); return true; } void CLOSTexture::DeleteTexture() { glDeleteTextures(1, &m_Texture); if (m_TextureSmooth1) glDeleteTextures(1, &m_TextureSmooth1); if (m_TextureSmooth2) glDeleteTextures(1, &m_TextureSmooth2); m_Texture = 0; m_TextureSmooth1 = 0; m_TextureSmooth2 = 0; } void CLOSTexture::MakeDirty() { m_Dirty = true; } void CLOSTexture::BindTexture(int unit) { if (m_Dirty) { RecomputeTexture(unit); m_Dirty = false; } g_Renderer.BindTexture(unit, m_Texture); } GLuint CLOSTexture::GetTextureSmooth() { if (CRenderer::IsInitialised() && !g_Renderer.m_Options.m_SmoothLOS) return GetTexture(); else return whichTex ? m_TextureSmooth1 : m_TextureSmooth2; } void CLOSTexture::InterpolateLOS() { if (CRenderer::IsInitialised() && !g_Renderer.m_Options.m_SmoothLOS) return; if (!m_ShaderInitialized) { if (!CreateShader()) return; // RecomputeTexture(0) will not cause the ConstructTexture to run. // Force the textures to be created. DeleteTexture(); ConstructTexture(0); m_Dirty = true; } if (m_Dirty) { RecomputeTexture(0); m_Dirty = false; } GLint originalFBO; glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &originalFBO); pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_smoothFbo); pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, whichTex ? m_TextureSmooth2 : m_TextureSmooth1, 0); GLenum status = pglCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); if (status != GL_FRAMEBUFFER_COMPLETE_EXT) { LOGWARNING("LOS framebuffer object incomplete: 0x%04X", status); } m_smoothShader->BeginPass(); CShaderProgramPtr shader = m_smoothShader->GetShader(); glDisable(GL_BLEND); shader->Bind(); shader->BindTexture(str_losTex1, m_Texture); shader->BindTexture(str_losTex2, whichTex ? m_TextureSmooth1 : m_TextureSmooth2); shader->Uniform(str_delta, (float)g_Renderer.GetTimeManager().GetFrameDelta() * 4.0f, 0.0f, 0.0f, 0.0f); const SViewPort oldVp = g_Renderer.GetViewport(); const SViewPort vp = { 0, 0, m_TextureSize, m_TextureSize }; g_Renderer.SetViewport(vp); float quadVerts[] = { 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f }; float quadTex[] = { 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f }; shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 0, quadTex); shader->VertexPointer(2, GL_FLOAT, 0, quadVerts); shader->AssertPointersBound(); glDrawArrays(GL_TRIANGLES, 0, 6); g_Renderer.SetViewport(oldVp); shader->Unbind(); m_smoothShader->EndPass(); pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, 0, 0); pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, originalFBO); whichTex = !whichTex; } GLuint CLOSTexture::GetTexture() { if (m_Dirty) { RecomputeTexture(0); m_Dirty = false; } return m_Texture; } const CMatrix3D& CLOSTexture::GetTextureMatrix() { ENSURE(!m_Dirty); return m_TextureMatrix; } const CMatrix3D* CLOSTexture::GetMinimapTextureMatrix() { ENSURE(!m_Dirty); return &m_MinimapTextureMatrix; } void CLOSTexture::ConstructTexture(int unit) { CmpPtr cmpTerrain(m_Simulation, SYSTEM_ENTITY); if (!cmpTerrain) return; m_MapSize = cmpTerrain->GetVerticesPerSide(); m_TextureSize = (GLsizei)round_up_to_pow2(round_up((size_t)m_MapSize + g_BlurSize - 1, g_SubTextureAlignment)); glGenTextures(1, &m_Texture); // Initialise texture with SoD color, for the areas we don't // overwrite with glTexSubImage2D later u8* texData = new u8[m_TextureSize * m_TextureSize * 4]; memset(texData, 0x00, m_TextureSize * m_TextureSize * 4); if (CRenderer::IsInitialised() && g_Renderer.m_Options.m_SmoothLOS) { glGenTextures(1, &m_TextureSmooth1); glGenTextures(1, &m_TextureSmooth2); g_Renderer.BindTexture(unit, m_TextureSmooth1); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_TextureSize, m_TextureSize, 0, GL_ALPHA, GL_UNSIGNED_BYTE, texData); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); g_Renderer.BindTexture(unit, m_TextureSmooth2); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_TextureSize, m_TextureSize, 0, GL_ALPHA, GL_UNSIGNED_BYTE, texData); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); } g_Renderer.BindTexture(unit, m_Texture); glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, m_TextureSize, m_TextureSize, 0, GL_ALPHA, GL_UNSIGNED_BYTE, texData); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); delete[] texData; { // Texture matrix: We want to map // world pos (0, y, 0) (i.e. first vertex) // onto texcoord (0.5/texsize, 0.5/texsize) (i.e. middle of first texel); // world pos ((mapsize-1)*cellsize, y, (mapsize-1)*cellsize) (i.e. last vertex) // onto texcoord ((mapsize-0.5) / texsize, (mapsize-0.5) / texsize) (i.e. middle of last texel) float s = (m_MapSize-1) / (float)(m_TextureSize * (m_MapSize-1) * TERRAIN_TILE_SIZE); float t = 0.5f / m_TextureSize; m_TextureMatrix.SetZero(); m_TextureMatrix._11 = s; m_TextureMatrix._23 = s; m_TextureMatrix._14 = t; m_TextureMatrix._24 = t; m_TextureMatrix._44 = 1; } { // Minimap matrix: We want to map UV (0,0)-(1,1) onto (0,0)-(mapsize/texsize, mapsize/texsize) float s = m_MapSize / (float)m_TextureSize; m_MinimapTextureMatrix.SetZero(); m_MinimapTextureMatrix._11 = s; m_MinimapTextureMatrix._22 = s; m_MinimapTextureMatrix._44 = 1; } } void CLOSTexture::RecomputeTexture(int unit) { // If the map was resized, delete and regenerate the texture if (m_Texture) { CmpPtr cmpTerrain(m_Simulation, SYSTEM_ENTITY); if (cmpTerrain && m_MapSize != (ssize_t)cmpTerrain->GetVerticesPerSide()) DeleteTexture(); } bool recreated = false; if (!m_Texture) { ConstructTexture(unit); recreated = true; } PROFILE("recompute LOS texture"); std::vector losData; size_t pitch; losData.resize(GetBitmapSize(m_MapSize, m_MapSize, &pitch)); CmpPtr cmpRangeManager(m_Simulation, SYSTEM_ENTITY); if (!cmpRangeManager) return; ICmpRangeManager::CLosQuerier los(cmpRangeManager->GetLosQuerier(g_Game->GetSimulation2()->GetSimContext().GetCurrentDisplayedPlayer())); GenerateBitmap(los, &losData[0], m_MapSize, m_MapSize, pitch); if (CRenderer::IsInitialised() && g_Renderer.m_Options.m_SmoothLOS && recreated) { g_Renderer.BindTexture(unit, m_TextureSmooth1); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, pitch, m_MapSize, GL_ALPHA, GL_UNSIGNED_BYTE, &losData[0]); g_Renderer.BindTexture(unit, m_TextureSmooth2); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, pitch, m_MapSize, GL_ALPHA, GL_UNSIGNED_BYTE, &losData[0]); } g_Renderer.BindTexture(unit, m_Texture); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, pitch, m_MapSize, GL_ALPHA, GL_UNSIGNED_BYTE, &losData[0]); } size_t CLOSTexture::GetBitmapSize(size_t w, size_t h, size_t* pitch) { *pitch = round_up(w + g_BlurSize - 1, g_SubTextureAlignment); return *pitch * (h + g_BlurSize - 1); } void CLOSTexture::GenerateBitmap(ICmpRangeManager::CLosQuerier los, u8* losData, size_t w, size_t h, size_t pitch) { u8 *dataPtr = losData; // Initialise the top padding for (size_t j = 0; j < g_BlurSize/2; ++j) for (size_t i = 0; i < pitch; ++i) *dataPtr++ = 0; for (size_t j = 0; j < h; ++j) { // Initialise the left padding for (size_t i = 0; i < g_BlurSize/2; ++i) *dataPtr++ = 0; // Fill in the visibility data for (size_t i = 0; i < w; ++i) { if (los.IsVisible_UncheckedRange(i, j)) *dataPtr++ = 255; else if (los.IsExplored_UncheckedRange(i, j)) *dataPtr++ = 127; else *dataPtr++ = 0; } // Initialise the right padding for (size_t i = 0; i < pitch - w - g_BlurSize/2; ++i) *dataPtr++ = 0; } // Initialise the bottom padding for (size_t j = 0; j < g_BlurSize/2; ++j) for (size_t i = 0; i < pitch; ++i) *dataPtr++ = 0; // Horizontal blur: for (size_t j = g_BlurSize/2; j < h + g_BlurSize/2; ++j) { for (size_t i = 0; i < w; ++i) { u8* d = &losData[i+j*pitch]; *d = ( 1*d[0] + 6*d[1] + 15*d[2] + 20*d[3] + 15*d[4] + 6*d[5] + 1*d[6] ) / 64; } } // Vertical blur: for (size_t j = 0; j < h; ++j) { for (size_t i = 0; i < w; ++i) { u8* d = &losData[i+j*pitch]; *d = ( 1*d[0*pitch] + 6*d[1*pitch] + 15*d[2*pitch] + 20*d[3*pitch] + 15*d[4*pitch] + 6*d[5*pitch] + 1*d[6*pitch] ) / 64; } } } ================================================ FILE: fpsgame/graphics/LOSTexture.h ================================================ /* Copyright (C) 2014 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "lib/ogl.h" #include "maths/Matrix3D.h" #include "simulation2/components/ICmpRangeManager.h" #include "graphics/ShaderManager.h" class CSimulation2; /** * Maintains the LOS (fog-of-war / shroud-of-darkness) texture, used for * rendering and for the minimap. */ class CLOSTexture { NONCOPYABLE(CLOSTexture); friend class TestLOSTexture; public: CLOSTexture(CSimulation2& simulation); ~CLOSTexture(); /** * Marks the LOS texture as needing recomputation. Call this after each * simulation update, to ensure responsive updates. */ void MakeDirty(); /** * Recomputes the LOS texture if necessary, and binds it to the requested * texture unit. * Also switches the current active texture unit, and enables texturing on it. * The texture is in 8-bit ALPHA format. */ void BindTexture(int unit); /** * Recomputes the LOS texture if necessary, and returns the texture handle. * Also potentially switches the current active texture unit, and enables texturing on it. * The texture is in 8-bit ALPHA format. */ GLuint GetTexture(); void InterpolateLOS(); GLuint GetTextureSmooth(); /** * Returns a matrix to map (x,y,z) world coordinates onto (u,v) LOS texture * coordinates, in the form expected by glLoadMatrixf. * This must only be called after BindTexture. */ const CMatrix3D& GetTextureMatrix(); /** * Returns a matrix to map (0,0)-(1,1) texture coordinates onto LOS texture * coordinates, in the form expected by glLoadMatrixf. * This must only be called after BindTexture. */ const CMatrix3D* GetMinimapTextureMatrix(); private: void DeleteTexture(); bool CreateShader(); void ConstructTexture(int unit); void RecomputeTexture(int unit); size_t GetBitmapSize(size_t w, size_t h, size_t* pitch); void GenerateBitmap(ICmpRangeManager::CLosQuerier los, u8* losData, size_t w, size_t h, size_t pitch); CSimulation2& m_Simulation; bool m_Dirty; bool m_ShaderInitialized; GLuint m_Texture; GLuint m_TextureSmooth1, m_TextureSmooth2; bool whichTex; GLuint m_smoothFbo; CShaderTechniquePtr m_smoothShader; ssize_t m_MapSize; // vertexes per side GLsizei m_TextureSize; // texels per side CMatrix3D m_TextureMatrix; CMatrix3D m_MinimapTextureMatrix; }; ================================================ FILE: fpsgame/graphics/LightEnv.cpp ================================================ /* Copyright (C) 2012 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /* * CLightEnv implementation */ #include "precompiled.h" #include "maths/MathUtil.h" #include "graphics/LightEnv.h" CLightEnv::CLightEnv() : m_Elevation(DEGTORAD(45)), m_Rotation(DEGTORAD(315)), m_LightingModel("standard"), m_SunColor(1.5, 1.5, 1.5), m_TerrainAmbientColor(0x50/255.f, 0x60/255.f, 0x85/255.f), m_UnitsAmbientColor(0x80/255.f, 0x80/255.f, 0x80/255.f), m_FogColor(0xCC/255.f, 0xCC/255.f, 0xE5/255.f), m_FogFactor(0.000f), m_FogMax(0.5f), m_Brightness(0.0f), m_Contrast(1.0f), m_Saturation(0.99f), m_Bloom(0.1999f) { CalculateSunDirection(); } void CLightEnv::SetElevation(float f) { m_Elevation = f; CalculateSunDirection(); } void CLightEnv::SetRotation(float f) { m_Rotation = f; CalculateSunDirection(); } void CLightEnv::CalculateSunDirection() { m_SunDir.Y = -sinf(m_Elevation); float scale = 1 + m_SunDir.Y; m_SunDir.X = scale * sinf(m_Rotation); m_SunDir.Z = scale * cosf(m_Rotation); m_SunDir.Normalize(); } ================================================ FILE: fpsgame/graphics/LightEnv.h ================================================ /* Copyright (C) 2012 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /* * CLightEnv, a class describing the current lights */ #ifndef INCLUDED_LIGHTENV #define INCLUDED_LIGHTENV #include "graphics/Color.h" #include "maths/MathUtil.h" #include "maths/Vector3D.h" class CMapWriter; class CMapReader; /** * Class CLightEnv: description of a lighting environment - contains all the * necessary parameters for representation of the lighting within a scenario */ class CLightEnv { friend class CMapWriter; friend class CMapReader; friend class CXMLReader; private: /** * Height of sun above the horizon, in radians. * For example, an elevation of M_PI/2 means the sun is straight up. */ float m_Elevation; /** * Direction of sun on the compass, in radians. * For example, a rotation of zero means the sun is in the direction (0,0,-1) * and a rotation of M_PI/2 means the sun is in the direction (1,0,0) (not taking * elevation into account). */ float m_Rotation; /** * Vector corresponding to m_Elevation and m_Rotation. * Updated by CalculateSunDirection. */ CVector3D m_SunDir; /** * A string that shaders use to determine what lighting model to implement. * Current recognised values are "old" and "standard". */ std::string m_LightingModel; public: RGBColor m_SunColor; RGBColor m_TerrainAmbientColor; RGBColor m_UnitsAmbientColor; RGBColor m_FogColor; float m_FogFactor; float m_FogMax; float m_Brightness, m_Contrast, m_Saturation, m_Bloom; public: CLightEnv(); float GetElevation() const { return m_Elevation; } float GetRotation() const { return m_Rotation; } const CVector3D& GetSunDir() const { return m_SunDir; } const std::string& GetLightingModel() const { return m_LightingModel; } void SetElevation(float f); void SetRotation(float f); void SetLightingModel(const std::string& model) { m_LightingModel = model; } /** * Calculate brightness of a point of a unit with the given normal vector, * for rendering with CPU lighting. * The resulting color contains both ambient and diffuse light. * To cope with sun overbrightness, the color is scaled by 0.5. * * @param normal normal vector (must have length 1) */ RGBColor EvaluateUnitScaled(const CVector3D& normal) const { float dot = -normal.Dot(m_SunDir); RGBColor color = m_UnitsAmbientColor; if (dot > 0) color += m_SunColor * dot; return color * 0.5f; } /** * Compute the diffuse sun lighting color on terrain, for rendering with CPU lighting. * To cope with sun overbrightness, the color is scaled by 0.5. * * @param normal normal vector (must have length 1) */ SColor4ub EvaluateTerrainDiffuseScaled(const CVector3D& normal) const { float dot = -normal.Dot(m_SunDir); return ConvertRGBColorTo4ub(m_SunColor * dot * 0.5f); } /** * Compute the diffuse sun lighting factor on terrain, for rendering with shader lighting. * * @param normal normal vector (must have length 1) */ SColor4ub EvaluateTerrainDiffuseFactor(const CVector3D& normal) const { float dot = -normal.Dot(m_SunDir); int c = clamp((int)(dot * 255), 0, 255); return SColor4ub(c, c, c, 255); } // Comparison operators bool operator==(const CLightEnv& o) const { return m_Elevation == o.m_Elevation && m_Rotation == o.m_Rotation && m_LightingModel == o.m_LightingModel && m_SunColor == o.m_SunColor && m_TerrainAmbientColor == o.m_TerrainAmbientColor && m_UnitsAmbientColor == o.m_UnitsAmbientColor && m_FogColor == o.m_FogColor && m_FogFactor == o.m_FogFactor && m_FogMax == o.m_FogMax && m_Brightness == o.m_Brightness && m_Contrast == o.m_Contrast && m_Saturation == o.m_Saturation && m_Bloom == o.m_Bloom; } bool operator!=(const CLightEnv& o) const { return !(*this == o); } private: void CalculateSunDirection(); }; #endif // INCLUDED_LIGHTENV ================================================ FILE: fpsgame/graphics/MapGenerator.cpp ================================================ /* Copyright (C) 2015 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "MapGenerator.h" #include "lib/timer.h" #include "ps/CLogger.h" #include "ps/Profile.h" // TODO: what's a good default? perhaps based on map size #define RMS_RUNTIME_SIZE 96 * 1024 * 1024 CMapGeneratorWorker::CMapGeneratorWorker() { // If something happens before we initialize, that's a failure m_Progress = -1; } CMapGeneratorWorker::~CMapGeneratorWorker() { // Wait for thread to end pthread_join(m_WorkerThread, NULL); } void CMapGeneratorWorker::Initialize(const VfsPath& scriptFile, const std::string& settings) { CScopeLock lock(m_WorkerMutex); // Set progress to positive value m_Progress = 1; m_ScriptPath = scriptFile; m_Settings = settings; // Launch the worker thread int ret = pthread_create(&m_WorkerThread, NULL, &RunThread, this); ENSURE(ret == 0); } void* CMapGeneratorWorker::RunThread(void *data) { debug_SetThreadName("MapGenerator"); g_Profiler2.RegisterCurrentThread("MapGenerator"); CMapGeneratorWorker* self = static_cast(data); self->m_ScriptInterface = new ScriptInterface("RMS", "MapGenerator", ScriptInterface::CreateRuntime(g_ScriptRuntime, RMS_RUNTIME_SIZE)); // Run map generation scripts if (!self->Run() || self->m_Progress > 0) { // Don't leave progress in an unknown state, if generator failed, set it to -1 CScopeLock lock(self->m_WorkerMutex); self->m_Progress = -1; } // At this point the random map scripts are done running, so the thread has no further purpose // and can die. The data will be stored in m_MapData already if successful, or m_Progress // will contain an error value on failure. return NULL; } bool CMapGeneratorWorker::Run() { // We must destroy the ScriptInterface in the same thread because the JSAPI requires that! // Also we must not be in a request when calling the ScriptInterface destructor, so the autoFree object // must be instantiated before the request (destructors are called in reverse order of instantiation) struct AutoFree { AutoFree(ScriptInterface* p) : m_p(p) {} ~AutoFree() { SAFE_DELETE(m_p); } ScriptInterface* m_p; } autoFree(m_ScriptInterface); JSContext* cx = m_ScriptInterface->GetContext(); JSAutoRequest rq(cx); m_ScriptInterface->SetCallbackData(static_cast (this)); // Replace RNG with a seeded deterministic function m_ScriptInterface->ReplaceNondeterministicRNG(m_MapGenRNG); m_ScriptInterface->LoadGlobalScripts(); // Functions for RMS m_ScriptInterface->RegisterFunction("LoadLibrary"); m_ScriptInterface->RegisterFunction("ExportMap"); m_ScriptInterface->RegisterFunction("SetProgress"); m_ScriptInterface->RegisterFunction("MaybeGC"); m_ScriptInterface->RegisterFunction, CMapGeneratorWorker::GetCivData>("GetCivData"); m_ScriptInterface->RegisterFunction("GetTemplate"); m_ScriptInterface->RegisterFunction("TemplateExists"); m_ScriptInterface->RegisterFunction, std::string, bool, CMapGeneratorWorker::FindTemplates>("FindTemplates"); m_ScriptInterface->RegisterFunction, std::string, bool, CMapGeneratorWorker::FindActorTemplates>("FindActorTemplates"); // Parse settings JS::RootedValue settingsVal(cx); if (!m_ScriptInterface->ParseJSON(m_Settings, &settingsVal) && settingsVal.isUndefined()) { LOGERROR("CMapGeneratorWorker::Run: Failed to parse settings"); return false; } // Init RNG seed u32 seed; if (!m_ScriptInterface->GetProperty(settingsVal, "Seed", seed)) { // No seed specified LOGWARNING("CMapGeneratorWorker::Run: No seed value specified - using 0"); seed = 0; } m_MapGenRNG.seed(seed); // Copy settings to global variable JS::RootedValue global(cx, m_ScriptInterface->GetGlobalObject()); if (!m_ScriptInterface->SetProperty(global, "g_MapSettings", settingsVal)) { LOGERROR("CMapGeneratorWorker::Run: Failed to define g_MapSettings"); return false; } // Load RMS LOGMESSAGE("Loading RMS '%s'", m_ScriptPath.string8()); if (!m_ScriptInterface->LoadGlobalScriptFile(m_ScriptPath)) { LOGERROR("CMapGeneratorWorker::Run: Failed to load RMS '%s'", m_ScriptPath.string8()); return false; } return true; } int CMapGeneratorWorker::GetProgress() { CScopeLock lock(m_WorkerMutex); return m_Progress; } shared_ptr CMapGeneratorWorker::GetResults() { CScopeLock lock(m_WorkerMutex); return m_MapData; } bool CMapGeneratorWorker::LoadLibrary(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& name) { CMapGeneratorWorker* self = static_cast(pCxPrivate->pCBData); return self->LoadScripts(name); } void CMapGeneratorWorker::ExportMap(ScriptInterface::CxPrivate* pCxPrivate, JS::HandleValue data) { CMapGeneratorWorker* self = static_cast(pCxPrivate->pCBData); // Copy results CScopeLock lock(self->m_WorkerMutex); self->m_MapData = self->m_ScriptInterface->WriteStructuredClone(data); self->m_Progress = 0; } void CMapGeneratorWorker::SetProgress(ScriptInterface::CxPrivate* pCxPrivate, int progress) { CMapGeneratorWorker* self = static_cast(pCxPrivate->pCBData); // Copy data CScopeLock lock(self->m_WorkerMutex); self->m_Progress = progress; } void CMapGeneratorWorker::MaybeGC(ScriptInterface::CxPrivate* pCxPrivate) { CMapGeneratorWorker* self = static_cast(pCxPrivate->pCBData); self->m_ScriptInterface->MaybeGC(); } std::vector CMapGeneratorWorker::GetCivData(ScriptInterface::CxPrivate* UNUSED(pCxPrivate)) { VfsPath path(L"simulation/data/civs/"); VfsPaths pathnames; std::vector data; // Load all JSON files in civs directory Status ret = vfs::GetPathnames(g_VFS, path, L"*.json", pathnames); if (ret == INFO::OK) { for (const VfsPath& p : pathnames) { // Load JSON file CVFSFile file; PSRETURN ret = file.Load(g_VFS, p); if (ret != PSRETURN_OK) LOGERROR("CMapGeneratorWorker::GetCivData: Failed to load file '%s': %s", p.string8(), GetErrorString(ret)); else data.push_back(file.DecodeUTF8()); // assume it's UTF-8 } } else { // Some error reading directory wchar_t error[200]; LOGERROR("CMapGeneratorWorker::GetCivData: Error reading directory '%s': %s", path.string8(), utf8_from_wstring(StatusDescription(ret, error, ARRAY_SIZE(error)))); } return data; } CParamNode CMapGeneratorWorker::GetTemplate(ScriptInterface::CxPrivate* pCxPrivate, const std::string& templateName) { CMapGeneratorWorker* self = static_cast(pCxPrivate->pCBData); const CParamNode& templateRoot = self->m_TemplateLoader.GetTemplateFileData(templateName).GetChild("Entity"); if (!templateRoot.IsOk()) LOGERROR("Invalid template found for '%s'", templateName.c_str()); return templateRoot; } bool CMapGeneratorWorker::TemplateExists(ScriptInterface::CxPrivate* pCxPrivate, const std::string& templateName) { CMapGeneratorWorker* self = static_cast(pCxPrivate->pCBData); return self->m_TemplateLoader.TemplateExists(templateName); } std::vector CMapGeneratorWorker::FindTemplates(ScriptInterface::CxPrivate* pCxPrivate, const std::string& path, bool includeSubdirectories) { CMapGeneratorWorker* self = static_cast(pCxPrivate->pCBData); return self->m_TemplateLoader.FindTemplates(path, includeSubdirectories, SIMULATION_TEMPLATES); } std::vector CMapGeneratorWorker::FindActorTemplates(ScriptInterface::CxPrivate* pCxPrivate, const std::string& path, bool includeSubdirectories) { CMapGeneratorWorker* self = static_cast(pCxPrivate->pCBData); return self->m_TemplateLoader.FindTemplates(path, includeSubdirectories, ACTOR_TEMPLATES); } bool CMapGeneratorWorker::LoadScripts(const std::wstring& libraryName) { // Ignore libraries that are already loaded if (m_LoadedLibraries.find(libraryName) != m_LoadedLibraries.end()) return true; // Mark this as loaded, to prevent it recursively loading itself m_LoadedLibraries.insert(libraryName); VfsPath path = L"maps/random/" + libraryName + L"/"; VfsPaths pathnames; // Load all scripts in mapgen directory Status ret = vfs::GetPathnames(g_VFS, path, L"*.js", pathnames); if (ret == INFO::OK) { for (const VfsPath& p : pathnames) { LOGMESSAGE("Loading map generator script '%s'", p.string8()); if (!m_ScriptInterface->LoadGlobalScriptFile(p)) { LOGERROR("CMapGeneratorWorker::LoadScripts: Failed to load script '%s'", p.string8()); return false; } } } else { // Some error reading directory wchar_t error[200]; LOGERROR("CMapGeneratorWorker::LoadScripts: Error reading scripts in directory '%s': %s", path.string8(), utf8_from_wstring(StatusDescription(ret, error, ARRAY_SIZE(error)))); return false; } return true; } ////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////// CMapGenerator::CMapGenerator() : m_Worker(new CMapGeneratorWorker()) { } CMapGenerator::~CMapGenerator() { delete m_Worker; } void CMapGenerator::GenerateMap(const VfsPath& scriptFile, const std::string& settings) { m_Worker->Initialize(scriptFile, settings); } int CMapGenerator::GetProgress() { return m_Worker->GetProgress(); } shared_ptr CMapGenerator::GetResults() { return m_Worker->GetResults(); } ================================================ FILE: fpsgame/graphics/MapGenerator.h ================================================ /* Copyright (C) 2015 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_MAPGENERATOR #define INCLUDED_MAPGENERATOR #include "ps/FileIo.h" #include "ps/ThreadUtil.h" #include "ps/TemplateLoader.h" #include "scriptinterface/ScriptInterface.h" #include #include class CMapGeneratorWorker; /** * Random map generator interface. Initialized by CMapReader and then checked * periodically during loading, until it's finished (progress value is 0). * * The actual work is performed by CMapGeneratorWorker in a separate thread. */ class CMapGenerator { NONCOPYABLE(CMapGenerator); public: CMapGenerator(); ~CMapGenerator(); /** * Start the map generator thread * * @param scriptFile The VFS path for the script, e.g. "maps/random/latium.js" * @param settings JSON string containing settings for the map generator */ void GenerateMap(const VfsPath& scriptFile, const std::string& settings); /** * Get status of the map generator thread * * @return Progress percentage 1-100 if active, 0 when finished, or -1 on error */ int GetProgress(); /** * Get random map data, according to this format: * http://trac.wildfiregames.com/wiki/Random_Map_Generator_Internals#Dataformat * * @return StructuredClone containing map data */ shared_ptr GetResults(); private: CMapGeneratorWorker* m_Worker; }; /** * Random map generator worker thread. * (This is run in a thread so that the GUI remains responsive while loading) * * Thread-safety: * - Initialize and constructor/destructor must be called from the main thread. * - ScriptInterface created and destroyed by thread * - StructuredClone used to return JS map data - jsvals can't be used across threads/runtimes */ class CMapGeneratorWorker { public: CMapGeneratorWorker(); ~CMapGeneratorWorker(); /** * Start the map generator thread * * @param scriptFile The VFS path for the script, e.g. "maps/random/latium.js" * @param settings JSON string containing settings for the map generator */ void Initialize(const VfsPath& scriptFile, const std::string& settings); /** * Get status of the map generator thread * * @return Progress percentage 1-100 if active, 0 when finished, or -1 on error */ int GetProgress(); /** * Get random map data, according to this format: * http://trac.wildfiregames.com/wiki/Random_Map_Generator_Internals#Dataformat * * @return StructuredClone containing map data */ shared_ptr GetResults(); private: // Mapgen /** * Load all scripts of the given library * * @param libraryName String specifying name of the library (subfolder of ../maps/random/) * @return true if all scripts ran successfully, false if there's an error */ bool LoadScripts(const std::wstring& libraryName); // callbacks for script functions static bool LoadLibrary(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& name); static void ExportMap(ScriptInterface::CxPrivate* pCxPrivate, JS::HandleValue data); static void SetProgress(ScriptInterface::CxPrivate* pCxPrivate, int progress); static void MaybeGC(ScriptInterface::CxPrivate* pCxPrivate); static std::vector GetCivData(ScriptInterface::CxPrivate* pCxPrivate); static CParamNode GetTemplate(ScriptInterface::CxPrivate* pCxPrivate, const std::string& templateName); static bool TemplateExists(ScriptInterface::CxPrivate* pCxPrivate, const std::string& templateName); static std::vector FindTemplates(ScriptInterface::CxPrivate* pCxPrivate, const std::string& path, bool includeSubdirectories); static std::vector FindActorTemplates(ScriptInterface::CxPrivate* pCxPrivate, const std::string& path, bool includeSubdirectories); std::set m_LoadedLibraries; shared_ptr m_MapData; boost::rand48 m_MapGenRNG; int m_Progress; ScriptInterface* m_ScriptInterface; VfsPath m_ScriptPath; std::string m_Settings; CTemplateLoader m_TemplateLoader; // Thread static void* RunThread(void* data); bool Run(); pthread_t m_WorkerThread; CMutex m_WorkerMutex; }; #endif //INCLUDED_MAPGENERATOR ================================================ FILE: fpsgame/graphics/MapIO.h ================================================ /* Copyright (C) 2009 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_MAPIO #define INCLUDED_MAPIO class CMapIO { public: // current file version given to saved maps enum { FILE_VERSION = 6 }; // supported file read version - file with version less than this will be reject enum { FILE_READ_VERSION = 6 }; #pragma pack(push, 1) // description of a tile for I/O purposes struct STileDesc { // index into the texture array of first texture on tile u16 m_Tex1Index; // index into the texture array of second texture; (0xFFFF) if none u16 m_Tex2Index; // priority u32 m_Priority; }; #pragma pack(pop) }; #endif ================================================ FILE: fpsgame/graphics/MapReader.cpp ================================================ /* Copyright (C) 2016 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "MapReader.h" #include "graphics/Camera.h" #include "graphics/CinemaManager.h" #include "graphics/Entity.h" #include "graphics/GameView.h" #include "graphics/MapGenerator.h" #include "graphics/Patch.h" #include "graphics/Terrain.h" #include "graphics/TerrainTextureEntry.h" #include "graphics/TerrainTextureManager.h" #include "lib/timer.h" #include "lib/external_libraries/libsdl.h" #include "maths/MathUtil.h" #include "ps/CLogger.h" #include "ps/Loader.h" #include "ps/LoaderThunks.h" #include "ps/World.h" #include "ps/XML/Xeromyces.h" #include "renderer/PostprocManager.h" #include "renderer/SkyManager.h" #include "renderer/WaterManager.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpObstruction.h" #include "simulation2/components/ICmpOwnership.h" #include "simulation2/components/ICmpPlayer.h" #include "simulation2/components/ICmpPlayerManager.h" #include "simulation2/components/ICmpPosition.h" #include "simulation2/components/ICmpTerrain.h" #include "simulation2/components/ICmpVisual.h" #include "simulation2/components/ICmpWaterManager.h" #include CMapReader::CMapReader() : xml_reader(0), m_PatchesPerSide(0), m_MapGen(0) { cur_terrain_tex = 0; // important - resets generator state // Maps that don't override the default probably want the old lighting model //m_LightEnv.SetLightingModel("old"); //pPostproc->SetPostEffect(L"default"); } // LoadMap: try to load the map from given file; reinitialise the scene to new data if successful void CMapReader::LoadMap(const VfsPath& pathname, JSRuntime* rt, JS::HandleValue settings, CTerrain *pTerrain_, WaterManager* pWaterMan_, SkyManager* pSkyMan_, CLightEnv *pLightEnv_, CGameView *pGameView_, CCinemaManager* pCinema_, CTriggerManager* pTrigMan_, CPostprocManager* pPostproc_, CSimulation2 *pSimulation2_, const CSimContext* pSimContext_, int playerID_, bool skipEntities) { // latch parameters (held until DelayedLoadFinished) pTerrain = pTerrain_; pLightEnv = pLightEnv_; pGameView = pGameView_; pWaterMan = pWaterMan_; pSkyMan = pSkyMan_; pCinema = pCinema_; pTrigMan = pTrigMan_; pPostproc = pPostproc_; pSimulation2 = pSimulation2_; pSimContext = pSimContext_; m_PlayerID = playerID_; m_SkipEntities = skipEntities; m_StartingCameraTarget = INVALID_ENTITY; m_ScriptSettings.init(rt, settings); filename_xml = pathname.ChangeExtension(L".xml"); // In some cases (particularly tests) we don't want to bother storing a large // mostly-empty .pmp file, so we let the XML file specify basic terrain instead. // If there's an .xml file and no .pmp, then we're probably in this XML-only mode only_xml = false; if (!VfsFileExists(pathname) && VfsFileExists(filename_xml)) { only_xml = true; } file_format_version = CMapIO::FILE_VERSION; // default if there's no .pmp if (!only_xml) { // [25ms] unpacker.Read(pathname, "PSMP"); file_format_version = unpacker.GetVersion(); } // check oldest supported version if (file_format_version < FILE_READ_VERSION) throw PSERROR_File_InvalidVersion(); // delete all existing entities if (pSimulation2) pSimulation2->ResetState(); // reset post effects if (pPostproc) pPostproc->SetPostEffect(L"default"); // load map or script settings script if (settings.isUndefined()) RegMemFun(this, &CMapReader::LoadScriptSettings, L"CMapReader::LoadScriptSettings", 50); else RegMemFun(this, &CMapReader::LoadRMSettings, L"CMapReader::LoadRMSettings", 50); // load player settings script (must be done before reading map) RegMemFun(this, &CMapReader::LoadPlayerSettings, L"CMapReader::LoadPlayerSettings", 50); // unpack the data if (!only_xml) RegMemFun(this, &CMapReader::UnpackMap, L"CMapReader::UnpackMap", 1200); // read the corresponding XML file RegMemFun(this, &CMapReader::ReadXML, L"CMapReader::ReadXML", 50); // apply terrain data to the world RegMemFun(this, &CMapReader::ApplyTerrainData, L"CMapReader::ApplyTerrainData", 5); // read entities RegMemFun(this, &CMapReader::ReadXMLEntities, L"CMapReader::ReadXMLEntities", 5800); // apply misc data to the world RegMemFun(this, &CMapReader::ApplyData, L"CMapReader::ApplyData", 5); // load map settings script (must be done after reading map) RegMemFun(this, &CMapReader::LoadMapSettings, L"CMapReader::LoadMapSettings", 5); RegMemFun(this, &CMapReader::DelayLoadFinished, L"CMapReader::DelayLoadFinished", 5); } // LoadRandomMap: try to load the map data; reinitialise the scene to new data if successful void CMapReader::LoadRandomMap(const CStrW& scriptFile, JSRuntime* rt, JS::HandleValue settings, CTerrain *pTerrain_, WaterManager* pWaterMan_, SkyManager* pSkyMan_, CLightEnv *pLightEnv_, CGameView *pGameView_, CCinemaManager* pCinema_, CTriggerManager* pTrigMan_, CPostprocManager* pPostproc_, CSimulation2 *pSimulation2_, int playerID_) { // latch parameters (held until DelayedLoadFinished) m_ScriptFile = scriptFile; pSimulation2 = pSimulation2_; pSimContext = pSimulation2 ? &pSimulation2->GetSimContext() : NULL; m_ScriptSettings.init(rt, settings); pTerrain = pTerrain_; pLightEnv = pLightEnv_; pGameView = pGameView_; pWaterMan = pWaterMan_; pSkyMan = pSkyMan_; pCinema = pCinema_; pTrigMan = pTrigMan_; pPostproc = pPostproc_; m_PlayerID = playerID_; m_SkipEntities = false; m_StartingCameraTarget = INVALID_ENTITY; // delete all existing entities if (pSimulation2) pSimulation2->ResetState(); only_xml = false; // copy random map settings (before entity creation) RegMemFun(this, &CMapReader::LoadRMSettings, L"CMapReader::LoadRMSettings", 50); // load player settings script (must be done before reading map) RegMemFun(this, &CMapReader::LoadPlayerSettings, L"CMapReader::LoadPlayerSettings", 50); // load map generator with random map script RegMemFun(this, &CMapReader::GenerateMap, L"CMapReader::GenerateMap", 5000); // parse RMS results into terrain structure RegMemFun(this, &CMapReader::ParseTerrain, L"CMapReader::ParseTerrain", 500); // parse RMS results into environment settings RegMemFun(this, &CMapReader::ParseEnvironment, L"CMapReader::ParseEnvironment", 5); // parse RMS results into camera settings RegMemFun(this, &CMapReader::ParseCamera, L"CMapReader::ParseCamera", 5); // apply terrain data to the world RegMemFun(this, &CMapReader::ApplyTerrainData, L"CMapReader::ApplyTerrainData", 5); // parse RMS results into entities RegMemFun(this, &CMapReader::ParseEntities, L"CMapReader::ParseEntities", 1000); // apply misc data to the world RegMemFun(this, &CMapReader::ApplyData, L"CMapReader::ApplyData", 5); // load map settings script (must be done after reading map) RegMemFun(this, &CMapReader::LoadMapSettings, L"CMapReader::LoadMapSettings", 5); RegMemFun(this, &CMapReader::DelayLoadFinished, L"CMapReader::DelayLoadFinished", 5); } // UnpackMap: unpack the given data from the raw data stream into local variables int CMapReader::UnpackMap() { // now unpack everything into local data int ret = UnpackTerrain(); if (ret != 0) // failed or timed out { return ret; } return 0; } // UnpackTerrain: unpack the terrain from the end of the input data stream // - data: map size, heightmap, list of textures used by map, texture tile assignments int CMapReader::UnpackTerrain() { // yield after this time is reached. balances increased progress bar // smoothness vs. slowing down loading. const double end_time = timer_Time() + 200e-3; // first call to generator (this is skipped after first call, // i.e. when the loop below was interrupted) if (cur_terrain_tex == 0) { m_PatchesPerSide = (ssize_t)unpacker.UnpackSize(); // unpack heightmap [600us] size_t verticesPerSide = m_PatchesPerSide*PATCH_SIZE+1; m_Heightmap.resize(SQR(verticesPerSide)); unpacker.UnpackRaw(&m_Heightmap[0], SQR(verticesPerSide)*sizeof(u16)); // unpack # textures num_terrain_tex = unpacker.UnpackSize(); m_TerrainTextures.reserve(num_terrain_tex); } // unpack texture names; find handle for each texture. // interruptible. while (cur_terrain_tex < num_terrain_tex) { CStr texturename; unpacker.UnpackString(texturename); ENSURE(CTerrainTextureManager::IsInitialised()); // we need this for the terrain properties (even when graphics are disabled) CTerrainTextureEntry* texentry = g_TexMan.FindTexture(texturename); m_TerrainTextures.push_back(texentry); cur_terrain_tex++; LDR_CHECK_TIMEOUT(cur_terrain_tex, num_terrain_tex); } // unpack tile data [3ms] ssize_t tilesPerSide = m_PatchesPerSide*PATCH_SIZE; m_Tiles.resize(size_t(SQR(tilesPerSide))); unpacker.UnpackRaw(&m_Tiles[0], sizeof(STileDesc)*m_Tiles.size()); // reset generator state. cur_terrain_tex = 0; return 0; } int CMapReader::ApplyTerrainData() { if (m_PatchesPerSide == 0) { // we'll probably crash when trying to use this map later throw PSERROR_Game_World_MapLoadFailed("Error loading map: no terrain data.\nCheck application log for details."); } if (!only_xml) { // initialise the terrain pTerrain->Initialize(m_PatchesPerSide, &m_Heightmap[0]); // setup the textures on the minipatches STileDesc* tileptr = &m_Tiles[0]; for (ssize_t j=0; jGetPatch(i,j)->m_MiniPatches[m][k]; // can't fail mp.Tex = m_TerrainTextures[tileptr->m_Tex1Index]; mp.Priority = tileptr->m_Priority; tileptr++; } } } } } CmpPtr cmpTerrain(*pSimContext, SYSTEM_ENTITY); if (cmpTerrain) cmpTerrain->ReloadTerrain(); return 0; } // ApplyData: take all the input data, and rebuild the scene from it int CMapReader::ApplyData() { // copy over the lighting parameters if (pLightEnv) *pLightEnv = m_LightEnv; CmpPtr cmpPlayerManager(*pSimContext, SYSTEM_ENTITY); if (pGameView && cmpPlayerManager) { // Default to global camera (with constraints) pGameView->ResetCameraTarget(pGameView->GetCamera()->GetFocus()); // TODO: Starting rotation? CmpPtr cmpPlayer(*pSimContext, cmpPlayerManager->GetPlayerByID(m_PlayerID)); if (cmpPlayer && cmpPlayer->HasStartingCamera()) { // Use player starting camera CFixedVector3D pos = cmpPlayer->GetStartingCameraPos(); pGameView->ResetCameraTarget(CVector3D(pos.X.ToFloat(), pos.Y.ToFloat(), pos.Z.ToFloat())); } else if (m_StartingCameraTarget != INVALID_ENTITY) { // Point camera at entity CmpPtr cmpPosition(*pSimContext, m_StartingCameraTarget); if (cmpPosition) { CFixedVector3D pos = cmpPosition->GetPosition(); pGameView->ResetCameraTarget(CVector3D(pos.X.ToFloat(), pos.Y.ToFloat(), pos.Z.ToFloat())); } } } return 0; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////// PSRETURN CMapSummaryReader::LoadMap(const VfsPath& pathname) { VfsPath filename_xml = pathname.ChangeExtension(L".xml"); CXeromyces xmb_file; if (xmb_file.Load(g_VFS, filename_xml, "scenario") != PSRETURN_OK) return PSRETURN_File_ReadFailed; // Define all the relevant elements used in the XML file #define EL(x) int el_##x = xmb_file.GetElementID(#x) #define AT(x) int at_##x = xmb_file.GetAttributeID(#x) EL(scenario); EL(scriptsettings); #undef AT #undef EL XMBElement root = xmb_file.GetRoot(); ENSURE(root.GetNodeName() == el_scenario); XERO_ITER_EL(root, child) { int child_name = child.GetNodeName(); if (child_name == el_scriptsettings) { m_ScriptSettings = child.GetText(); } } return PSRETURN_OK; } void CMapSummaryReader::GetMapSettings(ScriptInterface& scriptInterface, JS::MutableHandleValue ret) { JSContext* cx = scriptInterface.GetContext(); JSAutoRequest rq(cx); scriptInterface.Eval("({})", ret); if (m_ScriptSettings.empty()) return; JS::RootedValue scriptSettingsVal(cx); scriptInterface.ParseJSON(m_ScriptSettings, &scriptSettingsVal); scriptInterface.SetProperty(ret, "settings", scriptSettingsVal, false); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Holds various state data while reading maps, so that loading can be // interrupted (e.g. to update the progress display) then later resumed. class CXMLReader { NONCOPYABLE(CXMLReader); public: CXMLReader(const VfsPath& xml_filename, CMapReader& mapReader) : m_MapReader(mapReader), nodes(NULL, 0, NULL) { Init(xml_filename); } CStr ReadScriptSettings(); // read everything except for entities void ReadXML(); // return semantics: see Loader.cpp!LoadFunc. int ProgressiveReadEntities(); private: CXeromyces xmb_file; CMapReader& m_MapReader; int el_entity; int el_tracks; int el_template, el_player; int el_position, el_orientation, el_obstruction; int el_actor; int at_x, at_y, at_z; int at_group, at_group2; int at_angle; int at_uid; int at_seed; XMBElementList nodes; // children of root // loop counters size_t node_idx; size_t entity_idx; // # entities+nonentities processed and total (for progress calc) int completed_jobs, total_jobs; // maximum used entity ID, so we can safely allocate new ones entity_id_t max_uid; void Init(const VfsPath& xml_filename); void ReadTerrain(XMBElement parent); void ReadEnvironment(XMBElement parent); void ReadCamera(XMBElement parent); void ReadPaths(XMBElement parent); void ReadTriggers(XMBElement parent); int ReadEntities(XMBElement parent, double end_time); }; void CXMLReader::Init(const VfsPath& xml_filename) { // must only assign once, so do it here node_idx = entity_idx = 0; if (xmb_file.Load(g_VFS, xml_filename, "scenario") != PSRETURN_OK) throw PSERROR_File_ReadFailed(); // define the elements and attributes that are frequently used in the XML file, // so we don't need to do lots of string construction and comparison when // reading the data. // (Needs to be synchronised with the list in CXMLReader - ugh) #define EL(x) el_##x = xmb_file.GetElementID(#x) #define AT(x) at_##x = xmb_file.GetAttributeID(#x) EL(entity); EL(tracks); EL(template); EL(player); EL(position); EL(orientation); EL(obstruction); EL(actor); AT(x); AT(y); AT(z); AT(group); AT(group2); AT(angle); AT(uid); AT(seed); #undef AT #undef EL XMBElement root = xmb_file.GetRoot(); ENSURE(xmb_file.GetElementString(root.GetNodeName()) == "Scenario"); nodes = root.GetChildNodes(); // find out total number of entities+nonentities // (used when calculating progress) completed_jobs = 0; total_jobs = 0; for (XMBElement node : nodes) total_jobs += node.GetChildNodes().size(); // Find the maximum entity ID, so we can safely allocate new IDs without conflicts max_uid = SYSTEM_ENTITY; XMBElement ents = nodes.GetFirstNamedItem(xmb_file.GetElementID("Entities")); XERO_ITER_EL(ents, ent) { CStr uid = ent.GetAttributes().GetNamedItem(at_uid); max_uid = std::max(max_uid, (entity_id_t)uid.ToUInt()); } } CStr CXMLReader::ReadScriptSettings() { XMBElement root = xmb_file.GetRoot(); ENSURE(xmb_file.GetElementString(root.GetNodeName()) == "Scenario"); nodes = root.GetChildNodes(); XMBElement settings = nodes.GetFirstNamedItem(xmb_file.GetElementID("ScriptSettings")); return settings.GetText(); } void CXMLReader::ReadTerrain(XMBElement parent) { #define AT(x) int at_##x = xmb_file.GetAttributeID(#x) AT(patches); AT(texture); AT(priority); AT(height); #undef AT ssize_t patches = 9; CStr texture = "grass1_spring"; int priority = 0; u16 height = 16384; XERO_ITER_ATTR(parent, attr) { if (attr.Name == at_patches) patches = attr.Value.ToInt(); else if (attr.Name == at_texture) texture = attr.Value; else if (attr.Name == at_priority) priority = attr.Value.ToInt(); else if (attr.Name == at_height) height = (u16)attr.Value.ToInt(); } m_MapReader.m_PatchesPerSide = patches; // Load the texture ENSURE(CTerrainTextureManager::IsInitialised()); // we need this for the terrain properties (even when graphics are disabled) CTerrainTextureEntry* texentry = g_TexMan.FindTexture(texture); m_MapReader.pTerrain->Initialize(patches, NULL); // Fill the heightmap u16* heightmap = m_MapReader.pTerrain->GetHeightMap(); ssize_t verticesPerSide = m_MapReader.pTerrain->GetVerticesPerSide(); for (ssize_t i = 0; i < SQR(verticesPerSide); ++i) heightmap[i] = height; // Fill the texture map for (ssize_t pz = 0; pz < patches; ++pz) { for (ssize_t px = 0; px < patches; ++px) { CPatch* patch = m_MapReader.pTerrain->GetPatch(px, pz); // can't fail for (ssize_t z = 0; z < PATCH_SIZE; ++z) { for (ssize_t x = 0; x < PATCH_SIZE; ++x) { patch->m_MiniPatches[z][x].Tex = texentry; patch->m_MiniPatches[z][x].Priority = priority; } } } } } void CXMLReader::ReadEnvironment(XMBElement parent) { #define EL(x) int el_##x = xmb_file.GetElementID(#x) #define AT(x) int at_##x = xmb_file.GetAttributeID(#x) EL(lightingmodel); EL(posteffect); EL(skyset); EL(suncolor); EL(sunelevation); EL(sunrotation); EL(terrainambientcolor); EL(unitsambientcolor); EL(water); EL(waterbody); EL(type); EL(color); EL(tint); EL(height); EL(shininess); // for compatibility EL(waviness); EL(murkiness); EL(windangle); EL(reflectiontint); // for compatibility EL(reflectiontintstrength); // for compatibility EL(fog); EL(fogcolor); EL(fogfactor); EL(fogthickness); EL(postproc); EL(brightness); EL(contrast); EL(saturation); EL(bloom); AT(r); AT(g); AT(b); #undef AT #undef EL XERO_ITER_EL(parent, element) { int element_name = element.GetNodeName(); XMBAttributeList attrs = element.GetAttributes(); if (element_name == el_lightingmodel) { // NOP - obsolete. } else if (element_name == el_skyset) { if (m_MapReader.pSkyMan) m_MapReader.pSkyMan->SetSkySet(element.GetText().FromUTF8()); } else if (element_name == el_suncolor) { m_MapReader.m_LightEnv.m_SunColor = RGBColor( attrs.GetNamedItem(at_r).ToFloat(), attrs.GetNamedItem(at_g).ToFloat(), attrs.GetNamedItem(at_b).ToFloat()); } else if (element_name == el_sunelevation) { m_MapReader.m_LightEnv.m_Elevation = attrs.GetNamedItem(at_angle).ToFloat(); } else if (element_name == el_sunrotation) { m_MapReader.m_LightEnv.m_Rotation = attrs.GetNamedItem(at_angle).ToFloat(); } else if (element_name == el_terrainambientcolor) { m_MapReader.m_LightEnv.m_TerrainAmbientColor = RGBColor( attrs.GetNamedItem(at_r).ToFloat(), attrs.GetNamedItem(at_g).ToFloat(), attrs.GetNamedItem(at_b).ToFloat()); } else if (element_name == el_unitsambientcolor) { m_MapReader.m_LightEnv.m_UnitsAmbientColor = RGBColor( attrs.GetNamedItem(at_r).ToFloat(), attrs.GetNamedItem(at_g).ToFloat(), attrs.GetNamedItem(at_b).ToFloat()); } else if (element_name == el_fog) { XERO_ITER_EL(element, fog) { int element_name = fog.GetNodeName(); if (element_name == el_fogcolor) { XMBAttributeList attrs = fog.GetAttributes(); m_MapReader.m_LightEnv.m_FogColor = RGBColor( attrs.GetNamedItem(at_r).ToFloat(), attrs.GetNamedItem(at_g).ToFloat(), attrs.GetNamedItem(at_b).ToFloat()); } else if (element_name == el_fogfactor) { m_MapReader.m_LightEnv.m_FogFactor = fog.GetText().ToFloat(); } else if (element_name == el_fogthickness) { m_MapReader.m_LightEnv.m_FogMax = fog.GetText().ToFloat(); } } } else if (element_name == el_postproc) { XERO_ITER_EL(element, postproc) { int element_name = postproc.GetNodeName(); if (element_name == el_brightness) { m_MapReader.m_LightEnv.m_Brightness = postproc.GetText().ToFloat(); } else if (element_name == el_contrast) { m_MapReader.m_LightEnv.m_Contrast = postproc.GetText().ToFloat(); } else if (element_name == el_saturation) { m_MapReader.m_LightEnv.m_Saturation = postproc.GetText().ToFloat(); } else if (element_name == el_bloom) { m_MapReader.m_LightEnv.m_Bloom = postproc.GetText().ToFloat(); } else if (element_name == el_posteffect) { if (m_MapReader.pPostproc) m_MapReader.pPostproc->SetPostEffect(postproc.GetText().FromUTF8()); } } } else if (element_name == el_water) { XERO_ITER_EL(element, waterbody) { ENSURE(waterbody.GetNodeName() == el_waterbody); XERO_ITER_EL(waterbody, waterelement) { int element_name = waterelement.GetNodeName(); if (element_name == el_height) { CmpPtr cmpWaterManager(*m_MapReader.pSimContext, SYSTEM_ENTITY); ENSURE(cmpWaterManager); cmpWaterManager->SetWaterLevel(entity_pos_t::FromString(waterelement.GetText())); continue; } // The rest are purely graphical effects, and should be ignored if // graphics are disabled if (!m_MapReader.pWaterMan) continue; if (element_name == el_type) { if (waterelement.GetText() == "default") m_MapReader.pWaterMan->m_WaterType = L"ocean"; else m_MapReader.pWaterMan->m_WaterType = waterelement.GetText().FromUTF8(); } else if (element_name == el_shininess || element_name == el_reflectiontint || element_name == el_reflectiontintstrength) { // deprecated. } #define READ_COLOR(el, out) \ else if (element_name == el) \ { \ XMBAttributeList attrs = waterelement.GetAttributes(); \ out = CColor( \ attrs.GetNamedItem(at_r).ToFloat(), \ attrs.GetNamedItem(at_g).ToFloat(), \ attrs.GetNamedItem(at_b).ToFloat(), \ 1.f); \ } #define READ_FLOAT(el, out) \ else if (element_name == el) \ { \ out = waterelement.GetText().ToFloat(); \ } \ READ_COLOR(el_color, m_MapReader.pWaterMan->m_WaterColor) READ_COLOR(el_tint, m_MapReader.pWaterMan->m_WaterTint) READ_FLOAT(el_waviness, m_MapReader.pWaterMan->m_Waviness) READ_FLOAT(el_murkiness, m_MapReader.pWaterMan->m_Murkiness) READ_FLOAT(el_windangle, m_MapReader.pWaterMan->m_WindAngle) #undef READ_FLOAT #undef READ_COLOR else debug_warn(L"Invalid map XML data"); } } } else debug_warn(L"Invalid map XML data"); } m_MapReader.m_LightEnv.CalculateSunDirection(); } void CXMLReader::ReadCamera(XMBElement parent) { // defaults if we don't find player starting camera #define EL(x) int el_##x = xmb_file.GetElementID(#x) #define AT(x) int at_##x = xmb_file.GetAttributeID(#x) EL(declination); EL(rotation); EL(position); AT(angle); AT(x); AT(y); AT(z); #undef AT #undef EL float declination = DEGTORAD(30.f), rotation = DEGTORAD(-45.f); CVector3D translation = CVector3D(100, 150, -100); XERO_ITER_EL(parent, element) { int element_name = element.GetNodeName(); XMBAttributeList attrs = element.GetAttributes(); if (element_name == el_declination) { declination = attrs.GetNamedItem(at_angle).ToFloat(); } else if (element_name == el_rotation) { rotation = attrs.GetNamedItem(at_angle).ToFloat(); } else if (element_name == el_position) { translation = CVector3D( attrs.GetNamedItem(at_x).ToFloat(), attrs.GetNamedItem(at_y).ToFloat(), attrs.GetNamedItem(at_z).ToFloat()); } else debug_warn(L"Invalid map XML data"); } if (m_MapReader.pGameView) { m_MapReader.pGameView->GetCamera()->m_Orientation.SetXRotation(declination); m_MapReader.pGameView->GetCamera()->m_Orientation.RotateY(rotation); m_MapReader.pGameView->GetCamera()->m_Orientation.Translate(translation); m_MapReader.pGameView->GetCamera()->UpdateFrustum(); } } void CXMLReader::ReadPaths(XMBElement parent) { #define EL(x) int el_##x = xmb_file.GetElementID(#x) #define AT(x) int at_##x = xmb_file.GetAttributeID(#x) EL(path); EL(rotation); EL(node); EL(position); EL(target); AT(name); AT(timescale); AT(orientation); AT(mode); AT(style); AT(x); AT(y); AT(z); AT(deltatime); #undef EL #undef AT XERO_ITER_EL(parent, element) { int elementName = element.GetNodeName(); if (elementName == el_path) { CCinemaData pathData; XMBAttributeList attrs = element.GetAttributes(); CStrW pathName(attrs.GetNamedItem(at_name).FromUTF8()); pathData.m_Timescale = fixed::FromString(attrs.GetNamedItem(at_timescale)); TNSpline pathSpline, targetSpline; fixed lastTargetTime = fixed::Zero(); pathData.m_Name = pathName; pathData.m_Orientation = attrs.GetNamedItem(at_orientation).FromUTF8(); pathData.m_Mode = attrs.GetNamedItem(at_mode).FromUTF8(); pathData.m_Style = attrs.GetNamedItem(at_style).FromUTF8(); XERO_ITER_EL(element, pathChild) { elementName = pathChild.GetNodeName(); attrs = pathChild.GetAttributes(); // Load node data used for spline if (elementName == el_node) { bool positionDeclared = false; SplineData data; data.Distance = fixed::FromString(attrs.GetNamedItem(at_deltatime)); lastTargetTime += fixed::FromString(attrs.GetNamedItem(at_deltatime)); XERO_ITER_EL(pathChild, nodeChild) { elementName = nodeChild.GetNodeName(); attrs = nodeChild.GetAttributes(); if (elementName == el_position) { data.Position.X = fixed::FromString(attrs.GetNamedItem(at_x)); data.Position.Y = fixed::FromString(attrs.GetNamedItem(at_y)); data.Position.Z = fixed::FromString(attrs.GetNamedItem(at_z)); positionDeclared = true; } else if (elementName == el_rotation) { data.Rotation.X = fixed::FromString(attrs.GetNamedItem(at_x)); data.Rotation.Y = fixed::FromString(attrs.GetNamedItem(at_y)); data.Rotation.Z = fixed::FromString(attrs.GetNamedItem(at_z)); } else if (elementName == el_target) { CFixedVector3D targetPosition; targetPosition.X = fixed::FromString(attrs.GetNamedItem(at_x)); targetPosition.Y = fixed::FromString(attrs.GetNamedItem(at_y)); targetPosition.Z = fixed::FromString(attrs.GetNamedItem(at_z)); targetSpline.AddNode(targetPosition, CFixedVector3D(), lastTargetTime); lastTargetTime = fixed::Zero(); } else LOGWARNING("Invalid cinematic element for node child"); } // Skip the node if no position if (positionDeclared) pathSpline.AddNode(data.Position, data.Rotation, data.Distance); } else LOGWARNING("Invalid cinematic element for path child"); } // Construct cinema path with data gathered CCinemaPath path(pathData, pathSpline, targetSpline); if (path.Empty()) { LOGWARNING("Path with name '%s' is empty", pathName.ToUTF8()); return; } if (!m_MapReader.pCinema->HasPath(pathName)) m_MapReader.pCinema->AddPath(pathName, path); else LOGWARNING("Path with name '%s' already exists", pathName.ToUTF8()); } else LOGWARNING("Invalid path child with name '%s'", element.GetText()); } } void CXMLReader::ReadTriggers(XMBElement UNUSED(parent)) { } int CXMLReader::ReadEntities(XMBElement parent, double end_time) { XMBElementList entities = parent.GetChildNodes(); ENSURE(m_MapReader.pSimulation2); CSimulation2& sim = *m_MapReader.pSimulation2; CmpPtr cmpPlayerManager(sim, SYSTEM_ENTITY); while (entity_idx < entities.size()) { // all new state at this scope and below doesn't need to be // wrapped, since we only yield after a complete iteration. XMBElement entity = entities[entity_idx++]; ENSURE(entity.GetNodeName() == el_entity); XMBAttributeList attrs = entity.GetAttributes(); CStr uid = attrs.GetNamedItem(at_uid); ENSURE(!uid.empty()); int EntityUid = uid.ToInt(); CStrW TemplateName; int PlayerID = 0; CFixedVector3D Position; CFixedVector3D Orientation; long Seed = -1; // Obstruction control groups. entity_id_t ControlGroup = INVALID_ENTITY; entity_id_t ControlGroup2 = INVALID_ENTITY; XERO_ITER_EL(entity, setting) { int element_name = setting.GetNodeName(); //