mirror of
https://github.com/libretro/libretro-3dengine.git
synced 2026-11-07 11:06:04 -10:00
945 lines
27 KiB
C++
945 lines
27 KiB
C++
/*
|
|
* Libretro 3DEngine
|
|
* Copyright (C) 2013-2014 - Hans-Kristian Arntzen
|
|
* Copyright (C) 2013-2014 - Daniel De Matteis
|
|
*
|
|
* Libretro 3DEngine 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 Found-
|
|
* ation, either version 3 of the License, or (at your option) any later version.
|
|
*
|
|
* Libretro 3DEngine 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 Libretro 3DEngine.
|
|
* If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "libretro.h"
|
|
#include "program.h"
|
|
#include "../engine/mesh.hpp"
|
|
#include "../engine/texture.hpp"
|
|
#include "../engine/object.hpp"
|
|
#include "collision_detection.hpp"
|
|
#include "location_math.h"
|
|
|
|
#ifdef HAVE_VULKAN
|
|
#include "../engine/vk_renderer.hpp"
|
|
#endif
|
|
|
|
#include <glsym/glsym.h>
|
|
|
|
#include <vector>
|
|
#include <string.h>
|
|
|
|
using namespace glm;
|
|
|
|
static bool first_init = true;
|
|
static std::string mesh_path;
|
|
static bool discard_hack_enable = false;
|
|
|
|
static std::vector<std1::shared_ptr<GL::Mesh> > meshes;
|
|
static std1::shared_ptr<GL::Texture> blank;
|
|
|
|
//forward decls
|
|
static void scenewalker_reset_mesh_path(void);
|
|
|
|
static float light_r;
|
|
static float light_g;
|
|
static float light_b;
|
|
static float ambient_light_r;
|
|
static float ambient_light_g;
|
|
static float ambient_light_b;
|
|
|
|
static bool update;
|
|
|
|
static vec3 player_size(0.4f, 0.8f, 0.4f);
|
|
|
|
enum
|
|
{
|
|
MODE_MODELVIEWER = 0,
|
|
MODE_SCENEWALKER = 1
|
|
};
|
|
|
|
unsigned mode_engine = MODE_MODELVIEWER;
|
|
|
|
static bool scene_flip_y(void) { return !hw_render.bottom_left_origin; }
|
|
|
|
static struct retro_vr_frame_state vr_frame;
|
|
bool modelviewer_vr_capable(void) { return mode_engine == MODE_SCENEWALKER; }
|
|
|
|
static float player_view_deg_x;
|
|
static float player_view_deg_y;
|
|
static vec3 player_pos(0, 2, 0);
|
|
|
|
extern bool vr_user_enable;
|
|
|
|
/* Per-frame scene state written by the input code and consumed by whichever
|
|
* renderer is active (GL meshes or the Vulkan renderer). */
|
|
static mat4 scene_model(1.0f);
|
|
static mat4 scene_view(1.0f);
|
|
static vec3 scene_eye(0.0f, 0.0f, 0.0f);
|
|
|
|
#ifdef HAVE_VULKAN
|
|
static bool vk_scene_ok = false;
|
|
#endif
|
|
|
|
static vec3 scenewalker_check_input(void)
|
|
{
|
|
input_poll_cb();
|
|
|
|
int analog_x = input_state_cb(0, RETRO_DEVICE_ANALOG,
|
|
RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_X);
|
|
|
|
int analog_y = input_state_cb(0, RETRO_DEVICE_ANALOG,
|
|
RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_Y);
|
|
|
|
int analog_ry = input_state_cb(0, RETRO_DEVICE_ANALOG,
|
|
RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_Y);
|
|
|
|
int analog_rx = input_state_cb(0, RETRO_DEVICE_ANALOG,
|
|
RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_X);
|
|
|
|
static bool old_jump;
|
|
bool new_jump = input_state_cb(0, RETRO_DEVICE_JOYPAD, 0,
|
|
RETRO_DEVICE_ID_JOYPAD_B);
|
|
|
|
bool jump = new_jump && !old_jump;
|
|
old_jump = new_jump;
|
|
|
|
bool run_pressed = input_state_cb(0, RETRO_DEVICE_JOYPAD, 0,
|
|
RETRO_DEVICE_ID_JOYPAD_Y);
|
|
bool mouselook_pressed = input_state_cb(0, RETRO_DEVICE_JOYPAD, 0,
|
|
RETRO_DEVICE_ID_JOYPAD_X);
|
|
|
|
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0,
|
|
RETRO_DEVICE_ID_JOYPAD_LEFT))
|
|
analog_rx = run_pressed ? -32767 : -16384;
|
|
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0,
|
|
RETRO_DEVICE_ID_JOYPAD_RIGHT))
|
|
analog_rx = run_pressed ? 32767 : 16384;
|
|
|
|
if (location_camera_control_enable)
|
|
{
|
|
if (loc_float_greater_than(current_location.longitude, previous_location.longitude))
|
|
analog_y = run_pressed ? -32767 : -16384;
|
|
else if (loc_float_lesser_than(current_location.longitude, previous_location.longitude))
|
|
analog_y = run_pressed ? 32767 : 16384;
|
|
}
|
|
|
|
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0,
|
|
RETRO_DEVICE_ID_JOYPAD_UP))
|
|
{
|
|
if (mouselook_pressed)
|
|
analog_ry = run_pressed ? -32767 : -16384;
|
|
else
|
|
analog_y = run_pressed ? -32767 : -16384;
|
|
}
|
|
|
|
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0,
|
|
RETRO_DEVICE_ID_JOYPAD_DOWN))
|
|
{
|
|
if (mouselook_pressed)
|
|
analog_ry = run_pressed ? 32767 : 16384;
|
|
else
|
|
analog_y = run_pressed ? 32767 : 16384;
|
|
}
|
|
|
|
|
|
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0,
|
|
RETRO_DEVICE_ID_JOYPAD_L))
|
|
analog_x = run_pressed ? -32767 : -16384;
|
|
|
|
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0,
|
|
RETRO_DEVICE_ID_JOYPAD_R))
|
|
analog_x = run_pressed ? 32767 : 16384;
|
|
|
|
if (abs(analog_x) < 10000)
|
|
analog_x = 0;
|
|
if (abs(analog_y) < 10000)
|
|
analog_y = 0;
|
|
if (abs(analog_rx) < 10000)
|
|
analog_rx = 0;
|
|
if (abs(analog_ry) < 10000)
|
|
analog_ry = 0;
|
|
|
|
#if 0
|
|
if (log_cb)
|
|
{
|
|
log_cb(RETRO_LOG_INFO, "analog_x: %d\n", analog_x);
|
|
log_cb(RETRO_LOG_INFO, "analog_y: %d\n", analog_y);
|
|
log_cb(RETRO_LOG_INFO, "analog_rx: %d\n", analog_rx);
|
|
log_cb(RETRO_LOG_INFO, "analog_ry: %d\n", analog_ry);
|
|
}
|
|
#endif
|
|
|
|
if (vr_active)
|
|
analog_ry = 0; /* head controls pitch */
|
|
|
|
player_view_deg_y += analog_rx * -0.00008f;
|
|
player_view_deg_x += analog_ry * -0.00005f;
|
|
|
|
player_view_deg_x = clamp(player_view_deg_x, -80.0f, 80.0f);
|
|
|
|
mat4 rotate_x = rotate(mat4(1.0), player_view_deg_x, vec3(1, 0, 0));
|
|
mat4 rotate_y = rotate(mat4(1.0), player_view_deg_y, vec3(0, 1, 0));
|
|
mat4 rotate_y_right = rotate(mat4(1.0), player_view_deg_y - 90.0f, vec3(0, 1, 0));
|
|
|
|
vec3 look_dir = vec3(rotate_y * rotate_x * vec4(0, 0, -1, 1));
|
|
|
|
vec3 right_walk_dir = vec3(rotate_y_right * vec4(0, 0, -1, 1));
|
|
vec3 front_walk_dir = vec3(rotate_y * vec4(0, 0, -1, 1));
|
|
|
|
if (vr_active)
|
|
{
|
|
/* walk where the head points, on the ground plane */
|
|
front_walk_dir = vec3(rotate_y * vec4(vr_head_forward_xz(vr_frame), 0.0f));
|
|
right_walk_dir = cross(front_walk_dir, vec3(0, 1, 0));
|
|
}
|
|
|
|
vec3 velocity = front_walk_dir * vec3(analog_y * -0.000002f) +
|
|
right_walk_dir * vec3(analog_x * 0.000002f);
|
|
|
|
vec3 player_pos_espace = player_pos / player_size;
|
|
vec3 velocity_espace = velocity / player_size;
|
|
|
|
coll_detection(player_pos_espace, velocity_espace);
|
|
player_pos_espace += velocity_espace;
|
|
coll_wall_hug_detection(player_pos_espace);
|
|
|
|
static vec3 gravity;
|
|
static bool can_jump;
|
|
gravity += vec3(0.0f, -0.01f, 0.0f);
|
|
if (can_jump && jump)
|
|
{
|
|
gravity[1] += 0.3f;
|
|
can_jump = false;
|
|
}
|
|
gravity[1] -= gravity[1] * 0.01f;
|
|
|
|
vec3 old_gravity = gravity;
|
|
coll_detection(player_pos_espace, gravity);
|
|
if (old_gravity[1] != gravity[1])
|
|
{
|
|
gravity = vec3(0.0f);
|
|
can_jump = true;
|
|
}
|
|
|
|
player_pos_espace += gravity;
|
|
coll_wall_hug_detection(player_pos_espace);
|
|
|
|
player_pos = player_pos_espace * player_size;
|
|
|
|
mat4 view = lookAt(player_pos, player_pos + look_dir, vec3(0, 1, 0));
|
|
|
|
bool start_pressed = input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START);
|
|
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L2))
|
|
{
|
|
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT))
|
|
{
|
|
if (start_pressed)
|
|
light_r -= 0.01;
|
|
else
|
|
ambient_light_r -= 0.01;
|
|
}
|
|
else
|
|
{
|
|
if (start_pressed)
|
|
light_r += 0.01;
|
|
else
|
|
ambient_light_r += 0.01;
|
|
}
|
|
}
|
|
|
|
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R2))
|
|
{
|
|
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT))
|
|
{
|
|
if (start_pressed)
|
|
light_g -= 0.01;
|
|
else
|
|
ambient_light_g -= 0.01;
|
|
}
|
|
else
|
|
{
|
|
if (start_pressed)
|
|
light_g += 0.01;
|
|
else
|
|
ambient_light_g += 0.01;
|
|
}
|
|
}
|
|
|
|
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3))
|
|
{
|
|
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT))
|
|
{
|
|
if (start_pressed)
|
|
light_b -= 0.01;
|
|
else
|
|
ambient_light_b -= 0.01;
|
|
}
|
|
else
|
|
{
|
|
if (start_pressed)
|
|
light_b += 0.01;
|
|
else
|
|
ambient_light_b += 0.01;
|
|
}
|
|
}
|
|
|
|
scene_view = view;
|
|
scene_eye = player_pos;
|
|
|
|
return player_size;
|
|
}
|
|
|
|
|
|
static vec3 modelviewer_check_input(void)
|
|
{
|
|
if (mode_engine == MODE_SCENEWALKER)
|
|
return scenewalker_check_input();
|
|
|
|
static float model_rotate_y;
|
|
static float model_rotate_x;
|
|
static float model_scale = 1.0f;
|
|
|
|
input_poll_cb();
|
|
|
|
int analog_x = input_state_cb(0, RETRO_DEVICE_ANALOG,
|
|
RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_X);
|
|
|
|
int analog_y = input_state_cb(0, RETRO_DEVICE_ANALOG,
|
|
RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_Y);
|
|
|
|
int analog_ry = input_state_cb(0, RETRO_DEVICE_ANALOG,
|
|
RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_Y);
|
|
|
|
if (abs(analog_x) < 10000)
|
|
analog_x = 0;
|
|
if (abs(analog_y) < 10000)
|
|
analog_y = 0;
|
|
|
|
if (abs(analog_ry) < 10000)
|
|
analog_ry = 0;
|
|
|
|
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT))
|
|
analog_x -= 30000;
|
|
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT))
|
|
analog_x += 30000;
|
|
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP))
|
|
analog_y -= 30000;
|
|
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN))
|
|
analog_y += 30000;
|
|
|
|
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B))
|
|
analog_ry -= 30000;
|
|
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A))
|
|
analog_ry += 30000;
|
|
|
|
bool start_pressed = input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START);
|
|
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L2))
|
|
{
|
|
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT))
|
|
{
|
|
if (start_pressed)
|
|
light_r -= 0.01;
|
|
else
|
|
ambient_light_r -= 0.01;
|
|
}
|
|
else
|
|
{
|
|
if (start_pressed)
|
|
light_r += 0.01;
|
|
else
|
|
ambient_light_r += 0.01;
|
|
}
|
|
}
|
|
|
|
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R2))
|
|
{
|
|
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT))
|
|
{
|
|
if (start_pressed)
|
|
light_g -= 0.01;
|
|
else
|
|
ambient_light_g -= 0.01;
|
|
}
|
|
else
|
|
{
|
|
if (start_pressed)
|
|
light_g += 0.01;
|
|
else
|
|
ambient_light_g += 0.01;
|
|
}
|
|
}
|
|
|
|
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3))
|
|
{
|
|
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT))
|
|
{
|
|
if (start_pressed)
|
|
light_b -= 0.01;
|
|
else
|
|
ambient_light_b -= 0.01;
|
|
}
|
|
else
|
|
{
|
|
if (start_pressed)
|
|
light_b += 0.01;
|
|
else
|
|
ambient_light_b += 0.01;
|
|
}
|
|
}
|
|
|
|
model_scale *= 1.0f - analog_ry * 0.000001f;
|
|
model_scale = clamp(model_scale, 0.0001f, 100.0f);
|
|
model_rotate_x += analog_y * 0.0001f;
|
|
model_rotate_y += analog_x * 0.00015f;
|
|
|
|
mat4 translation = translate(mat4(1.0), vec3(0, 0, -40));
|
|
mat4 scaler = scale(mat4(1.0), vec3(model_scale, model_scale, model_scale));
|
|
mat4 rotate_x = rotate(mat4(1.0), model_rotate_x, vec3(1, 0, 0));
|
|
mat4 rotate_y = rotate(mat4(1.0), model_rotate_y, vec3(0, 1, 0));
|
|
|
|
scene_model = translation * scaler * rotate_x * rotate_y;
|
|
//check_collision_cube();
|
|
|
|
return player_size;
|
|
}
|
|
|
|
/* Pushes the state computed by the input code into the GL meshes. */
|
|
static void apply_scene_state_gl(void)
|
|
{
|
|
const bool scene = (mode_engine == MODE_SCENEWALKER);
|
|
for (unsigned i = 0; i < meshes.size(); i++)
|
|
{
|
|
if (scene)
|
|
{
|
|
meshes[i]->set_view(scene_view);
|
|
meshes[i]->set_eye(scene_eye);
|
|
}
|
|
else
|
|
meshes[i]->set_model(scene_model);
|
|
|
|
meshes[i]->set_lighting(light_r, light_g, light_b);
|
|
meshes[i]->set_ambient_lighting(ambient_light_r, ambient_light_g, ambient_light_b);
|
|
}
|
|
}
|
|
|
|
static void init_lighting(void)
|
|
{
|
|
if (mode_engine == MODE_SCENEWALKER)
|
|
{
|
|
light_r = normalize(0);
|
|
light_g = normalize(10);
|
|
light_b = normalize(0);
|
|
}
|
|
else
|
|
{
|
|
light_r = normalize(-1);
|
|
light_g = normalize(-1);
|
|
light_b = normalize(-1);
|
|
}
|
|
ambient_light_r = 0.25f;
|
|
ambient_light_g = 0.25f;
|
|
ambient_light_b = 0.25f;
|
|
}
|
|
|
|
static void init_mesh(const std::string& path)
|
|
{
|
|
if (log_cb)
|
|
log_cb(RETRO_LOG_INFO, "Loading Mesh ...\n");
|
|
|
|
static const std::string vertex_shader =
|
|
"uniform mat4 uModel;\n"
|
|
"uniform mat4 uMVP;\n"
|
|
"attribute vec4 aVertex;\n"
|
|
"attribute vec3 aNormal;\n"
|
|
"attribute vec2 aTex;\n"
|
|
"varying vec4 vNormal;\n"
|
|
"varying vec2 vTex;\n"
|
|
"varying vec4 vPos;\n"
|
|
"void main() {\n"
|
|
" gl_Position = uMVP * aVertex;\n"
|
|
" vTex = aTex;\n"
|
|
" vPos = uModel * aVertex;\n"
|
|
" vNormal = uModel * vec4(aNormal, 0.0);\n"
|
|
"}";
|
|
|
|
static const std::string fragment_shader_avoid_discard_hack =
|
|
"#ifdef GL_ES\n"
|
|
"precision mediump float;\n"
|
|
"#endif\n"
|
|
"varying vec2 vTex;\n"
|
|
"varying vec4 vNormal;\n"
|
|
"varying vec4 vPos;\n"
|
|
|
|
"uniform sampler2D sDiffuse;\n"
|
|
"uniform sampler2D sAmbient;\n"
|
|
|
|
"uniform vec3 uLightPos;\n"
|
|
"uniform vec3 uLightAmbient;\n"
|
|
"uniform vec3 uMTLAmbient;\n"
|
|
"uniform float uMTLAlphaMod;\n"
|
|
"uniform vec3 uMTLDiffuse;\n"
|
|
"uniform vec3 uMTLSpecular;\n"
|
|
"uniform float uMTLSpecularPower;\n"
|
|
|
|
"void main() {\n"
|
|
" vec4 colorDiffuseFull = texture2D(sDiffuse, vTex);\n"
|
|
" vec4 colorAmbientFull = texture2D(sAmbient, vTex);\n"
|
|
|
|
" if (colorDiffuseFull.a < 0.5)\n"
|
|
" discard;\n"
|
|
|
|
" vec3 colorDiffuse = mix(uMTLDiffuse, colorDiffuseFull.rgb, vec3(colorDiffuseFull.a));\n"
|
|
" vec3 colorAmbient = mix(uMTLAmbient, colorAmbientFull.rgb, vec3(colorAmbientFull.a));\n"
|
|
|
|
" vec3 normal = normalize(vNormal.xyz);\n"
|
|
" float directivity = dot(uLightPos, -normal);\n"
|
|
|
|
" vec3 diffuse = colorDiffuse * clamp(directivity, 0.0, 1.0);\n"
|
|
" vec3 ambient = colorAmbient * uLightAmbient;\n"
|
|
|
|
" vec3 modelToFace = normalize(-vPos.xyz);\n"
|
|
" float specularity = pow(clamp(dot(modelToFace, reflect(uLightPos, normal)), 0.0, 1.0), uMTLSpecularPower);\n"
|
|
" vec3 specular = uMTLSpecular * specularity;\n"
|
|
|
|
" gl_FragColor = vec4(diffuse + ambient + specular, uMTLAlphaMod);\n"
|
|
"}";
|
|
|
|
static const std::string fragment_shader =
|
|
"#ifdef GL_ES\n"
|
|
"precision mediump float;\n"
|
|
"#endif\n"
|
|
"varying vec2 vTex;\n"
|
|
"varying vec4 vNormal;\n"
|
|
"varying vec4 vPos;\n"
|
|
|
|
"uniform sampler2D sDiffuse;\n"
|
|
"uniform sampler2D sAmbient;\n"
|
|
|
|
"uniform vec3 uLightPos;\n"
|
|
"uniform vec3 uLightAmbient;\n"
|
|
"uniform vec3 uMTLAmbient;\n"
|
|
"uniform float uMTLAlphaMod;\n"
|
|
"uniform vec3 uMTLDiffuse;\n"
|
|
"uniform vec3 uMTLSpecular;\n"
|
|
"uniform float uMTLSpecularPower;\n"
|
|
|
|
"void main() {\n"
|
|
" vec4 colorDiffuseFull = texture2D(sDiffuse, vTex);\n"
|
|
" vec4 colorAmbientFull = texture2D(sAmbient, vTex);\n"
|
|
|
|
" vec3 colorDiffuse = mix(uMTLDiffuse, colorDiffuseFull.rgb, vec3(colorDiffuseFull.a));\n"
|
|
" vec3 colorAmbient = mix(uMTLAmbient, colorAmbientFull.rgb, vec3(colorAmbientFull.a));\n"
|
|
|
|
" vec3 normal = normalize(vNormal.xyz);\n"
|
|
" float directivity = dot(uLightPos, -normal);\n"
|
|
|
|
" vec3 diffuse = colorDiffuse * clamp(directivity, 0.0, 1.0);\n"
|
|
" vec3 ambient = colorAmbient * uLightAmbient;\n"
|
|
|
|
" vec3 modelToFace = normalize(-vPos.xyz);\n"
|
|
" float specularity = pow(clamp(dot(modelToFace, reflect(uLightPos, normal)), 0.0, 1.0), uMTLSpecularPower);\n"
|
|
" vec3 specular = uMTLSpecular * specularity;\n"
|
|
|
|
" gl_FragColor = vec4(diffuse + ambient + specular, uMTLAlphaMod * colorDiffuseFull.a);\n"
|
|
"}";
|
|
|
|
static const std::string fragment_shader_scene =
|
|
"#ifdef GL_ES\n"
|
|
"precision mediump float;\n"
|
|
"#endif\n"
|
|
"varying vec2 vTex;\n"
|
|
"varying vec4 vNormal;\n"
|
|
"varying vec4 vPos;\n"
|
|
|
|
"uniform sampler2D sDiffuse;\n"
|
|
"uniform sampler2D sAmbient;\n"
|
|
|
|
"uniform vec3 uLightPos;\n"
|
|
"uniform vec3 uLightAmbient;\n"
|
|
"uniform vec3 uEyePos;\n"
|
|
"uniform vec3 uMTLAmbient;\n"
|
|
"uniform float uMTLAlphaMod;\n"
|
|
"uniform vec3 uMTLDiffuse;\n"
|
|
"uniform vec3 uMTLSpecular;\n"
|
|
"uniform float uMTLSpecularPower;\n"
|
|
|
|
"void main() {\n"
|
|
" vec4 colorDiffuseFull = texture2D(sDiffuse, vTex);\n"
|
|
" vec4 colorAmbientFull = texture2D(sAmbient, vTex);\n"
|
|
|
|
" vec3 lightDir = normalize(vPos.xyz - uLightPos);\n"
|
|
|
|
" vec3 colorDiffuse = mix(uMTLDiffuse, colorDiffuseFull.rgb, vec3(colorDiffuseFull.a));\n"
|
|
" vec3 colorAmbient = mix(uMTLAmbient, colorAmbientFull.rgb, vec3(colorAmbientFull.a));\n"
|
|
|
|
" vec3 normal = normalize(vNormal.xyz);\n"
|
|
" float directivity = dot(lightDir, -normal);\n"
|
|
|
|
" vec3 diffuse = colorDiffuse * clamp(directivity, 0.0, 1.0);\n"
|
|
" vec3 ambient = colorAmbient * uLightAmbient;\n"
|
|
|
|
" vec3 modelToFace = normalize(uEyePos - vPos.xyz);\n"
|
|
" float specularity = pow(clamp(dot(modelToFace, reflect(lightDir, normal)), 0.0, 1.0), uMTLSpecularPower);\n"
|
|
" vec3 specular = uMTLSpecular * specularity;\n"
|
|
|
|
" gl_FragColor = vec4(diffuse + ambient + specular, uMTLAlphaMod * colorDiffuseFull.a);\n"
|
|
"}";
|
|
|
|
std1::shared_ptr<GL::Shader> shader(new GL::Shader(vertex_shader, (mode_engine == MODE_SCENEWALKER)
|
|
? fragment_shader_scene : ((discard_hack_enable)
|
|
? fragment_shader_avoid_discard_hack : fragment_shader)));
|
|
meshes = OBJ::load_from_file(path);
|
|
|
|
mat4 projection;
|
|
if (mode_engine == MODE_SCENEWALKER)
|
|
{
|
|
projection = perspective(45.0f, 640.0f / 480.0f, 1.0f, 100.0f);
|
|
if (scene_flip_y())
|
|
projection = scale(mat4(1.0), vec3(1, -1, 1)) * projection;
|
|
}
|
|
else
|
|
{
|
|
projection = scale(mat4(1.0), vec3(1, -1, 1)) * perspective(45.0f, 4.0f / 3.0f, 0.2f, 100.0f);
|
|
}
|
|
|
|
for (unsigned i = 0; i < meshes.size(); i++)
|
|
{
|
|
meshes[i]->set_projection(projection);
|
|
meshes[i]->set_shader(shader);
|
|
meshes[i]->set_blank(blank);
|
|
|
|
if (mode_engine == MODE_SCENEWALKER)
|
|
{
|
|
meshes[i]->set_lighting(0, 10, 0);
|
|
|
|
const std::vector<GL::Vertex>& vertices = *meshes[i]->get_vertex();
|
|
for (unsigned v = 0; v < vertices.size(); v += 3)
|
|
coll_triangles_push(v, vertices, player_size);
|
|
}
|
|
}
|
|
|
|
init_lighting();
|
|
}
|
|
|
|
#ifdef HAVE_VULKAN
|
|
/* Vulkan counterpart of init_mesh(): same OBJ parse, same collision data,
|
|
* but the GPU objects live in VKR instead of GL::Mesh. */
|
|
static bool vulkan_load_scene(void)
|
|
{
|
|
unsigned i, v;
|
|
const bool scene = (mode_engine == MODE_SCENEWALKER);
|
|
std::vector<OBJ::Part> parts;
|
|
VKR::ShaderMode mode;
|
|
|
|
if (log_cb)
|
|
log_cb(RETRO_LOG_INFO, "Loading Mesh (Vulkan) ...\n");
|
|
|
|
parts = OBJ::load_parts(mesh_path);
|
|
|
|
if (scene)
|
|
{
|
|
for (i = 0; i < parts.size(); i++)
|
|
for (v = 0; v + 2 < parts[i].vertices.size(); v += 3)
|
|
coll_triangles_push(v, parts[i].vertices, player_size);
|
|
mode = VKR::SHADER_SCENE;
|
|
}
|
|
else
|
|
mode = discard_hack_enable ? VKR::SHADER_MODEL_DISCARD : VKR::SHADER_MODEL;
|
|
|
|
init_lighting();
|
|
|
|
vk_scene_ok = VKR::load_scene(parts, mode);
|
|
return vk_scene_ok;
|
|
}
|
|
#endif
|
|
|
|
extern char retro_path_info[1024];
|
|
|
|
static void modelviewer_context_reset(void)
|
|
{
|
|
#ifdef HAVE_VULKAN
|
|
if (renderer_is_vulkan())
|
|
{
|
|
vk_scene_ok = false;
|
|
|
|
if (strstr(retro_path_info, ".mtl") || mode_engine == MODE_SCENEWALKER)
|
|
{
|
|
coll_triangles_clear();
|
|
scenewalker_reset_mesh_path();
|
|
mode_engine = MODE_SCENEWALKER;
|
|
}
|
|
|
|
if (!VKR::init(environ_cb))
|
|
{
|
|
if (log_cb)
|
|
log_cb(RETRO_LOG_ERROR, "Vulkan renderer init failed.\n");
|
|
return;
|
|
}
|
|
|
|
vulkan_load_scene();
|
|
update = true;
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
renderer_dead_state = true;
|
|
meshes.clear();
|
|
blank.reset();
|
|
renderer_dead_state = false;
|
|
|
|
if (strstr(retro_path_info, ".mtl") || mode_engine == MODE_SCENEWALKER)
|
|
{
|
|
coll_triangles_clear();
|
|
scenewalker_reset_mesh_path();
|
|
mode_engine = MODE_SCENEWALKER;
|
|
}
|
|
|
|
rglgen_resolve_symbols(hw_render.get_proc_address);
|
|
|
|
blank = GL::Texture::blank();
|
|
init_mesh(mesh_path);
|
|
|
|
update = true;
|
|
}
|
|
|
|
/* Called from the frontend's context_destroy (Vulkan only). */
|
|
static void modelviewer_context_destroy(void)
|
|
{
|
|
#ifdef HAVE_VULKAN
|
|
vk_scene_ok = false;
|
|
VKR::destroy();
|
|
#endif
|
|
}
|
|
|
|
/* Option changes used to be applied by faking a context reset. For Vulkan
|
|
* only the scene (pipeline variant + geometry) needs rebuilding. */
|
|
static void modelviewer_apply_option_change(void)
|
|
{
|
|
#ifdef HAVE_VULKAN
|
|
if (renderer_is_vulkan())
|
|
{
|
|
if (!VKR::initialized())
|
|
return; /* will be built by the real context_reset */
|
|
if (mode_engine == MODE_SCENEWALKER)
|
|
coll_triangles_clear();
|
|
vulkan_load_scene();
|
|
return;
|
|
}
|
|
#endif
|
|
modelviewer_context_reset();
|
|
}
|
|
|
|
static void modelviewer_update_variables(retro_environment_t environ_cb)
|
|
{
|
|
struct retro_variable var;
|
|
|
|
var.key = "3dengine-modelviewer-discard-hack";
|
|
var.value = NULL;
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
if (!strcmp(var.value, "disabled"))
|
|
discard_hack_enable = false;
|
|
else if (!strcmp(var.value, "enabled"))
|
|
discard_hack_enable = true;
|
|
|
|
if (!first_init)
|
|
modelviewer_apply_option_change();
|
|
}
|
|
|
|
var.key = "3dengine-modelviewer-vr-enable";
|
|
var.value = NULL;
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
if (!strcmp(var.value, "disabled"))
|
|
vr_user_enable = false;
|
|
else if (!strcmp(var.value, "enabled"))
|
|
vr_user_enable = true;
|
|
|
|
if (!first_init)
|
|
modelviewer_apply_option_change();
|
|
}
|
|
}
|
|
|
|
#ifdef HAVE_VULKAN
|
|
static void modelviewer_run_vulkan(unsigned width, unsigned height, bool scene, bool vr)
|
|
{
|
|
VKR::Frame f;
|
|
|
|
f.model = scene_model;
|
|
f.light = vec3(light_r, light_g, light_b);
|
|
f.ambient = vec3(ambient_light_r, ambient_light_g, ambient_light_b);
|
|
f.width = width;
|
|
f.height = height;
|
|
f.num_passes = 0;
|
|
|
|
if (vr)
|
|
{
|
|
unsigned eye;
|
|
mat4 rig = translate(mat4(1.0), player_pos) *
|
|
rotate(mat4(1.0), player_view_deg_y, vec3(0, 1, 0));
|
|
|
|
for (eye = 0; eye < 2; eye++)
|
|
{
|
|
const struct retro_vr_eye_state &es = vr_frame.eyes[eye];
|
|
mat4 eye_world = rig * vr_eye_pose_matrix(es);
|
|
VKR::Pass &p = f.passes[eye];
|
|
|
|
/* Same matrices as the GL path; VKR applies the GL->Vulkan clip
|
|
* correction, so no scene_flip_y() handling here. */
|
|
p.view = inverse(eye_world);
|
|
p.proj = vr_projection(es.fov_tan, 0.1f, 100.0f);
|
|
p.eye_pos = vec3(eye_world[3]);
|
|
p.x = eye * vr_eye_width;
|
|
p.width = vr_eye_width;
|
|
p.height = vr_eye_height;
|
|
}
|
|
f.num_passes = 2;
|
|
}
|
|
else
|
|
{
|
|
VKR::Pass &p = f.passes[0];
|
|
p.proj = scene ? perspective(45.0f, 640.0f / 480.0f, 1.0f, 100.0f)
|
|
: perspective(45.0f, 4.0f / 3.0f, 0.2f, 100.0f);
|
|
p.view = scene ? scene_view : mat4(1.0f);
|
|
p.eye_pos = scene_eye;
|
|
p.x = 0;
|
|
p.width = width;
|
|
p.height = height;
|
|
f.num_passes = 1;
|
|
}
|
|
|
|
if (!vk_scene_ok || !VKR::render(f, video_cb))
|
|
video_cb(NULL, width, height, 0); /* dupe the last frame */
|
|
}
|
|
#endif
|
|
|
|
static void modelviewer_run(void)
|
|
{
|
|
unsigned i, width, height;
|
|
const bool scene = (mode_engine == MODE_SCENEWALKER);
|
|
bool vr = false;
|
|
|
|
if (scene && vr_active)
|
|
{
|
|
vr_poll_frame(&vr_frame); /* once, before input, per the API doc */
|
|
vr = vr_active; /* may drop if the resize renegotiation failed */
|
|
}
|
|
|
|
vec3 look_dir = modelviewer_check_input();
|
|
(void)look_dir;
|
|
|
|
width = vr ? vr_eye_width * 2 : engine_width;
|
|
height = vr ? vr_eye_height : engine_height;
|
|
|
|
#ifdef HAVE_VULKAN
|
|
if (renderer_is_vulkan())
|
|
{
|
|
modelviewer_run_vulkan(width, height, scene, vr);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
apply_scene_state_gl();
|
|
|
|
glBindFramebuffer(GL_FRAMEBUFFER, hw_render.get_current_framebuffer());
|
|
glViewport(0, 0, width, height);
|
|
glClearColor(0.2f, 0.2f, 0.2f, 1.0f);
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
|
|
glEnable(GL_DEPTH_TEST);
|
|
/* Only the flipped projection needs CW. Unflipped is normal CCW. */
|
|
glFrontFace((scene && !scene_flip_y()) ? GL_CCW : GL_CW);
|
|
glEnable(GL_CULL_FACE);
|
|
glEnable(GL_BLEND);
|
|
|
|
if (vr)
|
|
{
|
|
unsigned eye;
|
|
mat4 rig = translate(mat4(1.0), player_pos) *
|
|
rotate(mat4(1.0), player_view_deg_y, vec3(0, 1, 0));
|
|
|
|
glEnable(GL_SCISSOR_TEST);
|
|
for (eye = 0; eye < 2; eye++)
|
|
{
|
|
const struct retro_vr_eye_state &es = vr_frame.eyes[eye];
|
|
mat4 eye_world = rig * vr_eye_pose_matrix(es);
|
|
mat4 view = inverse(eye_world);
|
|
mat4 proj = vr_projection(es.fov_tan, 0.1f, 100.0f);
|
|
vec3 eye_pos = vec3(eye_world[3]);
|
|
|
|
if (scene_flip_y())
|
|
proj = scale(mat4(1.0), vec3(1, -1, 1)) * proj;
|
|
|
|
glViewport(eye * vr_eye_width, 0, vr_eye_width, vr_eye_height);
|
|
glScissor (eye * vr_eye_width, 0, vr_eye_width, vr_eye_height);
|
|
|
|
for (i = 0; i < meshes.size(); i++)
|
|
{
|
|
meshes[i]->set_projection(proj);
|
|
meshes[i]->set_view(view);
|
|
meshes[i]->set_eye(eye_pos);
|
|
meshes[i]->render();
|
|
}
|
|
}
|
|
glDisable(GL_SCISSOR_TEST);
|
|
}
|
|
else
|
|
for (i = 0; i < meshes.size(); i++)
|
|
meshes[i]->render();
|
|
|
|
glDisable(GL_BLEND);
|
|
glDisable(GL_DEPTH_TEST);
|
|
glDisable(GL_CULL_FACE);
|
|
video_cb(RETRO_HW_FRAME_BUFFER_VALID, width, height, 0);
|
|
}
|
|
static void scenewalker_reset_mesh_path(void)
|
|
{
|
|
//always assume matching .obj file is there
|
|
size_t extPos = mesh_path.rfind('.');
|
|
if (extPos != std::string::npos)
|
|
{
|
|
// Erase the current extension.
|
|
mesh_path.erase(extPos);
|
|
// Add the new extension.
|
|
mesh_path.append(".obj");
|
|
|
|
if (log_cb)
|
|
log_cb(RETRO_LOG_INFO, "New path: %s\n", mesh_path.c_str());
|
|
}
|
|
}
|
|
|
|
static void modelviewer_load_game(const struct retro_game_info *info)
|
|
{
|
|
mode_engine = MODE_MODELVIEWER;
|
|
|
|
mesh_path = info->path;
|
|
|
|
if (strstr(info->path, ".mtl"))
|
|
{
|
|
mode_engine = MODE_SCENEWALKER;
|
|
coll_test_crash_detection();
|
|
scenewalker_reset_mesh_path();
|
|
}
|
|
else
|
|
player_size = vec3(0, 0, 0);
|
|
|
|
first_init = false;
|
|
|
|
vr_frame_defaults(&vr_frame);
|
|
}
|
|
|
|
const engine_program_t engine_program_modelviewer = {
|
|
modelviewer_load_game,
|
|
modelviewer_run,
|
|
modelviewer_context_reset,
|
|
modelviewer_update_variables,
|
|
modelviewer_check_input,
|
|
modelviewer_context_destroy,
|
|
};
|