1233 lines
34 KiB
C++
1233 lines
34 KiB
C++
// Copyright © 2008-2020 Pioneer Developers. See AUTHORS.txt for details
|
|
// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt
|
|
|
|
#include "ModelViewer.h"
|
|
#include "FileSystem.h"
|
|
#include "GameConfig.h"
|
|
#include "GameSaveError.h"
|
|
#include "KeyBindings.h"
|
|
#include "ModManager.h"
|
|
#include "OS.h"
|
|
#include "SDL_keycode.h"
|
|
#include "StringF.h"
|
|
#include "graphics/Drawables.h"
|
|
#include "graphics/Graphics.h"
|
|
#include "graphics/Light.h"
|
|
#include "graphics/TextureBuilder.h"
|
|
#include "graphics/VertexArray.h"
|
|
#include "graphics/opengl/RendererGL.h"
|
|
#include "scenegraph/Animation.h"
|
|
#include "scenegraph/BinaryConverter.h"
|
|
#include "scenegraph/DumpVisitor.h"
|
|
#include "scenegraph/FindNodeVisitor.h"
|
|
#include "scenegraph/ModelSkin.h"
|
|
|
|
#include "imgui/imgui.h"
|
|
|
|
#include <iterator>
|
|
|
|
#include "Pi.h"
|
|
|
|
//default options
|
|
ModelViewer::Options::Options() :
|
|
attachGuns(false),
|
|
showTags(false),
|
|
showDockingLocators(false),
|
|
showCollMesh(false),
|
|
showAabb(false),
|
|
showShields(false),
|
|
showGrid(false),
|
|
showLandingPad(false),
|
|
showUI(true),
|
|
wireframe(false),
|
|
mouselookEnabled(false),
|
|
gridInterval(10.f),
|
|
lightPreset(0),
|
|
orthoView(false)
|
|
{
|
|
}
|
|
|
|
//some utility functions
|
|
namespace {
|
|
//azimuth/elevation in degrees to a dir vector
|
|
vector3f az_el_to_dir(float yaw, float pitch)
|
|
{
|
|
//0,0 points to "right" (1,0,0)
|
|
vector3f v;
|
|
v.x = cos(DEG2RAD(yaw)) * cos(DEG2RAD(pitch));
|
|
v.y = sin(DEG2RAD(pitch));
|
|
v.z = sin(DEG2RAD(yaw)) * cos(DEG2RAD(pitch));
|
|
return v;
|
|
}
|
|
|
|
float zoom_distance(const float base_distance, const float zoom)
|
|
{
|
|
return base_distance * powf(2.0f, zoom);
|
|
}
|
|
} // namespace
|
|
|
|
// An adaptor for automagic reverse range-for iteration of containers
|
|
// One might be able to specialize this for raw arrays, but that's beyond the
|
|
// point of its use-case.
|
|
// One might also point out that this is surely more work to code than simply
|
|
// writing an explicit iterator loop, to which I say: bah humbug!
|
|
template <typename T>
|
|
struct reverse_container_t {
|
|
using iterator = std::reverse_iterator<typename T::iterator>;
|
|
using const_iterator = std::reverse_iterator<typename T::const_iterator>;
|
|
|
|
using value_type = typename std::remove_reference<T>::type;
|
|
|
|
reverse_container_t(value_type &ref) :
|
|
ref(ref) {}
|
|
|
|
iterator begin() { return iterator(ref.end()); }
|
|
const_iterator begin() const { return const_iterator(ref.cend()); }
|
|
|
|
iterator end() { return iterator(ref.begin()); }
|
|
const_iterator end() const { return const_iterator(ref.cbegin()); }
|
|
|
|
private:
|
|
value_type &ref;
|
|
};
|
|
|
|
// Use this function for automatic template parameter deduction
|
|
template <typename T>
|
|
reverse_container_t<T> reverse_container(T &ref) { return reverse_container_t<T>(ref); }
|
|
|
|
namespace ImGui {
|
|
bool ColorEdit3(const char *label, Color &color)
|
|
{
|
|
Color4f _c = color.ToColor4f();
|
|
bool changed = ColorEdit3(label, &_c[0]);
|
|
color = Color(_c);
|
|
return changed;
|
|
}
|
|
} // namespace ImGui
|
|
|
|
void ModelViewerApp::Startup()
|
|
{
|
|
Application::Startup();
|
|
OS::RedirectStdio();
|
|
|
|
std::unique_ptr<GameConfig> config(new GameConfig);
|
|
|
|
Lua::Init();
|
|
|
|
ModManager::Init();
|
|
|
|
Graphics::RendererOGL::RegisterRenderer();
|
|
|
|
auto *renderer = StartupRenderer(config.get());
|
|
|
|
// FIXME MAJOR FIXME: Action / Axis bindings depend on Pi::input to get their data.
|
|
// This is OBVIOUSLY suboptimal, and *must* be redesigned.
|
|
// Either make Input a singleton (lots of function overhead when polling axes)
|
|
// or cache input state on the binding itself (probably the best option)
|
|
|
|
Pi::input = StartupInput(config.get());
|
|
StartupPiGui();
|
|
|
|
NavLights::Init(renderer);
|
|
Shields::Init(renderer);
|
|
|
|
//run main loop until quit
|
|
m_modelViewer = std::make_shared<ModelViewer>(this, Lua::manager);
|
|
if (!m_modelName.empty())
|
|
m_modelViewer->SetModel(m_modelName);
|
|
|
|
m_modelViewer->ResetCamera();
|
|
|
|
QueueLifecycle(m_modelViewer);
|
|
}
|
|
|
|
void ModelViewerApp::Shutdown()
|
|
{
|
|
//uninit components
|
|
m_modelViewer.reset();
|
|
Lua::Uninit();
|
|
Shields::Uninit();
|
|
NavLights::Uninit();
|
|
Graphics::Uninit();
|
|
|
|
ShutdownRenderer();
|
|
Application::Shutdown();
|
|
}
|
|
|
|
void ModelViewerApp::PreUpdate()
|
|
{
|
|
HandleEvents();
|
|
GetPiGui()->NewFrame();
|
|
}
|
|
|
|
void ModelViewerApp::PostUpdate()
|
|
{
|
|
GetRenderer()->ClearDepthBuffer();
|
|
GetPiGui()->Render();
|
|
}
|
|
|
|
ModelViewer::ModelViewer(ModelViewerApp *app, LuaManager *lm) :
|
|
m_app(app),
|
|
m_input(app->GetInput()),
|
|
m_pigui(app->GetPiGui()),
|
|
m_renderer(app->GetRenderer()),
|
|
m_screenshotQueued(false),
|
|
m_shieldIsHit(false),
|
|
m_settingColourSliders(false),
|
|
m_shieldHitPan(-1.48f),
|
|
m_decalTexture(0),
|
|
m_rotX(0),
|
|
m_rotY(0),
|
|
m_zoom(0),
|
|
m_baseDistance(100.0f),
|
|
m_rng(time(0)),
|
|
m_modelIsShip(false),
|
|
m_colors({ Color(255, 0, 0),
|
|
Color(0, 255, 0),
|
|
Color(0, 0, 255) }),
|
|
m_modelName(""),
|
|
m_requestedModelName(),
|
|
m_logWindowSize(350.0f, 500.0f),
|
|
m_animWindowSize(0.0f, 150.0f)
|
|
{
|
|
onModelChanged.connect(sigc::mem_fun(*this, &ModelViewer::OnModelChanged));
|
|
SetupAxes();
|
|
|
|
//for grid, background
|
|
Graphics::RenderStateDesc rsd;
|
|
rsd.depthWrite = false;
|
|
rsd.cullMode = Graphics::CULL_NONE;
|
|
m_bgState = m_renderer->CreateRenderState(rsd);
|
|
}
|
|
|
|
void ModelViewer::Start()
|
|
{
|
|
UpdateModelList();
|
|
UpdateDecalList();
|
|
}
|
|
|
|
void ModelViewer::End()
|
|
{
|
|
ClearModel();
|
|
}
|
|
|
|
void ModelViewer::ReloadModel()
|
|
{
|
|
AddLog(stringf("Reloading model %0", m_modelName));
|
|
//camera is not reset, it would be annoying when
|
|
//tweaking materials
|
|
SetModel(m_modelName);
|
|
m_resetLogScroll = true;
|
|
}
|
|
|
|
void ModelViewer::ToggleCollMesh()
|
|
{
|
|
m_options.showDockingLocators = !m_options.showDockingLocators;
|
|
m_options.showCollMesh = !m_options.showCollMesh;
|
|
m_options.showAabb = m_options.showCollMesh;
|
|
}
|
|
|
|
void ModelViewer::ToggleShowShields()
|
|
{
|
|
m_options.showShields = !m_options.showShields;
|
|
}
|
|
|
|
void ModelViewer::ToggleGrid()
|
|
{
|
|
if (!m_options.showGrid) {
|
|
m_options.showGrid = true;
|
|
m_options.gridInterval = 1.0f;
|
|
} else {
|
|
m_options.gridInterval = powf(10, ceilf(log10f(m_options.gridInterval)) + 1);
|
|
if (m_options.gridInterval >= 10000.0f) {
|
|
m_options.showGrid = false;
|
|
m_options.gridInterval = 0.0f;
|
|
}
|
|
}
|
|
AddLog(m_options.showGrid ? stringf("Grid: %0{d}", int(m_options.gridInterval)) : "Grid: off");
|
|
}
|
|
|
|
void ModelViewer::ToggleGuns()
|
|
{
|
|
if (!m_gunModel) {
|
|
CreateTestResources();
|
|
}
|
|
|
|
if (!m_gunModel) {
|
|
AddLog("test_gun.model not available");
|
|
return;
|
|
}
|
|
|
|
m_options.attachGuns = !m_options.attachGuns;
|
|
SceneGraph::Model::TVecMT tags;
|
|
m_model->FindTagsByStartOfName("tag_gun_", tags);
|
|
if (tags.empty()) {
|
|
AddLog("Missing tags \"tag_gun_XXX\" in model");
|
|
return;
|
|
}
|
|
if (m_options.attachGuns) {
|
|
for (auto tag : tags) {
|
|
tag->AddChild(new SceneGraph::ModelNode(m_gunModel.get()));
|
|
}
|
|
} else { //detach
|
|
//we know there's nothing else
|
|
for (auto tag : tags) {
|
|
tag->RemoveChildAt(0);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
bool ModelViewer::SetRandomColor()
|
|
{
|
|
if (!m_model || !m_model->SupportsPatterns()) return false;
|
|
|
|
SceneGraph::ModelSkin skin;
|
|
skin.SetRandomColors(m_rng);
|
|
skin.Apply(m_model.get());
|
|
|
|
m_colors = skin.GetColors();
|
|
|
|
return true;
|
|
}
|
|
|
|
void ModelViewer::UpdateShield()
|
|
{
|
|
if (m_shieldIsHit) {
|
|
m_shieldHitPan += 0.05f;
|
|
}
|
|
if (m_shieldHitPan > 0.34f) {
|
|
m_shieldHitPan = -1.48f;
|
|
m_shieldIsHit = false;
|
|
}
|
|
}
|
|
|
|
void ModelViewer::HitIt()
|
|
{
|
|
if (m_model) {
|
|
assert(m_shields.get());
|
|
// pick a point on the shield to serve as the point of impact.
|
|
SceneGraph::StaticGeometry *sg = m_shields->GetFirstShieldMesh();
|
|
if (sg) {
|
|
SceneGraph::StaticGeometry::Mesh &mesh = sg->GetMeshAt(0);
|
|
|
|
// Please don't do this in game, no speed guarantee
|
|
const Uint32 posOffs = mesh.vertexBuffer->GetDesc().GetOffset(Graphics::ATTRIB_POSITION);
|
|
const Uint32 stride = mesh.vertexBuffer->GetDesc().stride;
|
|
const Uint32 vtxIdx = m_rng.Int32() % mesh.vertexBuffer->GetSize();
|
|
|
|
const Uint8 *vtxPtr = mesh.vertexBuffer->Map<Uint8>(Graphics::BUFFER_MAP_READ);
|
|
const vector3f pos = *reinterpret_cast<const vector3f *>(vtxPtr + vtxIdx * stride + posOffs);
|
|
mesh.vertexBuffer->Unmap();
|
|
m_shields->AddHit(vector3d(pos));
|
|
}
|
|
}
|
|
m_shieldHitPan = -1.48f;
|
|
m_shieldIsHit = true;
|
|
}
|
|
|
|
void ModelViewer::AddLog(const std::string &line)
|
|
{
|
|
m_log.push_back(line);
|
|
Output("%s\n", line.c_str());
|
|
}
|
|
|
|
void ModelViewer::ChangeCameraPreset(CameraPreset preset)
|
|
{
|
|
if (!m_model) return;
|
|
|
|
switch (preset) {
|
|
case CameraPreset::Bottom:
|
|
m_rotX = -90.0f;
|
|
m_rotY = 0.0f;
|
|
break;
|
|
case CameraPreset::Top:
|
|
m_rotX = 90.0f;
|
|
m_rotY = 0.0f;
|
|
break;
|
|
|
|
case CameraPreset::Left:
|
|
m_rotX = 0.f;
|
|
m_rotY = 90.0f;
|
|
break;
|
|
case CameraPreset::Right:
|
|
m_rotX = 0.f;
|
|
m_rotY = -90.0f;
|
|
break;
|
|
|
|
case CameraPreset::Front:
|
|
m_rotX = 0.f;
|
|
m_rotY = 180.0f;
|
|
break;
|
|
case CameraPreset::Back:
|
|
m_rotX = 0.f;
|
|
m_rotY = 0.0f;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ModelViewer::ToggleViewControlMode()
|
|
{
|
|
m_options.mouselookEnabled = !m_options.mouselookEnabled;
|
|
m_input->SetCapturingMouse(m_options.mouselookEnabled);
|
|
|
|
if (m_options.mouselookEnabled) {
|
|
m_viewRot = matrix3x3f::RotateY(DEG2RAD(m_rotY)) * matrix3x3f::RotateX(DEG2RAD(Clamp(m_rotX, -90.0f, 90.0f)));
|
|
m_viewPos = zoom_distance(m_baseDistance, m_zoom) * m_viewRot.VectorZ();
|
|
} else {
|
|
// TODO: re-initialise the turntable style view position from the current mouselook view
|
|
ResetCamera();
|
|
}
|
|
}
|
|
|
|
void ModelViewer::ClearModel()
|
|
{
|
|
m_model.reset();
|
|
|
|
m_animations.clear();
|
|
m_currentAnimation = nullptr;
|
|
m_patterns.clear();
|
|
m_currentPattern = 0;
|
|
m_currentDecal = 0;
|
|
|
|
m_gunModel.reset();
|
|
m_scaleModel.reset();
|
|
|
|
m_options.mouselookEnabled = false;
|
|
m_input->SetCapturingMouse(false);
|
|
m_viewPos = vector3f(0.0f, 0.0f, 10.0f);
|
|
}
|
|
|
|
void ModelViewer::CreateTestResources()
|
|
{
|
|
//load gun model for attachment test
|
|
//landingpad model for scale test
|
|
SceneGraph::Loader loader(m_renderer);
|
|
try {
|
|
SceneGraph::Model *m = loader.LoadModel("test_gun");
|
|
m_gunModel.reset(m);
|
|
|
|
m = loader.LoadModel("scale");
|
|
m_scaleModel.reset(m);
|
|
} catch (SceneGraph::LoadingError &) {
|
|
AddLog("Could not load test_gun or scale model");
|
|
}
|
|
}
|
|
|
|
void ModelViewer::DrawBackground()
|
|
{
|
|
m_renderer->SetOrthographicProjection(0.f, 1.f, 0.f, 1.f, -1.f, 1.f);
|
|
m_renderer->SetTransform(matrix4x4f::Identity());
|
|
|
|
if (!m_bgBuffer.Valid()) {
|
|
const Color top = Color::BLACK;
|
|
const Color bottom = Color(77, 77, 77);
|
|
Graphics::VertexArray bgArr(Graphics::ATTRIB_POSITION | Graphics::ATTRIB_DIFFUSE, 6);
|
|
// triangle 1
|
|
bgArr.Add(vector3f(0.f, 0.f, 0.f), bottom);
|
|
bgArr.Add(vector3f(1.f, 0.f, 0.f), bottom);
|
|
bgArr.Add(vector3f(1.f, 1.f, 0.f), top);
|
|
// triangle 2
|
|
bgArr.Add(vector3f(0.f, 0.f, 0.f), bottom);
|
|
bgArr.Add(vector3f(1.f, 1.f, 0.f), top);
|
|
bgArr.Add(vector3f(0.f, 1.f, 0.f), top);
|
|
|
|
Graphics::VertexBufferDesc vbd;
|
|
vbd.attrib[0].semantic = Graphics::ATTRIB_POSITION;
|
|
vbd.attrib[0].format = Graphics::ATTRIB_FORMAT_FLOAT3;
|
|
vbd.attrib[1].semantic = Graphics::ATTRIB_DIFFUSE;
|
|
vbd.attrib[1].format = Graphics::ATTRIB_FORMAT_UBYTE4;
|
|
vbd.numVertices = 6;
|
|
vbd.usage = Graphics::BUFFER_USAGE_STATIC;
|
|
|
|
// VertexBuffer
|
|
m_bgBuffer.Reset(m_renderer->CreateVertexBuffer(vbd));
|
|
m_bgBuffer->Populate(bgArr);
|
|
}
|
|
|
|
m_renderer->DrawBuffer(m_bgBuffer.Get(), m_bgState, Graphics::vtxColorMaterial, Graphics::TRIANGLES);
|
|
}
|
|
|
|
//Draw grid and axes
|
|
void ModelViewer::DrawGrid(const matrix4x4f &trans, float radius)
|
|
{
|
|
assert(m_options.showGrid);
|
|
|
|
const float dist = zoom_distance(m_baseDistance, m_zoom);
|
|
|
|
const float max = std::min(powf(10, ceilf(log10f(dist))), ceilf(radius / m_options.gridInterval) * m_options.gridInterval);
|
|
|
|
static std::vector<vector3f> points;
|
|
points.clear();
|
|
|
|
for (float x = -max; x <= max; x += m_options.gridInterval) {
|
|
points.push_back(vector3f(x, 0, -max));
|
|
points.push_back(vector3f(x, 0, max));
|
|
points.push_back(vector3f(0, x, -max));
|
|
points.push_back(vector3f(0, x, max));
|
|
|
|
points.push_back(vector3f(x, -max, 0));
|
|
points.push_back(vector3f(x, max, 0));
|
|
points.push_back(vector3f(0, -max, x));
|
|
points.push_back(vector3f(0, max, x));
|
|
|
|
points.push_back(vector3f(-max, x, 0));
|
|
points.push_back(vector3f(max, x, 0));
|
|
points.push_back(vector3f(-max, 0, x));
|
|
points.push_back(vector3f(max, 0, x));
|
|
}
|
|
|
|
m_renderer->SetTransform(trans);
|
|
m_gridLines.SetData(points.size(), &points[0], Color(128, 128, 128));
|
|
m_gridLines.Draw(m_renderer, m_bgState);
|
|
|
|
// industry-standard red/green/blue XYZ axis indiactor
|
|
m_renderer->SetTransform(trans * matrix4x4f::ScaleMatrix(radius));
|
|
Graphics::Drawables::GetAxes3DDrawable(m_renderer)->Draw(m_renderer);
|
|
}
|
|
|
|
void ModelViewer::DrawModel(const matrix4x4f &mv)
|
|
{
|
|
assert(m_model);
|
|
|
|
m_model->UpdateAnimations();
|
|
|
|
m_model->SetDebugFlags(
|
|
(m_options.showAabb ? SceneGraph::Model::DEBUG_BBOX : 0x0) |
|
|
(m_options.showCollMesh ? SceneGraph::Model::DEBUG_COLLMESH : 0x0) |
|
|
(m_options.showTags ? SceneGraph::Model::DEBUG_TAGS : 0x0) |
|
|
(m_options.showDockingLocators ? SceneGraph::Model::DEBUG_DOCKING : 0x0) |
|
|
(m_options.wireframe ? SceneGraph::Model::DEBUG_WIREFRAME : 0x0));
|
|
|
|
m_model->Render(mv);
|
|
m_navLights->Render(m_renderer);
|
|
}
|
|
|
|
void ModelViewer::Update(float deltaTime)
|
|
{
|
|
HandleInput();
|
|
|
|
UpdateLights();
|
|
UpdateCamera(deltaTime);
|
|
UpdateShield();
|
|
|
|
// render the gradient backdrop
|
|
DrawBackground();
|
|
|
|
//update animations, draw model etc.
|
|
if (m_model) {
|
|
m_navLights->Update(deltaTime);
|
|
m_shields->SetEnabled(m_options.showShields || m_shieldIsHit);
|
|
|
|
//Calculate the impact's radius dependant on time
|
|
const float dif1 = 0.34 - (-1.48f);
|
|
const float dif2 = m_shieldHitPan - (-1.48f);
|
|
//Range from start (0.0) to end (1.0)
|
|
const float dif = dif2 / (dif1 * 1.0f);
|
|
|
|
m_shields->Update(m_options.showShields ? 1.0f : (1.0f - dif), 1.0f);
|
|
|
|
// setup rendering
|
|
if (!m_options.orthoView) {
|
|
m_renderer->SetPerspectiveProjection(85, Graphics::GetScreenWidth() / float(Graphics::GetScreenHeight()), 0.1f, 100000.f);
|
|
} else {
|
|
/* TODO: Zoom in ortho mode seems don't work as in perspective mode,
|
|
/ I change "screen dimensions" to avoid the problem.
|
|
/ However the zoom needs more care
|
|
*/
|
|
if (m_zoom <= 0.0) m_zoom = 0.01;
|
|
float screenW = Graphics::GetScreenWidth() * m_zoom / 10;
|
|
float screenH = Graphics::GetScreenHeight() * m_zoom / 10;
|
|
matrix4x4f orthoMat = matrix4x4f::OrthoFrustum(-screenW, screenW, -screenH, screenH, 0.1f, 100000.0f);
|
|
orthoMat.ClearToRotOnly();
|
|
m_renderer->SetProjection(orthoMat);
|
|
}
|
|
|
|
m_renderer->SetTransform(matrix4x4f::Identity());
|
|
|
|
// calc camera info
|
|
matrix4x4f mv;
|
|
float zd = 0;
|
|
if (m_options.mouselookEnabled) {
|
|
mv = m_viewRot.Transpose() * matrix4x4f::Translation(-m_viewPos);
|
|
} else {
|
|
m_rotX = Clamp(m_rotX, -90.0f, 90.0f);
|
|
matrix4x4f rot = matrix4x4f::Identity();
|
|
rot.RotateX(DEG2RAD(-m_rotX));
|
|
rot.RotateY(DEG2RAD(-m_rotY));
|
|
if (m_options.orthoView)
|
|
zd = -m_baseDistance;
|
|
else
|
|
zd = -zoom_distance(m_baseDistance, m_zoom);
|
|
mv = matrix4x4f::Translation(0.0f, 0.0f, zd) * rot;
|
|
}
|
|
|
|
// draw the model itself
|
|
DrawModel(mv);
|
|
|
|
// helper rendering
|
|
if (m_options.showLandingPad) {
|
|
if (!m_scaleModel) CreateTestResources();
|
|
m_scaleModel->Render(mv * matrix4x4f::Translation(0.f, m_landingMinOffset, 0.f));
|
|
}
|
|
|
|
if (m_options.showGrid) {
|
|
DrawGrid(mv, m_model->GetDrawClipRadius());
|
|
}
|
|
}
|
|
|
|
if (m_options.showUI && !m_screenshotQueued) {
|
|
DrawPiGui();
|
|
}
|
|
if (m_screenshotQueued) {
|
|
m_screenshotQueued = false;
|
|
Screenshot();
|
|
}
|
|
|
|
// if we've requested a different model then switch too it
|
|
if (!m_requestedModelName.empty()) {
|
|
SetModel(m_requestedModelName);
|
|
ResetCamera();
|
|
m_requestedModelName.clear();
|
|
}
|
|
}
|
|
|
|
void ModelViewer::SetDecals(const std::string &texname)
|
|
{
|
|
if (!m_model) return;
|
|
|
|
m_decalTexture = Graphics::TextureBuilder::Decal(stringf("textures/decals/%0.dds", texname)).GetOrCreateTexture(m_renderer, "decal");
|
|
|
|
m_model->SetDecalTexture(m_decalTexture, 0);
|
|
m_model->SetDecalTexture(m_decalTexture, 1);
|
|
m_model->SetDecalTexture(m_decalTexture, 2);
|
|
m_model->SetDecalTexture(m_decalTexture, 3);
|
|
}
|
|
|
|
void ModelViewer::SetupAxes()
|
|
{
|
|
auto *page = m_input->GetBindingPage("ModelViewer");
|
|
auto *group = page->GetBindingGroup("View");
|
|
|
|
#define AXIS(name, axis, positive, negative) m_input->AddAxisBinding(name, group, KeyBindings::AxisBinding(axis, positive, negative))
|
|
#define ACTION(name, b1, b2) m_input->AddActionBinding(name, group, KeyBindings::ActionBinding(b1, b2))
|
|
|
|
m_zoomAxis = AXIS("BindZoomAxis", {}, SDLK_EQUALS, SDLK_MINUS);
|
|
|
|
m_moveForward = AXIS("BindMoveForward", {}, SDLK_w, SDLK_s);
|
|
m_moveLeft = AXIS("BindMoveLeft", {}, SDLK_a, SDLK_d);
|
|
m_moveUp = AXIS("BindMoveUp", {}, SDLK_q, SDLK_e);
|
|
|
|
// Like Blender, but a bit different because we like that
|
|
// 1 - front (+ctrl back)
|
|
// 7 - top (+ctrl bottom)
|
|
// 3 - left (+ctrl right)
|
|
// 2,4,6,8 incrementally rotate
|
|
|
|
m_viewFront = ACTION("BindViewFront", SDLK_KP_1, SDLK_m);
|
|
m_viewFront->onPress.connect([=]() {
|
|
this->ChangeCameraPreset(m_input->KeyModState() & KMOD_CTRL ? CameraPreset::Back : CameraPreset::Front);
|
|
});
|
|
|
|
m_viewLeft = ACTION("BindViewLeft", SDLK_KP_3, SDLK_PERIOD);
|
|
m_viewLeft->onPress.connect([=]() {
|
|
this->ChangeCameraPreset(m_input->KeyModState() & KMOD_CTRL ? CameraPreset::Right : CameraPreset::Left);
|
|
});
|
|
|
|
m_viewTop = ACTION("BindViewTop", SDLK_KP_7, SDLK_u);
|
|
m_viewTop->onPress.connect([=]() {
|
|
this->ChangeCameraPreset(m_input->KeyModState() & KMOD_CTRL ? CameraPreset::Bottom : CameraPreset::Top);
|
|
});
|
|
|
|
m_rotateViewLeft = AXIS("BindRotateViewLeft", {}, SDLK_KP_6, SDLK_KP_4);
|
|
m_rotateViewUp = AXIS("BindRotateViewUp", {}, SDLK_KP_8, SDLK_KP_2);
|
|
}
|
|
|
|
void ModelViewer::HandleInput()
|
|
{
|
|
// FIXME: better handle dispatching input to Action/Axis bindings
|
|
|
|
/*
|
|
* Special butans
|
|
*
|
|
* Space: reset camera
|
|
* Keyboard: rotate view
|
|
* plus/minus: zoom view
|
|
* Shift: zoom faster
|
|
* printscr - screenshots
|
|
* tab - toggle ui (always invisible on screenshots)
|
|
* g - grid
|
|
* o - switch orthographic<->perspective
|
|
*
|
|
*/
|
|
|
|
if (m_input->IsKeyPressed(SDLK_ESCAPE)) {
|
|
if (m_model) {
|
|
ClearModel();
|
|
UpdateModelList();
|
|
UpdateDecalList();
|
|
} else {
|
|
RequestEndLifecycle();
|
|
}
|
|
}
|
|
|
|
if (m_input->IsKeyPressed(SDLK_SPACE)) {
|
|
ResetCamera();
|
|
ResetThrusters();
|
|
}
|
|
|
|
if (m_input->IsKeyPressed(SDLK_TAB))
|
|
m_options.showUI = !m_options.showUI;
|
|
|
|
if (m_input->IsKeyPressed(SDLK_t))
|
|
m_options.showTags = !m_options.showTags;
|
|
|
|
if (m_input->IsKeyPressed(SDLK_PRINTSCREEN))
|
|
m_screenshotQueued = true;
|
|
|
|
if (m_input->IsKeyPressed(SDLK_g))
|
|
ToggleGrid();
|
|
|
|
if (m_input->IsKeyPressed(SDLK_o))
|
|
m_options.orthoView = !m_options.orthoView;
|
|
|
|
if (m_input->IsKeyPressed(SDLK_z))
|
|
m_options.wireframe = !m_options.wireframe;
|
|
|
|
if (m_input->IsKeyPressed(SDLK_f))
|
|
ToggleViewControlMode();
|
|
|
|
if (m_input->IsKeyPressed(SDLK_F6))
|
|
SaveModelToBinary();
|
|
|
|
if (m_input->IsKeyPressed(SDLK_F11) && m_input->KeyModState() & KMOD_SHIFT)
|
|
m_renderer->ReloadShaders();
|
|
|
|
//landing pad test
|
|
if (m_input->IsKeyPressed(SDLK_p)) {
|
|
m_options.showLandingPad = !m_options.showLandingPad;
|
|
AddLog(stringf("Scale/landing pad test %0", m_options.showLandingPad ? "on" : "off"));
|
|
}
|
|
|
|
// random colors, eastereggish
|
|
if (m_input->IsKeyPressed(SDLK_r))
|
|
SetRandomColor();
|
|
}
|
|
|
|
void ModelViewer::UpdateModelList()
|
|
{
|
|
m_fileNames.clear();
|
|
|
|
const std::string basepath("models");
|
|
FileSystem::FileSource &fileSource = FileSystem::gameDataFiles;
|
|
for (FileSystem::FileEnumerator files(fileSource, basepath, FileSystem::FileEnumerator::Recurse); !files.Finished(); files.Next()) {
|
|
const FileSystem::FileInfo &info = files.Current();
|
|
const std::string &fpath = info.GetPath();
|
|
|
|
//check it's the expected type
|
|
if (info.IsFile()) {
|
|
if (ends_with_ci(fpath, ".model"))
|
|
m_fileNames.push_back(info.GetName().substr(0, info.GetName().size() - 6));
|
|
else if (ends_with_ci(fpath, ".sgm"))
|
|
m_fileNames.push_back(info.GetName());
|
|
}
|
|
}
|
|
}
|
|
|
|
void ModelViewer::UpdateDecalList()
|
|
{
|
|
m_decals.clear();
|
|
m_currentDecal = 0;
|
|
|
|
const std::string basepath("textures/decals");
|
|
FileSystem::FileSource &fileSource = FileSystem::gameDataFiles;
|
|
for (FileSystem::FileEnumerator files(fileSource, basepath); !files.Finished(); files.Next()) {
|
|
const FileSystem::FileInfo &info = files.Current();
|
|
const std::string &fpath = info.GetPath();
|
|
|
|
//check it's the expected type
|
|
if (info.IsFile() && ends_with_ci(fpath, ".dds")) {
|
|
m_decals.push_back(info.GetName().substr(0, info.GetName().size() - 4));
|
|
}
|
|
}
|
|
}
|
|
|
|
void ModelViewer::ResetCamera()
|
|
{
|
|
m_baseDistance = m_model ? m_model->GetDrawClipRadius() * 1.5f : 100.f;
|
|
m_rotX = m_rotY = 0.f;
|
|
m_zoom = 0.f;
|
|
}
|
|
|
|
void ModelViewer::ResetThrusters()
|
|
{
|
|
m_angularThrust = vector3f{};
|
|
m_linearThrust = vector3f{};
|
|
}
|
|
|
|
void ModelViewer::Screenshot()
|
|
{
|
|
char buf[256];
|
|
const time_t t = time(0);
|
|
const struct tm *_tm = localtime(&t);
|
|
strftime(buf, sizeof(buf), "modelviewer-%Y%m%d-%H%M%S.png", _tm);
|
|
Graphics::ScreendumpState sd;
|
|
m_renderer->Screendump(sd);
|
|
write_screenshot(sd, buf);
|
|
AddLog(stringf("Screenshot %0 saved", buf));
|
|
}
|
|
|
|
void ModelViewer::SaveModelToBinary()
|
|
{
|
|
if (!m_model)
|
|
return AddLog("No current model to binarize");
|
|
|
|
//load the current model in a pristine state (no navlights, shields...)
|
|
//and then save it into binary
|
|
|
|
std::unique_ptr<SceneGraph::Model> model;
|
|
try {
|
|
SceneGraph::Loader ld(m_renderer);
|
|
model.reset(ld.LoadModel(m_modelName));
|
|
} catch (...) {
|
|
//minimal error handling, this is not expected to happen since we got this far.
|
|
AddLog("Could not load model");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
SceneGraph::BinaryConverter bc(m_renderer);
|
|
bc.Save(m_modelName, model.get());
|
|
AddLog("Saved binary model file");
|
|
} catch (const CouldNotOpenFileException &) {
|
|
AddLog("Could not open file or directory for writing");
|
|
} catch (const CouldNotWriteToFileException &) {
|
|
AddLog("Error while writing to file");
|
|
}
|
|
}
|
|
|
|
void ModelViewer::SetAnimation(SceneGraph::Animation *anim)
|
|
{
|
|
m_currentAnimation = anim;
|
|
}
|
|
|
|
void ModelViewer::SetModel(const std::string &filename)
|
|
{
|
|
AddLog(stringf("Loading model %0...", filename));
|
|
|
|
//this is necessary to reload textures
|
|
m_renderer->RemoveAllCachedTextures();
|
|
|
|
ClearModel();
|
|
|
|
try {
|
|
if (ends_with_ci(filename, ".sgm")) {
|
|
//binary loader expects extension-less name. Might want to change this.
|
|
m_modelName = filename.substr(0, filename.size() - 4);
|
|
SceneGraph::BinaryConverter bc(m_renderer);
|
|
m_model.reset(bc.Load(m_modelName));
|
|
} else {
|
|
m_modelName = filename;
|
|
SceneGraph::Loader loader(m_renderer, true);
|
|
m_model.reset(loader.LoadModel(filename));
|
|
|
|
//dump warnings
|
|
for (std::vector<std::string>::const_iterator it = loader.GetLogMessages().begin();
|
|
it != loader.GetLogMessages().end(); ++it) {
|
|
AddLog(*it);
|
|
Output("%s\n", (*it).c_str());
|
|
}
|
|
}
|
|
|
|
Shields::ReparentShieldNodes(m_model.get());
|
|
|
|
//set decal textures, max 4 supported.
|
|
//Identical texture at the moment
|
|
SetDecals("pioneer");
|
|
Output("\n\n");
|
|
|
|
SceneGraph::DumpVisitor d(m_model.get());
|
|
m_model->GetRoot()->Accept(d);
|
|
AddLog(d.GetModelStatistics());
|
|
|
|
// If we've got the tag_landing set then use it for an offset otherwise grab the AABB
|
|
const SceneGraph::MatrixTransform *mt = m_model->FindTagByName("tag_landing");
|
|
if (mt)
|
|
m_landingMinOffset = mt->GetTransform().GetTranslate().y;
|
|
else if (m_model->GetCollisionMesh())
|
|
m_landingMinOffset = m_model->GetCollisionMesh()->GetAabb().min.y;
|
|
else
|
|
m_landingMinOffset = 0.0f;
|
|
|
|
//note: stations won't demonstrate full docking light logic in MV
|
|
m_navLights.reset(new NavLights(m_model.get()));
|
|
m_navLights->SetEnabled(true);
|
|
|
|
m_shields.reset(new Shields(m_model.get()));
|
|
} catch (SceneGraph::LoadingError &err) {
|
|
// report the error and show model picker.
|
|
m_model.reset();
|
|
AddLog(stringf("Could not load model %0: %1", filename, err.what()));
|
|
}
|
|
|
|
if (m_model)
|
|
onModelChanged.emit();
|
|
}
|
|
|
|
void ModelViewer::OnModelChanged()
|
|
{
|
|
ResetThrusters();
|
|
m_model->SetColors(m_colors);
|
|
|
|
SceneGraph::FindNodeVisitor visitor(SceneGraph::FindNodeVisitor::MATCH_NAME_STARTSWITH, "thruster_");
|
|
m_model->GetRoot()->Accept(visitor);
|
|
m_modelIsShip = !visitor.GetResults().empty();
|
|
m_modelSupportsDecals = m_model->SupportsDecals();
|
|
|
|
m_modelHasShields = m_shields.get() && m_shields->GetFirstShieldMesh();
|
|
|
|
m_animations = m_model->GetAnimations();
|
|
m_currentAnimation = nullptr;
|
|
|
|
m_patterns.clear();
|
|
m_currentPattern = 0;
|
|
m_modelSupportsPatterns = m_model->SupportsPatterns();
|
|
if (m_modelSupportsPatterns) {
|
|
for (const auto &pattern : m_model->GetPatterns()) {
|
|
m_patterns.push_back(pattern.name);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ModelViewer::DrawModelSelector()
|
|
{
|
|
vector2f selectorSize = m_windowSize * vector2f(0.4, 0.8);
|
|
ImGui::SetNextWindowSize({ selectorSize.x, selectorSize.y }, ImGuiCond_Always);
|
|
vector2f selectorPos = m_windowSize * 0.5 - selectorSize * 0.5;
|
|
ImGui::SetNextWindowPos({ selectorPos.x, selectorPos.y }, ImGuiCond_Always);
|
|
|
|
auto flags = ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize;
|
|
bool b_open = true; // Use the window close button to quit the modelviewer
|
|
if (ImGui::Begin("Select Model", &b_open, flags)) {
|
|
if (ImGui::BeginChild("FileList", ImVec2(0.0, -ImGui::GetFrameHeightWithSpacing()))) {
|
|
for (const auto &name : m_fileNames) {
|
|
if (ImGui::Selectable(name.c_str())) {
|
|
m_requestedModelName = name;
|
|
}
|
|
}
|
|
}
|
|
ImGui::EndChild();
|
|
}
|
|
ImGui::End();
|
|
|
|
if (!b_open || ImGui::IsKeyPressed(ImGuiKey_Escape)) {
|
|
RequestEndLifecycle();
|
|
}
|
|
}
|
|
|
|
void ModelViewer::DrawShipControls()
|
|
{
|
|
if (m_modelIsShip) {
|
|
ImGui::Columns(3, nullptr, false);
|
|
ImGui::TextUnformatted("Linear Thrust");
|
|
ImGui::Spacing();
|
|
|
|
bool valuesChanged = false;
|
|
valuesChanged |= ImGui::SliderFloat("X", &m_linearThrust.x, -1.0, 1.0);
|
|
valuesChanged |= ImGui::SliderFloat("Y", &m_linearThrust.y, -1.0, 1.0);
|
|
valuesChanged |= ImGui::SliderFloat("Z", &m_linearThrust.z, -1.0, 1.0);
|
|
|
|
ImGui::NextColumn();
|
|
ImGui::TextUnformatted("Angular Thrust");
|
|
ImGui::Spacing();
|
|
|
|
valuesChanged |= ImGui::SliderFloat("Pitch", &m_angularThrust.x, -1.0, 1.0);
|
|
valuesChanged |= ImGui::SliderFloat("Yaw", &m_angularThrust.y, -1.0, 1.0);
|
|
valuesChanged |= ImGui::SliderFloat("Roll", &m_angularThrust.z, -1.0, 1.0);
|
|
|
|
if (valuesChanged)
|
|
m_model->SetThrust(m_linearThrust, m_angularThrust);
|
|
|
|
ImGui::NextColumn();
|
|
ImGui::TextUnformatted("Pattern Colors");
|
|
ImGui::Spacing();
|
|
|
|
valuesChanged = false;
|
|
valuesChanged |= ImGui::ColorEdit3("Color 1", m_colors[0]);
|
|
valuesChanged |= ImGui::ColorEdit3("Color 2", m_colors[1]);
|
|
valuesChanged |= ImGui::ColorEdit3("Color 3", m_colors[2]);
|
|
|
|
if (valuesChanged)
|
|
m_model->SetColors(m_colors);
|
|
|
|
ImGui::Columns(1);
|
|
}
|
|
|
|
if (m_currentAnimation) {
|
|
ImGui::Spacing();
|
|
float progress = m_currentAnimation->GetProgress();
|
|
bool changed = ImGui::SliderFloat("Animation Progress", &progress, 0.0, m_currentAnimation->GetDuration());
|
|
if (changed) {
|
|
m_currentAnimation->SetProgress(progress);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ModelViewer::DrawModelOptions()
|
|
{
|
|
ImGui::TextUnformatted(m_modelName.c_str());
|
|
|
|
if (ImGui::Button("Reload Model"))
|
|
ReloadModel();
|
|
|
|
ImGui::NewLine();
|
|
|
|
if (ImGui::Button("Show Collision Mesh"))
|
|
ToggleCollMesh();
|
|
|
|
if (ImGui::Button("Toggle Grid Mode"))
|
|
ToggleGrid();
|
|
|
|
if (ImGui::Button("Set Random Colors"))
|
|
SetRandomColor();
|
|
|
|
if (m_modelHasShields) {
|
|
ImGui::NewLine();
|
|
|
|
if (ImGui::Button("Show Shields"))
|
|
ToggleShowShields();
|
|
|
|
if (ImGui::Button("Test Shield Hit"))
|
|
HitIt();
|
|
}
|
|
|
|
if (m_modelIsShip) {
|
|
if (ImGui::Button("Attach Test Guns"))
|
|
ToggleGuns();
|
|
}
|
|
|
|
ImGui::NewLine();
|
|
|
|
if (m_modelSupportsPatterns) {
|
|
const char *preview_name = m_patterns[m_currentPattern].c_str();
|
|
if (ImGui::BeginCombo("Pattern", preview_name)) {
|
|
for (size_t idx = 0; idx < m_patterns.size(); idx++) {
|
|
const bool selected = m_currentPattern == idx;
|
|
if (ImGui::Selectable(m_patterns[idx].c_str(), selected) && !selected) {
|
|
m_currentPattern = idx;
|
|
m_model->SetPattern(idx);
|
|
}
|
|
}
|
|
|
|
ImGui::EndCombo();
|
|
}
|
|
}
|
|
|
|
if (m_modelSupportsDecals) {
|
|
const char *preview_name = m_decals[m_currentDecal].c_str();
|
|
if (ImGui::BeginCombo("Decals", preview_name)) {
|
|
for (size_t idx = 0; idx < m_decals.size(); idx++) {
|
|
const bool selected = m_currentDecal == idx;
|
|
if (ImGui::Selectable(m_decals[idx].c_str(), selected) && !selected) {
|
|
m_currentDecal = idx;
|
|
SetDecals(m_decals[idx]);
|
|
}
|
|
}
|
|
|
|
ImGui::EndCombo();
|
|
}
|
|
}
|
|
|
|
const char *anim_name = m_currentAnimation ? m_currentAnimation->GetName().c_str() : "None";
|
|
if (ImGui::BeginCombo("Animation", anim_name)) {
|
|
for (const auto anim : m_animations) {
|
|
const bool selected = m_currentAnimation == anim;
|
|
if (ImGui::Selectable(anim->GetName().c_str(), selected) && !selected) {
|
|
// selected a new animation entry
|
|
SetAnimation(anim);
|
|
}
|
|
}
|
|
|
|
if (ImGui::Selectable("None", !m_currentAnimation) && m_currentAnimation) {
|
|
// Changed to no animation
|
|
SetAnimation(nullptr);
|
|
}
|
|
|
|
ImGui::EndCombo();
|
|
}
|
|
|
|
static std::vector<std::string> lightSetups = {
|
|
"Front Light", "Two-point", "Backlight"
|
|
};
|
|
|
|
uint32_t ¤tLights = m_options.lightPreset;
|
|
if (ImGui::BeginCombo("Lights", lightSetups[currentLights].c_str())) {
|
|
for (size_t idx = 0; idx < lightSetups.size(); idx++) {
|
|
const bool selected = currentLights == idx;
|
|
if (ImGui::Selectable(lightSetups[idx].c_str(), selected) && !selected) {
|
|
currentLights = idx;
|
|
}
|
|
}
|
|
|
|
ImGui::EndCombo();
|
|
}
|
|
}
|
|
|
|
void ModelViewer::DrawLog()
|
|
{
|
|
ImGui::SetNextWindowPos(ImVec2(m_windowSize.x - m_logWindowSize.x, m_windowSize.y - m_logWindowSize.y));
|
|
ImGui::SetNextWindowSize(ImVec2(m_logWindowSize.x, m_logWindowSize.y));
|
|
const auto flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoSavedSettings;
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0);
|
|
if (ImGui::Begin("LogWindow", nullptr, flags)) {
|
|
if (ImGui::BeginChild("ScrollArea")) {
|
|
for (const auto &message : m_log) {
|
|
ImGui::TextUnformatted(message.c_str());
|
|
}
|
|
|
|
if (m_resetLogScroll || ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) {
|
|
ImGui::SetScrollHereY(1.0f);
|
|
m_resetLogScroll = false;
|
|
}
|
|
}
|
|
ImGui::EndChild();
|
|
}
|
|
ImGui::End();
|
|
ImGui::PopStyleVar(1);
|
|
}
|
|
|
|
void ModelViewer::DrawPiGui()
|
|
{
|
|
m_windowSize = vector2f(Graphics::GetScreenWidth(), Graphics::GetScreenHeight());
|
|
|
|
if (m_model) {
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0);
|
|
{
|
|
const auto flags = ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize;
|
|
ImGui::SetNextWindowPos({ 0, 0 });
|
|
ImGui::SetNextWindowSize({ m_windowSize.x / 4.0f, m_windowSize.y - m_animWindowSize.y });
|
|
if (ImGui::Begin("Model Options", nullptr, flags)) {
|
|
DrawModelOptions();
|
|
}
|
|
ImGui::End();
|
|
}
|
|
|
|
if (m_modelIsShip) {
|
|
const auto flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize;
|
|
ImGui::SetNextWindowPos({ 0, m_windowSize.y - m_animWindowSize.y });
|
|
ImGui::SetNextWindowSize({ m_windowSize.x - m_logWindowSize.x, m_animWindowSize.y });
|
|
if (ImGui::Begin("Model Controls", nullptr, flags)) {
|
|
DrawShipControls();
|
|
}
|
|
ImGui::End();
|
|
}
|
|
ImGui::PopStyleVar(1);
|
|
|
|
} else {
|
|
DrawModelSelector();
|
|
}
|
|
|
|
DrawLog();
|
|
}
|
|
|
|
void ModelViewer::UpdateCamera(float deltaTime)
|
|
{
|
|
static const float BASE_ZOOM_RATE = 1.0f / 12.0f;
|
|
float zoomRate = (BASE_ZOOM_RATE * 8.0f) * deltaTime;
|
|
float rotateRate = 25.f * deltaTime;
|
|
float moveRate = 10.0f * deltaTime;
|
|
|
|
bool isShiftPressed = m_input->KeyState(SDLK_LSHIFT);
|
|
|
|
if (isShiftPressed) {
|
|
zoomRate *= 8.0f;
|
|
moveRate *= 4.0f;
|
|
rotateRate *= 4.0f;
|
|
}
|
|
|
|
std::array<int, 2> mouseMotion;
|
|
m_input->GetMouseMotion(mouseMotion.data());
|
|
bool rightMouseDown = m_input->MouseButtonState(SDL_BUTTON_RIGHT);
|
|
if (m_options.mouselookEnabled) {
|
|
const float degrees_per_pixel = 0.2f;
|
|
if (!rightMouseDown) {
|
|
// yaw and pitch
|
|
const float rot_y = degrees_per_pixel * mouseMotion[0];
|
|
const float rot_x = degrees_per_pixel * mouseMotion[1];
|
|
const matrix3x3f rot =
|
|
matrix3x3f::RotateX(DEG2RAD(rot_x)) *
|
|
matrix3x3f::RotateY(DEG2RAD(rot_y));
|
|
|
|
m_viewRot = m_viewRot * rot;
|
|
} else {
|
|
// roll
|
|
m_viewRot = m_viewRot * matrix3x3f::RotateZ(DEG2RAD(degrees_per_pixel * mouseMotion[0]));
|
|
}
|
|
|
|
vector3f motion(
|
|
m_moveLeft->GetValue(),
|
|
m_moveUp->GetValue(),
|
|
m_moveForward->GetValue());
|
|
|
|
m_viewPos += m_viewRot * motion;
|
|
} else {
|
|
//zoom
|
|
m_zoom += m_zoomAxis->GetValue() * BASE_ZOOM_RATE;
|
|
|
|
//zoom with mouse wheel
|
|
int mouseWheel = m_input->GetMouseWheel();
|
|
if (mouseWheel) m_zoom += mouseWheel > 0 ? -BASE_ZOOM_RATE : BASE_ZOOM_RATE;
|
|
|
|
m_zoom = Clamp(m_zoom, -10.0f, 10.0f); // distance range: [baseDistance * 1/1024, baseDistance * 1024]
|
|
|
|
//rotate
|
|
|
|
if (m_input->IsKeyDown(SDLK_UP)) m_rotX += rotateRate;
|
|
if (m_input->IsKeyDown(SDLK_DOWN)) m_rotX -= rotateRate;
|
|
if (m_input->IsKeyDown(SDLK_LEFT)) m_rotY += rotateRate;
|
|
if (m_input->IsKeyDown(SDLK_RIGHT)) m_rotY -= rotateRate;
|
|
|
|
m_rotX += rotateRate * m_rotateViewLeft->GetValue();
|
|
m_rotY += rotateRate * -m_rotateViewUp->GetValue();
|
|
|
|
//mouse rotate when right button held
|
|
if (rightMouseDown) {
|
|
m_rotY += 0.2f * mouseMotion[0];
|
|
m_rotX += 0.2f * mouseMotion[1];
|
|
}
|
|
}
|
|
}
|
|
|
|
void ModelViewer::UpdateLights()
|
|
{
|
|
using Graphics::Light;
|
|
std::vector<Light> lights;
|
|
|
|
switch (m_options.lightPreset) {
|
|
case 0:
|
|
//Front white
|
|
lights.push_back(Light(Light::LIGHT_DIRECTIONAL, az_el_to_dir(90, 0), Color::WHITE, Color::WHITE));
|
|
lights.push_back(Light(Light::LIGHT_DIRECTIONAL, az_el_to_dir(0, -90), Color(13, 13, 26), Color::WHITE));
|
|
break;
|
|
case 1:
|
|
//Two-point
|
|
lights.push_back(Light(Light::LIGHT_DIRECTIONAL, az_el_to_dir(120, 0), Color(230, 204, 204), Color::WHITE));
|
|
lights.push_back(Light(Light::LIGHT_DIRECTIONAL, az_el_to_dir(-30, -90), Color(178, 128, 0), Color::WHITE));
|
|
break;
|
|
case 2:
|
|
//Backlight
|
|
lights.push_back(Light(Light::LIGHT_DIRECTIONAL, az_el_to_dir(-75, 20), Color::WHITE, Color::WHITE));
|
|
lights.push_back(Light(Light::LIGHT_DIRECTIONAL, az_el_to_dir(0, -90), Color(13, 13, 26), Color::WHITE));
|
|
break;
|
|
case 3:
|
|
//4 lights
|
|
lights.push_back(Light(Light::LIGHT_DIRECTIONAL, az_el_to_dir(0, 90), Color::YELLOW, Color::WHITE));
|
|
lights.push_back(Light(Light::LIGHT_DIRECTIONAL, az_el_to_dir(0, -90), Color::GREEN, Color::WHITE));
|
|
lights.push_back(Light(Light::LIGHT_DIRECTIONAL, az_el_to_dir(0, 45), Color::BLUE, Color::WHITE));
|
|
lights.push_back(Light(Light::LIGHT_DIRECTIONAL, az_el_to_dir(0, -45), Color::WHITE, Color::WHITE));
|
|
break;
|
|
};
|
|
|
|
m_renderer->SetLights(int(lights.size()), &lights[0]);
|
|
}
|