light working.
This commit is contained in:
parent
d7970ed797
commit
44a7048c4a
9 changed files with 139 additions and 61 deletions
|
@ -17,11 +17,12 @@ void main()
|
||||||
vec3 Reflected = normalize(reflect( -lightvec, normal));
|
vec3 Reflected = normalize(reflect( -lightvec, normal));
|
||||||
|
|
||||||
//todo materials (& light color)
|
//todo materials (& light color)
|
||||||
vec4 IAmbient = vec4(0.0f, 0.0f, 0.0f, 0.0f);
|
vec4 IAmbient = vec4(0.2f, 0.2f, 0.2f, 1.0f);
|
||||||
vec4 IDiffuse = vec4(1.0f, 1.0f, 1.0f, 0.0f) * max(dot(normal, lightvec), 0.0);
|
vec4 IDiffuse = vec4(1.0f, 1.0f, 1.0f, 0.0f) * max(dot(normal, lightvec), 0.0);
|
||||||
//todo shininess
|
//todo shininess
|
||||||
vec4 ISpecular = vec4(1.0f, 0.0f, 0.0f, 0.0f) * pow(max(dot(Reflected, Eye), 0.0), 3);
|
vec4 ISpecular = vec4(1.0f, 0.0f, 0.0f, 0.0f) * pow(max(dot(Reflected, Eye), 0.0), 3);
|
||||||
|
|
||||||
gl_FragColor = vec4((IAmbient + IDiffuse) + ISpecular);
|
gl_FragColor = vec4((IAmbient + IDiffuse) + ISpecular);
|
||||||
|
//gl_FragColor = vec4(0.5+0.5*normal, 1.0);
|
||||||
}
|
}
|
||||||
)raw_string"
|
)raw_string"
|
||||||
|
|
|
@ -13,13 +13,13 @@ uniform vec3 lightpos;
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
//vec3 p = position + scale*gl_Vertex.xyz;
|
//vec3 p = position + scale*in_vertex.xyz;
|
||||||
|
|
||||||
vec3 p = (model*gl_Vertex).xyz;
|
vec3 p = (model*vec4(in_vertex, 1.0)).xyz;
|
||||||
lightvec = normalize(lightpos - p);
|
lightvec = normalize(lightpos - p);
|
||||||
|
|
||||||
vertex = p.xyz;
|
vertex = p.xyz;
|
||||||
normal = in_normal.xyz;
|
normal = normalize((model*vec4(in_normal.xyz, 0.0)).xyz);
|
||||||
|
|
||||||
gl_Position = vec4(p, 1.0);
|
gl_Position = vec4(p, 1.0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ namespace endofthejedi {
|
||||||
: m_numParticles(numParticles)
|
: m_numParticles(numParticles)
|
||||||
, m_particleRadius(particleSize)
|
, m_particleRadius(particleSize)
|
||||||
, m_maxAge(maxAge)
|
, m_maxAge(maxAge)
|
||||||
|
, m_age(0.0)
|
||||||
|
|
||||||
// 2d quad drawn as a triangle fan
|
// 2d quad drawn as a triangle fan
|
||||||
, m_data_quad({
|
, m_data_quad({
|
||||||
|
@ -112,14 +113,12 @@ namespace endofthejedi {
|
||||||
|
|
||||||
m_shader.bind();
|
m_shader.bind();
|
||||||
|
|
||||||
|
// TODO: does that work?
|
||||||
glBindAttribLocation(m_shader.program(), 0, "in_vertex");
|
glBindAttribLocation(m_shader.program(), 0, "in_vertex");
|
||||||
glBindAttribLocation(m_shader.program(), 1, "in_position");
|
glBindAttribLocation(m_shader.program(), 1, "in_position");
|
||||||
glBindAttribLocation(m_shader.program(), 2, "in_velocity");
|
glBindAttribLocation(m_shader.program(), 2, "in_velocity");
|
||||||
|
|
||||||
static float time = 0.0f;
|
glUniform1f(m_shader.location("time"), m_age);
|
||||||
time += 1.0/50.0;
|
|
||||||
|
|
||||||
glUniform1f(m_shader.location("time"), time);
|
|
||||||
glUniform1f(m_shader.location("maxAge"), m_maxAge);
|
glUniform1f(m_shader.location("maxAge"), m_maxAge);
|
||||||
glUniform1f(m_shader.location("size"), m_particleRadius);
|
glUniform1f(m_shader.location("size"), m_particleRadius);
|
||||||
|
|
||||||
|
@ -175,4 +174,14 @@ namespace endofthejedi {
|
||||||
exit(-1);
|
exit(-1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ParticleBatch::tick(float dt)
|
||||||
|
{
|
||||||
|
m_age += dt;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ParticleBatch::done() const
|
||||||
|
{
|
||||||
|
return m_age >= m_maxAge;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,9 @@ namespace endofthejedi {
|
||||||
void upload();
|
void upload();
|
||||||
void render();
|
void render();
|
||||||
|
|
||||||
|
void tick(float dt);
|
||||||
|
bool done() const;
|
||||||
|
|
||||||
Shader *shader() { return &m_shader; }
|
Shader *shader() { return &m_shader; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -31,7 +34,8 @@ namespace endofthejedi {
|
||||||
private:
|
private:
|
||||||
size_t m_numParticles;
|
size_t m_numParticles;
|
||||||
float m_particleRadius;
|
float m_particleRadius;
|
||||||
float m_maxAge;
|
const float m_maxAge;
|
||||||
|
float m_age;
|
||||||
|
|
||||||
GLuint m_data_vbos[5];
|
GLuint m_data_vbos[5];
|
||||||
|
|
||||||
|
|
|
@ -76,6 +76,10 @@ namespace endofthejedi {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/************************************************************/
|
||||||
|
/* Position data */
|
||||||
|
/************************************************************/
|
||||||
|
|
||||||
glEnableVertexAttribArray(0);
|
glEnableVertexAttribArray(0);
|
||||||
|
|
||||||
glGenBuffers(1, &m_vbo_id_position); // Generate buffer
|
glGenBuffers(1, &m_vbo_id_position); // Generate buffer
|
||||||
|
@ -95,31 +99,35 @@ namespace endofthejedi {
|
||||||
m_data_position.data(), // Buffer data pointer
|
m_data_position.data(), // Buffer data pointer
|
||||||
GL_STATIC_DRAW); // Usage - Data never changes;
|
GL_STATIC_DRAW); // Usage - Data never changes;
|
||||||
|
|
||||||
//normal data
|
/************************************************************/
|
||||||
glEnableVertexAttribArray(1);
|
/* Normal data */
|
||||||
glGenBuffers(1, &m_vbo_id_normal); // Generate buffer
|
/************************************************************/
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, m_vbo_id_normal); // Bind buffer
|
|
||||||
|
|
||||||
glVertexAttribPointer(1,
|
glEnableVertexAttribArray(1);
|
||||||
3, // three floats per normal
|
glGenBuffers(1, &m_vbo_id_normal); // Generate buffer
|
||||||
GL_FLOAT, // Data is floating point type
|
glBindBuffer(GL_ARRAY_BUFFER, m_vbo_id_normal); // Bind buffer
|
||||||
GL_FALSE, // No fixed point scaling
|
|
||||||
0, // stride: no
|
|
||||||
NULL); // No offset
|
|
||||||
|
|
||||||
// Fill bound buffer
|
glVertexAttribPointer(
|
||||||
glBufferData(
|
1,
|
||||||
GL_ARRAY_BUFFER, // Buffer target
|
3, // three floats per normal
|
||||||
3*m_numVertices*sizeof(float), // Buffer data size
|
GL_FLOAT, // Data is floating point type
|
||||||
m_data_normal.data(), // Buffer data pointer
|
GL_FALSE, // No fixed point scaling
|
||||||
GL_STATIC_DRAW); // Usage - Data never changes;
|
0, // stride: no
|
||||||
|
NULL); // No offset
|
||||||
|
|
||||||
|
// Fill bound buffer
|
||||||
|
glBufferData(
|
||||||
|
GL_ARRAY_BUFFER, // Buffer target
|
||||||
|
3 * m_numVertices * sizeof(float), // Buffer data size
|
||||||
|
m_data_normal.data(), // Buffer data pointer
|
||||||
|
GL_STATIC_DRAW); // Usage - Data never changes;
|
||||||
|
|
||||||
m_loaded_to_opengl = true;
|
m_loaded_to_opengl = true;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool bind()
|
bool bind(Shader &shader)
|
||||||
{
|
{
|
||||||
if (!m_loaded_to_opengl) {
|
if (!m_loaded_to_opengl) {
|
||||||
std::cout<<"[polygonmodel] warning: try to bind model vbo "
|
std::cout<<"[polygonmodel] warning: try to bind model vbo "
|
||||||
|
@ -135,9 +143,10 @@ namespace endofthejedi {
|
||||||
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, NULL);
|
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, NULL);
|
||||||
|
|
||||||
// bind normal vbo
|
// bind normal vbo
|
||||||
glEnableVertexAttribArray(1);
|
GLuint loc = glGetAttribLocation(shader.program(), "in_normal");
|
||||||
|
glEnableVertexAttribArray(loc);
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, m_vbo_id_normal);
|
glBindBuffer(GL_ARRAY_BUFFER, m_vbo_id_normal);
|
||||||
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, NULL);
|
glVertexAttribPointer(loc, 3, GL_FLOAT, GL_FALSE, 0, NULL);
|
||||||
|
|
||||||
m_binding_active = true;
|
m_binding_active = true;
|
||||||
|
|
||||||
|
@ -152,8 +161,13 @@ namespace endofthejedi {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
glBindAttribLocation(shader.program(), 0, "in_vertex");
|
//GLuint l0 = glGetAttribLocation(shader.program(), "in_normal");
|
||||||
glBindAttribLocation(shader.program(), 1, "in_normal");
|
//GLuint l1 = glGetAttribLocation(shader.program(), "in_vertex");
|
||||||
|
//GLuint l2 = glGetAttribLocation(shader.program(), "in_fakkkke");
|
||||||
|
//std::cout<<"locations: " << l0 << " " << l1 << " " << l2 << std::endl;
|
||||||
|
|
||||||
|
//glBindAttribLocation(shader.program(), 0, "in_vertex");
|
||||||
|
//glBindAttribLocation(shader.program(), 1, "in_normal");
|
||||||
|
|
||||||
glDrawArrays(GL_TRIANGLES, 0, m_numVertices);
|
glDrawArrays(GL_TRIANGLES, 0, m_numVertices);
|
||||||
return true;
|
return true;
|
||||||
|
@ -195,6 +209,8 @@ namespace endofthejedi {
|
||||||
m_data_normal.push_back(mesh->mNormals[index].x);
|
m_data_normal.push_back(mesh->mNormals[index].x);
|
||||||
m_data_normal.push_back(mesh->mNormals[index].y);
|
m_data_normal.push_back(mesh->mNormals[index].y);
|
||||||
m_data_normal.push_back(mesh->mNormals[index].z);
|
m_data_normal.push_back(mesh->mNormals[index].z);
|
||||||
|
|
||||||
|
//std::cout<<"adding normal: " << mesh->mNormals[index].x << " " << mesh->mNormals[index].y << " " << mesh->mNormals[index].z << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,8 @@
|
||||||
namespace endofthejedi {
|
namespace endofthejedi {
|
||||||
void RendererPolygon3d::setup()
|
void RendererPolygon3d::setup()
|
||||||
{
|
{
|
||||||
|
m_lastTime = -1.0;
|
||||||
|
|
||||||
std::cout<<"setup polygon 3d" << std::endl;
|
std::cout<<"setup polygon 3d" << std::endl;
|
||||||
|
|
||||||
//m_missileModel = new PolygonModel("../data/mesh/quad_screen_coords.stl");
|
//m_missileModel = new PolygonModel("../data/mesh/quad_screen_coords.stl");
|
||||||
|
@ -18,25 +20,6 @@ namespace endofthejedi {
|
||||||
addModel("../data/mesh/planet_12.stl", &m_planetModel);
|
addModel("../data/mesh/planet_12.stl", &m_planetModel);
|
||||||
addModel("../data/mesh/planet_12.stl", &m_shipModel);
|
addModel("../data/mesh/planet_12.stl", &m_shipModel);
|
||||||
|
|
||||||
size_t n = 100;
|
|
||||||
m_particles = new ParticleBatch(n, 0.05, 3.0);
|
|
||||||
|
|
||||||
// distribute in a circle
|
|
||||||
for (size_t i=0; i<n; i++) {
|
|
||||||
float t = 2.0 * M_PI * i / (float) n;
|
|
||||||
t += 0.2*util::randf_m1_1();
|
|
||||||
|
|
||||||
glm::vec2 v = 0.2f*glm::vec2(sin(t), cos(t));
|
|
||||||
|
|
||||||
m_particles->setParticle(
|
|
||||||
i,
|
|
||||||
//glm::vec2(i/(float) n, 0.0),
|
|
||||||
glm::vec2(0.0, 0.0),
|
|
||||||
v);
|
|
||||||
}
|
|
||||||
|
|
||||||
m_particles->upload();
|
|
||||||
|
|
||||||
std::string vss_simple =
|
std::string vss_simple =
|
||||||
#include "simple.vert"
|
#include "simple.vert"
|
||||||
;
|
;
|
||||||
|
@ -70,10 +53,21 @@ namespace endofthejedi {
|
||||||
|
|
||||||
void RendererPolygon3d::render(const game::State *state)
|
void RendererPolygon3d::render(const game::State *state)
|
||||||
{
|
{
|
||||||
|
if (m_lastTime == -1.0) {
|
||||||
|
m_lastTime = state->timestamp();
|
||||||
|
}
|
||||||
|
|
||||||
|
float dt = state->timestamp() - m_lastTime;
|
||||||
|
if (dt < 0.0) {
|
||||||
|
dt = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
glClearColor(0.0, 0.0, 0.0, 1.0);
|
glClearColor(0.0, 0.0, 0.0, 1.0);
|
||||||
|
|
||||||
m_state = state;
|
m_state = state;
|
||||||
|
|
||||||
|
advanceGraphicObjects(dt);
|
||||||
|
|
||||||
m_shader.bind();
|
m_shader.bind();
|
||||||
|
|
||||||
// TODO :Z?
|
// TODO :Z?
|
||||||
|
@ -91,17 +85,54 @@ namespace endofthejedi {
|
||||||
//glVertex2f(1.0f, 1.0f);
|
//glVertex2f(1.0f, 1.0f);
|
||||||
//glVertex2f(-1.0f, 1.0f);
|
//glVertex2f(-1.0f, 1.0f);
|
||||||
//glEnd();
|
//glEnd();
|
||||||
|
|
||||||
|
m_lastTime = state->timestamp();
|
||||||
}
|
}
|
||||||
|
|
||||||
void RendererPolygon3d::renderParticles()
|
void RendererPolygon3d::renderParticles()
|
||||||
{
|
{
|
||||||
//m_particles->bind();
|
for (ParticleBatch *batch : m_particles) {
|
||||||
m_particles->render();
|
batch->bind();
|
||||||
|
batch->render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RendererPolygon3d::addExplosionEffect(const glm::vec2 &pos, size_t n, float duration)
|
||||||
|
{
|
||||||
|
ParticleBatch *batch = new ParticleBatch(n, 0.005, duration);
|
||||||
|
|
||||||
|
for (size_t i=0; i<n; i++) {
|
||||||
|
// distribute in a circle
|
||||||
|
float t = 2.0 * M_PI * i / (float) n;
|
||||||
|
t += 0.2*util::randf_m1_1();
|
||||||
|
|
||||||
|
// with random velocities
|
||||||
|
glm::vec2 v = 0.2f*glm::vec2(sin(t), cos(t));
|
||||||
|
|
||||||
|
batch->setParticle(i, pos, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
batch->upload();
|
||||||
|
|
||||||
|
m_particles.push_back(batch);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RendererPolygon3d::advanceGraphicObjects(float dt)
|
||||||
|
{
|
||||||
|
for (ParticleBatch *batch : m_particles) {
|
||||||
|
batch->tick(dt);
|
||||||
|
if (batch->done()) {
|
||||||
|
m_particles.remove(batch);
|
||||||
|
delete(batch);
|
||||||
|
|
||||||
|
std::cout<<"particle batch done!" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RendererPolygon3d::renderPlanets()
|
void RendererPolygon3d::renderPlanets()
|
||||||
{
|
{
|
||||||
m_planetModel->bind();
|
m_planetModel->bind(m_shader);
|
||||||
|
|
||||||
for (const game::Planet *planet : m_state->planets) {
|
for (const game::Planet *planet : m_state->planets) {
|
||||||
glm::mat4 model = computeModelMatrix(planet);
|
glm::mat4 model = computeModelMatrix(planet);
|
||||||
|
@ -116,12 +147,12 @@ namespace endofthejedi {
|
||||||
|
|
||||||
void RendererPolygon3d::renderMissiles()
|
void RendererPolygon3d::renderMissiles()
|
||||||
{
|
{
|
||||||
m_missileModel->bind();
|
m_missileModel->bind(m_shader);
|
||||||
|
|
||||||
for (const game::Player *player : m_state->players) {
|
for (const game::Player *player : m_state->players) {
|
||||||
for (const game::Missile *missile : player->missiles) {
|
for (const game::Missile *missile : player->missiles) {
|
||||||
glm::vec3 c = glm::vec3(1.0, 1.0, 0.3);
|
glm::vec3 c = glm::vec3(1.0, 1.0, 0.3);
|
||||||
glUniform3f(m_shader.location("color"), c.x, c.y, c.z);
|
glUniform3f(m_shader.location("color"), c.x, c.y, c.z);
|
||||||
|
|
||||||
glm::mat4 model = computeModelMatrix(missile);
|
glm::mat4 model = computeModelMatrix(missile);
|
||||||
glUniformMatrix4fv(m_shader.location("model"), 1, GL_FALSE, glm::value_ptr(model));
|
glUniformMatrix4fv(m_shader.location("model"), 1, GL_FALSE, glm::value_ptr(model));
|
||||||
|
@ -133,7 +164,7 @@ namespace endofthejedi {
|
||||||
|
|
||||||
void RendererPolygon3d::renderShips()
|
void RendererPolygon3d::renderShips()
|
||||||
{
|
{
|
||||||
m_shipModel->bind();
|
m_shipModel->bind(m_shader);
|
||||||
|
|
||||||
for (const game::Ship *ship : m_state->ships) {
|
for (const game::Ship *ship : m_state->ships) {
|
||||||
glm::mat4 model = computeModelMatrix(ship);
|
glm::mat4 model = computeModelMatrix(ship);
|
||||||
|
@ -173,7 +204,7 @@ namespace endofthejedi {
|
||||||
|
|
||||||
// TODO: which visual size has the rocket? in game its just a point with
|
// TODO: which visual size has the rocket? in game its just a point with
|
||||||
// no size because all others have size.
|
// no size because all others have size.
|
||||||
return computeModelMatrix(missile->position, 0.02f, a);
|
return computeModelMatrix(missile->position, 0.05f, a);
|
||||||
}
|
}
|
||||||
|
|
||||||
glm::mat4 RendererPolygon3d::computeModelMatrix(const game::Ship *ship)
|
glm::mat4 RendererPolygon3d::computeModelMatrix(const game::Ship *ship)
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
#include "renderer.hpp"
|
#include "renderer.hpp"
|
||||||
|
|
||||||
|
#include <list>
|
||||||
|
|
||||||
#include "glclasses.hpp"
|
#include "glclasses.hpp"
|
||||||
|
|
||||||
#include "game.hpp"
|
#include "game.hpp"
|
||||||
|
@ -32,6 +34,10 @@ namespace endofthejedi {
|
||||||
|
|
||||||
void addModel(const std::string &filename, PolygonModel **dest);
|
void addModel(const std::string &filename, PolygonModel **dest);
|
||||||
|
|
||||||
|
void addExplosionEffect(const glm::vec2 &pos, size_t n, float duration);
|
||||||
|
|
||||||
|
void advanceGraphicObjects(float dt);
|
||||||
|
|
||||||
// shortcuts which use the lower versions
|
// shortcuts which use the lower versions
|
||||||
glm::mat4 computeModelMatrix(const game::Planet *planet);
|
glm::mat4 computeModelMatrix(const game::Planet *planet);
|
||||||
glm::mat4 computeModelMatrix(const game::Missile *missile);
|
glm::mat4 computeModelMatrix(const game::Missile *missile);
|
||||||
|
@ -55,6 +61,9 @@ namespace endofthejedi {
|
||||||
// for accessing
|
// for accessing
|
||||||
const game::State *m_state;
|
const game::State *m_state;
|
||||||
|
|
||||||
ParticleBatch *m_particles;
|
std::list<ParticleBatch*> m_particles;
|
||||||
|
|
||||||
|
// time value for last rendering cycle (-1 after setup/startup)
|
||||||
|
float m_lastTime;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,8 @@
|
||||||
namespace game {
|
namespace game {
|
||||||
void State::init()
|
void State::init()
|
||||||
{
|
{
|
||||||
|
m_nextId = 0;
|
||||||
|
m_time = 0.0;
|
||||||
m_shipRadius = 0.02;
|
m_shipRadius = 0.02;
|
||||||
m_maxMissileDistance = 2.0;
|
m_maxMissileDistance = 2.0;
|
||||||
m_playerRespawnTime = 2.0;
|
m_playerRespawnTime = 2.0;
|
||||||
|
@ -253,6 +255,8 @@ namespace game {
|
||||||
|
|
||||||
void State::advance(float dt)
|
void State::advance(float dt)
|
||||||
{
|
{
|
||||||
|
m_time += dt;
|
||||||
|
|
||||||
advancePlayerShipSpawns(dt);
|
advancePlayerShipSpawns(dt);
|
||||||
|
|
||||||
advanceExplosions(dt);
|
advanceExplosions(dt);
|
||||||
|
|
|
@ -77,6 +77,9 @@ namespace game {
|
||||||
// delete traces with this command
|
// delete traces with this command
|
||||||
void deleteTrace(Trace *trace); // using a pointer
|
void deleteTrace(Trace *trace); // using a pointer
|
||||||
|
|
||||||
|
// get the current time
|
||||||
|
float timestamp() const { return m_time; }
|
||||||
|
|
||||||
/*************************************************************************/
|
/*************************************************************************/
|
||||||
/* Rendering */
|
/* Rendering */
|
||||||
/*************************************************************************/
|
/*************************************************************************/
|
||||||
|
@ -115,7 +118,8 @@ namespace game {
|
||||||
float m_playerRespawnTime;
|
float m_playerRespawnTime;
|
||||||
float m_shipRadius;
|
float m_shipRadius;
|
||||||
float m_defaultEnergy;
|
float m_defaultEnergy;
|
||||||
int m_nextId=0;
|
int m_nextId;
|
||||||
int m_maxNumTraces;
|
int m_maxNumTraces;
|
||||||
|
float m_time;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue