Compare commits
No commits in common. "main" and "planet-deep" have entirely different histories.
main
...
planet-dee
6
.gitignore
vendored
6
.gitignore
vendored
@ -400,8 +400,4 @@ jumpingbird.*
|
|||||||
jumpingbird.data
|
jumpingbird.data
|
||||||
build
|
build
|
||||||
build-emcmake
|
build-emcmake
|
||||||
thirdparty
|
thirdparty1
|
||||||
|
|
||||||
proj-web/build
|
|
||||||
proj-windows/build
|
|
||||||
public
|
|
||||||
|
|||||||
43
AnimatedModel.h
Normal file
43
AnimatedModel.h
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
|
||||||
|
#include "Renderer.h"
|
||||||
|
#include "TextureManager.h"
|
||||||
|
|
||||||
|
namespace ZL
|
||||||
|
{
|
||||||
|
|
||||||
|
struct MeshGroup
|
||||||
|
{
|
||||||
|
std::vector<std::shared_ptr<Texture>> textures;
|
||||||
|
std::vector<VertexDataStruct> meshes;
|
||||||
|
std::vector<VertexRenderStruct> renderMeshes;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AnimatedModel
|
||||||
|
{
|
||||||
|
std::vector<MeshGroup> parts;
|
||||||
|
|
||||||
|
void RefreshRenderMeshes()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < parts.size(); i++)
|
||||||
|
{
|
||||||
|
parts[i].renderMeshes.resize(parts[i].meshes.size());
|
||||||
|
|
||||||
|
for (int j = 0; j < parts[i].meshes.size(); j++)
|
||||||
|
{
|
||||||
|
parts[i].renderMeshes[j].AssignFrom(parts[i].meshes[j]);
|
||||||
|
parts[i].renderMeshes[j].RefreshVBO();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
719
BoneAnimatedModel.cpp
Normal file
719
BoneAnimatedModel.cpp
Normal file
@ -0,0 +1,719 @@
|
|||||||
|
#include "BoneAnimatedModel.h"
|
||||||
|
#include <regex>
|
||||||
|
#include <string>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
namespace ZL
|
||||||
|
{
|
||||||
|
|
||||||
|
int getIndexByValue(const std::string& name, const std::vector<std::string>& words)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < words.size(); i++)
|
||||||
|
{
|
||||||
|
if (words[i] == name)
|
||||||
|
{
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void BoneSystem::LoadFromFile(const std::string& fileName, const std::string& ZIPFileName)
|
||||||
|
{
|
||||||
|
std::ifstream filestream;
|
||||||
|
std::istringstream zipStream;
|
||||||
|
|
||||||
|
if (!ZIPFileName.empty())
|
||||||
|
{
|
||||||
|
std::vector<char> fileData = readFileFromZIP(fileName, ZIPFileName);
|
||||||
|
std::string fileContents(fileData.begin(), fileData.end());
|
||||||
|
zipStream.str(fileContents);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
filestream.open(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::istream& f = (!ZIPFileName.empty()) ? static_cast<std::istream&>(zipStream) : static_cast<std::istream&>(filestream);
|
||||||
|
|
||||||
|
//Skip first 5 lines
|
||||||
|
std::string tempLine;
|
||||||
|
for (int i = 0; i < 5; i++)
|
||||||
|
{
|
||||||
|
std::getline(f, tempLine);
|
||||||
|
}
|
||||||
|
std::getline(f, tempLine);
|
||||||
|
|
||||||
|
static const std::regex pattern_count(R"(\d+)");
|
||||||
|
static const std::regex pattern_float(R"([-]?\d+\.\d+)");
|
||||||
|
static const std::regex pattern_int(R"([-]?\d+)");
|
||||||
|
static const std::regex pattern_boneChildren(R"(\'([^\']+)\')");
|
||||||
|
static const std::regex pattern_bone_weight(R"(\'([^\']+)\'.*?([-]?\d+\.\d+))");
|
||||||
|
|
||||||
|
std::smatch match;
|
||||||
|
|
||||||
|
int numberBones;
|
||||||
|
|
||||||
|
if (std::regex_search(tempLine, match, pattern_count)) {
|
||||||
|
std::string number_str = match.str();
|
||||||
|
numberBones = std::stoi(number_str);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw std::runtime_error("No number found in the input string.");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Bone> bones;
|
||||||
|
std::vector<std::string> boneNames;
|
||||||
|
std::vector<std::string> boneParentNames;
|
||||||
|
std::unordered_map<std::string, std::vector<std::string>> boneChildren;
|
||||||
|
|
||||||
|
bones.resize(numberBones);
|
||||||
|
boneNames.resize(numberBones);
|
||||||
|
boneParentNames.resize(numberBones);
|
||||||
|
|
||||||
|
for (int i = 0; i < numberBones; i++)
|
||||||
|
{
|
||||||
|
std::getline(f, tempLine);
|
||||||
|
std::string boneName = tempLine.substr(6);
|
||||||
|
|
||||||
|
boneNames[i] = boneName;
|
||||||
|
|
||||||
|
std::getline(f, tempLine);
|
||||||
|
|
||||||
|
std::vector<float> floatValues;
|
||||||
|
|
||||||
|
auto b = tempLine.cbegin();
|
||||||
|
auto e = tempLine.cend();
|
||||||
|
while (std::regex_search(b, e, match, pattern_float)) {
|
||||||
|
floatValues.push_back(std::stof(match.str()));
|
||||||
|
b = match.suffix().first;
|
||||||
|
}
|
||||||
|
|
||||||
|
bones[i].boneStartWorld = Vector3f{ floatValues[0], floatValues[1], floatValues[2] };
|
||||||
|
|
||||||
|
|
||||||
|
std::getline(f, tempLine); //skip tail
|
||||||
|
|
||||||
|
std::getline(f, tempLine); //len
|
||||||
|
|
||||||
|
if (std::regex_search(tempLine, match, pattern_float)) {
|
||||||
|
std::string len_str = match.str();
|
||||||
|
bones[i].boneLength = std::stof(len_str);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw std::runtime_error("No number found in the input string.");
|
||||||
|
}
|
||||||
|
//---------- matrix begin
|
||||||
|
|
||||||
|
std::getline(f, tempLine);
|
||||||
|
|
||||||
|
b = tempLine.cbegin();
|
||||||
|
e = tempLine.cend();
|
||||||
|
floatValues.clear();
|
||||||
|
while (std::regex_search(b, e, match, pattern_float)) {
|
||||||
|
floatValues.push_back(std::stof(match.str()));
|
||||||
|
b = match.suffix().first;
|
||||||
|
}
|
||||||
|
|
||||||
|
bones[i].boneMatrixWorld.m[0] = floatValues[0];
|
||||||
|
bones[i].boneMatrixWorld.m[0 + 1 * 3] = floatValues[1];
|
||||||
|
bones[i].boneMatrixWorld.m[0 + 2 * 3] = floatValues[2];
|
||||||
|
|
||||||
|
|
||||||
|
std::getline(f, tempLine);
|
||||||
|
|
||||||
|
b = tempLine.cbegin();
|
||||||
|
e = tempLine.cend();
|
||||||
|
floatValues.clear();
|
||||||
|
while (std::regex_search(b, e, match, pattern_float)) {
|
||||||
|
floatValues.push_back(std::stof(match.str()));
|
||||||
|
b = match.suffix().first;
|
||||||
|
}
|
||||||
|
|
||||||
|
bones[i].boneMatrixWorld.m[1] = floatValues[0];
|
||||||
|
bones[i].boneMatrixWorld.m[1 + 1 * 3] = floatValues[1];
|
||||||
|
bones[i].boneMatrixWorld.m[1 + 2 * 3] = floatValues[2];
|
||||||
|
|
||||||
|
|
||||||
|
std::getline(f, tempLine);
|
||||||
|
|
||||||
|
b = tempLine.cbegin();
|
||||||
|
e = tempLine.cend();
|
||||||
|
floatValues.clear();
|
||||||
|
while (std::regex_search(b, e, match, pattern_float)) {
|
||||||
|
floatValues.push_back(std::stof(match.str()));
|
||||||
|
b = match.suffix().first;
|
||||||
|
}
|
||||||
|
|
||||||
|
bones[i].boneMatrixWorld.m[2] = floatValues[0];
|
||||||
|
bones[i].boneMatrixWorld.m[2 + 1 * 3] = floatValues[1];
|
||||||
|
bones[i].boneMatrixWorld.m[2 + 2 * 3] = floatValues[2];
|
||||||
|
|
||||||
|
//----------- matrix end
|
||||||
|
std::getline(f, tempLine); //parent
|
||||||
|
|
||||||
|
if (tempLine == " Parent: None")
|
||||||
|
{
|
||||||
|
bones[i].parent = -1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::string boneParent = tempLine.substr(10);
|
||||||
|
boneParentNames[i] = boneParent;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::getline(f, tempLine); //children
|
||||||
|
|
||||||
|
b = tempLine.cbegin();
|
||||||
|
e = tempLine.cend();
|
||||||
|
while (std::regex_search(b, e, match, pattern_boneChildren)) {
|
||||||
|
|
||||||
|
boneChildren[boneName].push_back(match.str(1));
|
||||||
|
b = match.suffix().first;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//Now process all the bones:
|
||||||
|
for (int i = 0; i < numberBones; i++)
|
||||||
|
{
|
||||||
|
std::string boneName = boneNames[i];
|
||||||
|
std::string boneParent = boneParentNames[i];
|
||||||
|
if (boneParent == "")
|
||||||
|
{
|
||||||
|
bones[i].parent = -1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bones[i].parent = getIndexByValue(boneParent, boneNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int j = 0; j < boneChildren[boneName].size(); j++)
|
||||||
|
{
|
||||||
|
bones[i].children.push_back(getIndexByValue(boneChildren[boneName][j], boneNames));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*if (boneName == "Bone.020")
|
||||||
|
{
|
||||||
|
std::cout << i << std::endl;
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
startBones = bones;
|
||||||
|
currentBones = bones;
|
||||||
|
|
||||||
|
///std::cout << "Hello!" << std::endl;
|
||||||
|
|
||||||
|
std::getline(f, tempLine); //vertice count
|
||||||
|
int numberVertices;
|
||||||
|
|
||||||
|
if (std::regex_search(tempLine, match, pattern_count)) {
|
||||||
|
std::string number_str = match.str();
|
||||||
|
numberVertices = std::stoi(number_str);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw std::runtime_error("No number found in the input string.");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Vector3f> vertices;
|
||||||
|
|
||||||
|
vertices.resize(numberVertices);
|
||||||
|
for (int i = 0; i < numberVertices; i++)
|
||||||
|
{
|
||||||
|
std::getline(f, tempLine);
|
||||||
|
|
||||||
|
std::vector<float> floatValues;
|
||||||
|
|
||||||
|
auto b = tempLine.cbegin();
|
||||||
|
auto e = tempLine.cend();
|
||||||
|
while (std::regex_search(b, e, match, pattern_float)) {
|
||||||
|
floatValues.push_back(std::stof(match.str()));
|
||||||
|
b = match.suffix().first;
|
||||||
|
}
|
||||||
|
|
||||||
|
vertices[i] = Vector3f{floatValues[0], floatValues[1], floatValues[2]};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//==== process uv and normals begin
|
||||||
|
|
||||||
|
std::cout << "Hello x1" << std::endl;
|
||||||
|
|
||||||
|
std::getline(f, tempLine); //===UV Coordinates:
|
||||||
|
|
||||||
|
std::getline(f, tempLine); //triangle count
|
||||||
|
int numberTriangles;
|
||||||
|
|
||||||
|
if (std::regex_search(tempLine, match, pattern_count)) {
|
||||||
|
std::string number_str = match.str();
|
||||||
|
numberTriangles = std::stoi(number_str);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw std::runtime_error("No number found in the input string.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Now process UVs
|
||||||
|
std::vector<std::array<Vector2f, 3>> uvCoords;
|
||||||
|
|
||||||
|
uvCoords.resize(numberTriangles);
|
||||||
|
|
||||||
|
for (int i = 0; i < numberTriangles; i++)
|
||||||
|
{
|
||||||
|
std::getline(f, tempLine); //Face 0
|
||||||
|
|
||||||
|
int uvCount;
|
||||||
|
std::getline(f, tempLine);
|
||||||
|
if (std::regex_search(tempLine, match, pattern_count)) {
|
||||||
|
std::string number_str = match.str();
|
||||||
|
uvCount = std::stoi(number_str);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw std::runtime_error("No number found in the input string.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uvCount != 3)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("more than 3 uvs");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<float> floatValues;
|
||||||
|
|
||||||
|
for (int j = 0; j < 3; j++)
|
||||||
|
{
|
||||||
|
std::getline(f, tempLine); //UV <Vector (-0.3661, -1.1665)>
|
||||||
|
|
||||||
|
auto b = tempLine.cbegin();
|
||||||
|
auto e = tempLine.cend();
|
||||||
|
floatValues.clear();
|
||||||
|
while (std::regex_search(b, e, match, pattern_float)) {
|
||||||
|
floatValues.push_back(std::stof(match.str()));
|
||||||
|
b = match.suffix().first;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (floatValues.size() != 2)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("more than 2 uvs---");
|
||||||
|
}
|
||||||
|
|
||||||
|
uvCoords[i][j] = Vector2f{ floatValues[0],floatValues[1] };
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "Hello eee" << std::endl;
|
||||||
|
|
||||||
|
std::getline(f, tempLine); //===Normals:
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<Vector3f> normals;
|
||||||
|
|
||||||
|
normals.resize(numberVertices);
|
||||||
|
for (int i = 0; i < numberVertices; i++)
|
||||||
|
{
|
||||||
|
std::getline(f, tempLine);
|
||||||
|
|
||||||
|
std::vector<float> floatValues;
|
||||||
|
|
||||||
|
auto b = tempLine.cbegin();
|
||||||
|
auto e = tempLine.cend();
|
||||||
|
while (std::regex_search(b, e, match, pattern_float)) {
|
||||||
|
floatValues.push_back(std::stof(match.str()));
|
||||||
|
b = match.suffix().first;
|
||||||
|
}
|
||||||
|
|
||||||
|
normals[i] = Vector3f{ floatValues[0], floatValues[1], floatValues[2] };
|
||||||
|
}
|
||||||
|
|
||||||
|
//==== process uv and normals end
|
||||||
|
|
||||||
|
std::getline(f, tempLine); //triangle count.
|
||||||
|
//numberTriangles; //Need to check if new value is the same as was read before
|
||||||
|
|
||||||
|
if (std::regex_search(tempLine, match, pattern_count)) {
|
||||||
|
std::string number_str = match.str();
|
||||||
|
numberTriangles = std::stoi(number_str);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw std::runtime_error("No number found in the input string.");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::array<int, 3>> triangles;
|
||||||
|
|
||||||
|
triangles.resize(numberTriangles);
|
||||||
|
for (int i = 0; i < numberTriangles; i++)
|
||||||
|
{
|
||||||
|
std::getline(f, tempLine);
|
||||||
|
|
||||||
|
std::vector<int> intValues;
|
||||||
|
|
||||||
|
auto b = tempLine.cbegin();
|
||||||
|
auto e = tempLine.cend();
|
||||||
|
while (std::regex_search(b, e, match, pattern_int)) {
|
||||||
|
intValues.push_back(std::stoi(match.str()));
|
||||||
|
b = match.suffix().first;
|
||||||
|
}
|
||||||
|
|
||||||
|
triangles[i] = { intValues[0], intValues[1], intValues[2] };
|
||||||
|
}
|
||||||
|
|
||||||
|
std::getline(f, tempLine);//=== Vertex Weights ===
|
||||||
|
std::vector<std::array<BoneWeight, MAX_BONE_COUNT>> localVerticesBoneWeight;
|
||||||
|
localVerticesBoneWeight.resize(numberVertices);
|
||||||
|
|
||||||
|
for (int i = 0; i < numberVertices; i++)
|
||||||
|
{
|
||||||
|
std::getline(f, tempLine); //skip Vertex 0:
|
||||||
|
std::getline(f, tempLine); //vertex group count
|
||||||
|
int boneCount;
|
||||||
|
|
||||||
|
if (std::regex_search(tempLine, match, pattern_count)) {
|
||||||
|
std::string number_str = match.str();
|
||||||
|
boneCount = std::stoi(number_str);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw std::runtime_error("No number found in the input string.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (boneCount > MAX_BONE_COUNT)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("more than 5 bones");
|
||||||
|
}
|
||||||
|
|
||||||
|
float sumWeights = 0;
|
||||||
|
|
||||||
|
for (int j = 0; j < boneCount; j++)
|
||||||
|
{
|
||||||
|
std::getline(f, tempLine); //Group: 'Bone', Weight: 0.9929084181785583
|
||||||
|
if (std::regex_search(tempLine, match, pattern_bone_weight)) {
|
||||||
|
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> (<28><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>)
|
||||||
|
std::string word = match.str(1);
|
||||||
|
double weight = std::stod(match.str(2));
|
||||||
|
|
||||||
|
int boneNumber = getIndexByValue(word, boneNames);
|
||||||
|
localVerticesBoneWeight[i][j].boneIndex = boneNumber;
|
||||||
|
localVerticesBoneWeight[i][j].weight = weight;
|
||||||
|
sumWeights += weight;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw std::runtime_error("No match found in the input string.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Normalize weights:
|
||||||
|
for (int j = 0; j < boneCount; j++)
|
||||||
|
{
|
||||||
|
localVerticesBoneWeight[i][j].weight = localVerticesBoneWeight[i][j].weight / sumWeights;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
std::getline(f, tempLine);//=== Animation Keyframes ===
|
||||||
|
std::getline(f, tempLine);//=== Bone Transforms per Keyframe ===
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::getline(f, tempLine);
|
||||||
|
int numberKeyFrames;
|
||||||
|
|
||||||
|
if (std::regex_search(tempLine, match, pattern_count)) {
|
||||||
|
std::string number_str = match.str();
|
||||||
|
numberKeyFrames = std::stoi(number_str);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw std::runtime_error("No number found in the input string.");
|
||||||
|
}
|
||||||
|
|
||||||
|
animations.resize(1);
|
||||||
|
|
||||||
|
animations[0].keyFrames.resize(numberKeyFrames);
|
||||||
|
|
||||||
|
for (int i = 0; i < numberKeyFrames; i++)
|
||||||
|
{
|
||||||
|
std::getline(f, tempLine);
|
||||||
|
int numberFrame;
|
||||||
|
|
||||||
|
if (std::regex_search(tempLine, match, pattern_count)) {
|
||||||
|
std::string number_str = match.str();
|
||||||
|
numberFrame = std::stoi(number_str);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw std::runtime_error("No number found in the input string.");
|
||||||
|
}
|
||||||
|
|
||||||
|
animations[0].keyFrames[i].frame = numberFrame;
|
||||||
|
|
||||||
|
animations[0].keyFrames[i].bones.resize(numberBones);
|
||||||
|
|
||||||
|
for (int j = 0; j < numberBones; j++)
|
||||||
|
{
|
||||||
|
std::getline(f, tempLine);
|
||||||
|
std::string boneName = tempLine.substr(8);
|
||||||
|
int boneNumber = getIndexByValue(boneName, boneNames);
|
||||||
|
animations[0].keyFrames[i].bones[boneNumber] = startBones[boneNumber];
|
||||||
|
|
||||||
|
std::getline(f, tempLine); // Location: <Vector (0.0000, 0.0000, -0.0091)>
|
||||||
|
|
||||||
|
std::vector<float> floatValues;
|
||||||
|
|
||||||
|
auto b = tempLine.cbegin();
|
||||||
|
auto e = tempLine.cend();
|
||||||
|
while (std::regex_search(b, e, match, pattern_float)) {
|
||||||
|
floatValues.push_back(std::stof(match.str()));
|
||||||
|
b = match.suffix().first;
|
||||||
|
}
|
||||||
|
|
||||||
|
animations[0].keyFrames[i].bones[boneNumber].boneStartWorld = Vector3f{ floatValues[0], floatValues[1], floatValues[2] };
|
||||||
|
|
||||||
|
std::getline(f, tempLine); // Rotation
|
||||||
|
std::getline(f, tempLine); // Matrix
|
||||||
|
|
||||||
|
//=============== Matrix begin ==================
|
||||||
|
std::getline(f, tempLine);
|
||||||
|
|
||||||
|
|
||||||
|
b = tempLine.cbegin();
|
||||||
|
e = tempLine.cend();
|
||||||
|
floatValues.clear();
|
||||||
|
while (std::regex_search(b, e, match, pattern_float)) {
|
||||||
|
floatValues.push_back(std::stof(match.str()));
|
||||||
|
b = match.suffix().first;
|
||||||
|
}
|
||||||
|
|
||||||
|
animations[0].keyFrames[i].bones[boneNumber].boneMatrixWorld.m[0] = floatValues[0];
|
||||||
|
animations[0].keyFrames[i].bones[boneNumber].boneMatrixWorld.m[0 + 1 * 4] = floatValues[1];
|
||||||
|
animations[0].keyFrames[i].bones[boneNumber].boneMatrixWorld.m[0 + 2 * 4] = floatValues[2];
|
||||||
|
animations[0].keyFrames[i].bones[boneNumber].boneMatrixWorld.m[0 + 3 * 4] = floatValues[3];
|
||||||
|
|
||||||
|
|
||||||
|
std::getline(f, tempLine);
|
||||||
|
b = tempLine.cbegin();
|
||||||
|
e = tempLine.cend();
|
||||||
|
floatValues.clear();
|
||||||
|
while (std::regex_search(b, e, match, pattern_float)) {
|
||||||
|
floatValues.push_back(std::stof(match.str()));
|
||||||
|
b = match.suffix().first;
|
||||||
|
}
|
||||||
|
|
||||||
|
animations[0].keyFrames[i].bones[boneNumber].boneMatrixWorld.m[1] = floatValues[0];
|
||||||
|
animations[0].keyFrames[i].bones[boneNumber].boneMatrixWorld.m[1 + 1 * 4] = floatValues[1];
|
||||||
|
animations[0].keyFrames[i].bones[boneNumber].boneMatrixWorld.m[1 + 2 * 4] = floatValues[2];
|
||||||
|
animations[0].keyFrames[i].bones[boneNumber].boneMatrixWorld.m[1 + 3 * 4] = floatValues[3];
|
||||||
|
|
||||||
|
std::getline(f, tempLine);
|
||||||
|
b = tempLine.cbegin();
|
||||||
|
e = tempLine.cend();
|
||||||
|
floatValues.clear();
|
||||||
|
while (std::regex_search(b, e, match, pattern_float)) {
|
||||||
|
floatValues.push_back(std::stof(match.str()));
|
||||||
|
b = match.suffix().first;
|
||||||
|
}
|
||||||
|
|
||||||
|
animations[0].keyFrames[i].bones[boneNumber].boneMatrixWorld.m[2] = floatValues[0];
|
||||||
|
animations[0].keyFrames[i].bones[boneNumber].boneMatrixWorld.m[2 + 1 * 4] = floatValues[1];
|
||||||
|
animations[0].keyFrames[i].bones[boneNumber].boneMatrixWorld.m[2 + 2 * 4] = floatValues[2];
|
||||||
|
animations[0].keyFrames[i].bones[boneNumber].boneMatrixWorld.m[2 + 3 * 4] = floatValues[3];
|
||||||
|
|
||||||
|
|
||||||
|
std::getline(f, tempLine);
|
||||||
|
b = tempLine.cbegin();
|
||||||
|
e = tempLine.cend();
|
||||||
|
floatValues.clear();
|
||||||
|
while (std::regex_search(b, e, match, pattern_float)) {
|
||||||
|
floatValues.push_back(std::stof(match.str()));
|
||||||
|
b = match.suffix().first;
|
||||||
|
}
|
||||||
|
|
||||||
|
animations[0].keyFrames[i].bones[boneNumber].boneMatrixWorld.m[3] = floatValues[0];
|
||||||
|
animations[0].keyFrames[i].bones[boneNumber].boneMatrixWorld.m[3 + 1 * 4] = floatValues[1];
|
||||||
|
animations[0].keyFrames[i].bones[boneNumber].boneMatrixWorld.m[3 + 2 * 4] = floatValues[2];
|
||||||
|
animations[0].keyFrames[i].bones[boneNumber].boneMatrixWorld.m[3 + 3 * 4] = floatValues[3];
|
||||||
|
|
||||||
|
//std::getline(f, tempLine);// ignore last matrix line
|
||||||
|
|
||||||
|
//=============== Matrix end ==================
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now let's process bone weights and vertices
|
||||||
|
|
||||||
|
for (int i = 0; i < numberTriangles; i++)
|
||||||
|
{
|
||||||
|
|
||||||
|
mesh.PositionData.push_back(vertices[triangles[i][0]]);
|
||||||
|
mesh.PositionData.push_back(vertices[triangles[i][1]]);
|
||||||
|
mesh.PositionData.push_back(vertices[triangles[i][2]]);
|
||||||
|
|
||||||
|
verticesBoneWeight.push_back(localVerticesBoneWeight[triangles[i][0]]);
|
||||||
|
verticesBoneWeight.push_back(localVerticesBoneWeight[triangles[i][1]]);
|
||||||
|
verticesBoneWeight.push_back(localVerticesBoneWeight[triangles[i][2]]);
|
||||||
|
|
||||||
|
mesh.TexCoordData.push_back(uvCoords[i][0]);
|
||||||
|
mesh.TexCoordData.push_back(uvCoords[i][1]);
|
||||||
|
mesh.TexCoordData.push_back(uvCoords[i][2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
startMesh = mesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BoneSystem::Interpolate(int frame)
|
||||||
|
{
|
||||||
|
int startingFrame = -1;
|
||||||
|
for (int i = 0; i < animations[0].keyFrames.size() - 1; i++)
|
||||||
|
{
|
||||||
|
int oldFrame = animations[0].keyFrames[i].frame;
|
||||||
|
int nextFrame = animations[0].keyFrames[i + 1].frame;
|
||||||
|
if (frame >= oldFrame && frame < nextFrame)
|
||||||
|
{
|
||||||
|
startingFrame = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startingFrame == -1)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Exception here");
|
||||||
|
}
|
||||||
|
|
||||||
|
int modifiedFrameNumber = frame - animations[0].keyFrames[startingFrame].frame;
|
||||||
|
|
||||||
|
int diffFrames = animations[0].keyFrames[startingFrame + 1].frame - animations[0].keyFrames[startingFrame].frame;
|
||||||
|
|
||||||
|
float t = (modifiedFrameNumber + 0.f) / diffFrames;
|
||||||
|
|
||||||
|
std::vector<Bone>& oneFrameBones = animations[0].keyFrames[startingFrame].bones;
|
||||||
|
std::vector<Bone>& nextFrameBones = animations[0].keyFrames[startingFrame+1].bones;
|
||||||
|
|
||||||
|
std::vector<Matrix4f> skinningMatrixForEachBone;
|
||||||
|
//std::vector<Matrix3f> skinningMatrixForEachBone;
|
||||||
|
skinningMatrixForEachBone.resize(currentBones.size());
|
||||||
|
|
||||||
|
|
||||||
|
for (int i = 0; i < currentBones.size(); i++)
|
||||||
|
{
|
||||||
|
currentBones[i].boneStartWorld.v[0] = oneFrameBones[i].boneStartWorld.v[0] + t * (nextFrameBones[i].boneStartWorld.v[0] - oneFrameBones[i].boneStartWorld.v[0]);
|
||||||
|
currentBones[i].boneStartWorld.v[1] = oneFrameBones[i].boneStartWorld.v[1] + t * (nextFrameBones[i].boneStartWorld.v[1] - oneFrameBones[i].boneStartWorld.v[1]);
|
||||||
|
currentBones[i].boneStartWorld.v[2] = oneFrameBones[i].boneStartWorld.v[2] + t * (nextFrameBones[i].boneStartWorld.v[2] - oneFrameBones[i].boneStartWorld.v[2]);
|
||||||
|
|
||||||
|
Matrix3f oneFrameBonesMatrix;
|
||||||
|
|
||||||
|
oneFrameBonesMatrix.m[0] = oneFrameBones[i].boneMatrixWorld.m[0];
|
||||||
|
oneFrameBonesMatrix.m[1] = oneFrameBones[i].boneMatrixWorld.m[1];
|
||||||
|
oneFrameBonesMatrix.m[2] = oneFrameBones[i].boneMatrixWorld.m[2];
|
||||||
|
|
||||||
|
oneFrameBonesMatrix.m[3] = oneFrameBones[i].boneMatrixWorld.m[0 + 1*4];
|
||||||
|
oneFrameBonesMatrix.m[4] = oneFrameBones[i].boneMatrixWorld.m[1 + 1*4];
|
||||||
|
oneFrameBonesMatrix.m[5] = oneFrameBones[i].boneMatrixWorld.m[2 + 1*4];
|
||||||
|
|
||||||
|
oneFrameBonesMatrix.m[6] = oneFrameBones[i].boneMatrixWorld.m[0 + 2*4];
|
||||||
|
oneFrameBonesMatrix.m[7] = oneFrameBones[i].boneMatrixWorld.m[1 + 2*4];
|
||||||
|
oneFrameBonesMatrix.m[8] = oneFrameBones[i].boneMatrixWorld.m[2 + 2*4];
|
||||||
|
|
||||||
|
Matrix3f nextFrameBonesMatrix;
|
||||||
|
|
||||||
|
nextFrameBonesMatrix.m[0] = nextFrameBones[i].boneMatrixWorld.m[0];
|
||||||
|
nextFrameBonesMatrix.m[1] = nextFrameBones[i].boneMatrixWorld.m[1];
|
||||||
|
nextFrameBonesMatrix.m[2] = nextFrameBones[i].boneMatrixWorld.m[2];
|
||||||
|
|
||||||
|
nextFrameBonesMatrix.m[3] = nextFrameBones[i].boneMatrixWorld.m[0 + 1 * 4];
|
||||||
|
nextFrameBonesMatrix.m[4] = nextFrameBones[i].boneMatrixWorld.m[1 + 1 * 4];
|
||||||
|
nextFrameBonesMatrix.m[5] = nextFrameBones[i].boneMatrixWorld.m[2 + 1 * 4];
|
||||||
|
|
||||||
|
nextFrameBonesMatrix.m[6] = nextFrameBones[i].boneMatrixWorld.m[0 + 2 * 4];
|
||||||
|
nextFrameBonesMatrix.m[7] = nextFrameBones[i].boneMatrixWorld.m[1 + 2 * 4];
|
||||||
|
nextFrameBonesMatrix.m[8] = nextFrameBones[i].boneMatrixWorld.m[2 + 2 * 4];
|
||||||
|
|
||||||
|
Vector4f q1 = MatrixToQuat(oneFrameBonesMatrix);
|
||||||
|
Vector4f q2 = MatrixToQuat(nextFrameBonesMatrix);
|
||||||
|
Vector4f q1_norm = q1.normalized();
|
||||||
|
Vector4f q2_norm = q2.normalized();
|
||||||
|
|
||||||
|
Vector4f result = slerp(q1_norm, q2_norm, t);
|
||||||
|
|
||||||
|
Matrix3f boneMatrixWorld3 = QuatToMatrix(result);
|
||||||
|
|
||||||
|
currentBones[i].boneMatrixWorld = MakeMatrix4x4(boneMatrixWorld3, currentBones[i].boneStartWorld);
|
||||||
|
|
||||||
|
Matrix4f currentBoneMatrixWorld4 = currentBones[i].boneMatrixWorld;
|
||||||
|
Matrix4f startBoneMatrixWorld4 = animations[0].keyFrames[0].bones[i].boneMatrixWorld;
|
||||||
|
|
||||||
|
Matrix4f inverstedStartBoneMatrixWorld4 = InverseMatrix(startBoneMatrixWorld4);
|
||||||
|
|
||||||
|
skinningMatrixForEachBone[i] = MultMatrixMatrix(currentBoneMatrixWorld4, inverstedStartBoneMatrixWorld4);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
for (int i = 0; i < currentBones.size(); i++)
|
||||||
|
{
|
||||||
|
currentBones[i].boneStartWorld = oneFrameBones[i].boneStartWorld;
|
||||||
|
currentBones[i].boneMatrixWorld = oneFrameBones[i].boneMatrixWorld;
|
||||||
|
//Matrix4f currentBoneMatrixWorld4 = MakeMatrix4x4(currentBones[i].boneMatrixWorld, currentBones[i].boneStartWorld);
|
||||||
|
//Matrix4f startBoneMatrixWorld4 = MakeMatrix4x4(animations[0].keyFrames[0].bones[i].boneMatrixWorld, animations[0].keyFrames[0].bones[i].boneStartWorld);
|
||||||
|
Matrix4f currentBoneMatrixWorld4 = currentBones[i].boneMatrixWorld;
|
||||||
|
Matrix4f startBoneMatrixWorld4 = animations[0].keyFrames[0].bones[i].boneMatrixWorld;
|
||||||
|
Matrix4f inverstedStartBoneMatrixWorld4 = InverseMatrix(startBoneMatrixWorld4);
|
||||||
|
skinningMatrixForEachBone[i] = MultMatrixMatrix(currentBoneMatrixWorld4, inverstedStartBoneMatrixWorld4);
|
||||||
|
|
||||||
|
if (i == 10)
|
||||||
|
{
|
||||||
|
std::cout << i << std::endl;
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
for (int i = 0; i < mesh.PositionData.size(); i++)
|
||||||
|
{
|
||||||
|
Vector4f originalPos = {
|
||||||
|
startMesh.PositionData[i].v[0],
|
||||||
|
startMesh.PositionData[i].v[1],
|
||||||
|
startMesh.PositionData[i].v[2], 1.0};
|
||||||
|
|
||||||
|
Vector4f finalPos = Vector4f{0.f, 0.f, 0.f, 0.f};
|
||||||
|
|
||||||
|
bool vMoved = false;
|
||||||
|
//Vector3f finalPos = Vector3f{ 0.f, 0.f, 0.f };
|
||||||
|
|
||||||
|
for (int j = 0; j < MAX_BONE_COUNT; j++)
|
||||||
|
{
|
||||||
|
if (verticesBoneWeight[i][j].weight != 0)
|
||||||
|
{
|
||||||
|
vMoved = true;
|
||||||
|
//finalPos = finalPos + MultVectorMatrix(originalPos, skinningMatrixForEachBone[verticesBoneWeight[i][j].boneIndex]) * verticesBoneWeight[i][j].weight;
|
||||||
|
finalPos = finalPos + MultMatrixVector(skinningMatrixForEachBone[verticesBoneWeight[i][j].boneIndex], originalPos) * verticesBoneWeight[i][j].weight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (abs(finalPos.v[0] - originalPos.v[0]) > 1 || abs(finalPos.v[1] - originalPos.v[1]) > 1 || abs(finalPos.v[2] - originalPos.v[2]) > 1)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!vMoved)
|
||||||
|
{
|
||||||
|
finalPos = originalPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
mesh.PositionData[i].v[0] = finalPos.v[0];
|
||||||
|
mesh.PositionData[i].v[1] = finalPos.v[1];
|
||||||
|
mesh.PositionData[i].v[2] = finalPos.v[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
60
BoneAnimatedModel.h
Normal file
60
BoneAnimatedModel.h
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "ZLMath.h"
|
||||||
|
#include "Renderer.h"
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
|
||||||
|
namespace ZL
|
||||||
|
{
|
||||||
|
constexpr int MAX_BONE_COUNT = 6;
|
||||||
|
struct Bone
|
||||||
|
{
|
||||||
|
Vector3f boneStartWorld;
|
||||||
|
float boneLength;
|
||||||
|
Matrix4f boneMatrixWorld;
|
||||||
|
// boneVector = boneLength * (0, 1, 0) <20> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||||
|
// Then multiply by boneMatrixWorld <20> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>
|
||||||
|
|
||||||
|
int parent;
|
||||||
|
std::vector<int> children;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BoneWeight
|
||||||
|
{
|
||||||
|
int boneIndex = -1;
|
||||||
|
float weight = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AnimationKeyFrame
|
||||||
|
{
|
||||||
|
int frame;
|
||||||
|
std::vector<Bone> bones;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Animation
|
||||||
|
{
|
||||||
|
std::vector<AnimationKeyFrame> keyFrames;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BoneSystem
|
||||||
|
{
|
||||||
|
VertexDataStruct mesh;
|
||||||
|
VertexDataStruct startMesh;
|
||||||
|
std::vector<std::array<BoneWeight, MAX_BONE_COUNT>> verticesBoneWeight;
|
||||||
|
|
||||||
|
Matrix4f armatureMatrix;
|
||||||
|
|
||||||
|
std::vector<Bone> startBones;
|
||||||
|
std::vector<Bone> currentBones;
|
||||||
|
|
||||||
|
std::vector<Animation> animations;
|
||||||
|
|
||||||
|
void LoadFromFile(const std::string& fileName, const std::string& ZIPFileName = "");
|
||||||
|
|
||||||
|
void Interpolate(int frame);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
544
CMakeLists.txt
Normal file
544
CMakeLists.txt
Normal file
@ -0,0 +1,544 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
|
project(space-game001 LANGUAGES CXX)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
set(BUILD_CONFIGS Debug Release)
|
||||||
|
|
||||||
|
# ==============================
|
||||||
|
# Папка для всех сторонних либ
|
||||||
|
# ==============================
|
||||||
|
set(THIRDPARTY_DIR "${CMAKE_SOURCE_DIR}/thirdparty1")
|
||||||
|
file(MAKE_DIRECTORY "${THIRDPARTY_DIR}")
|
||||||
|
|
||||||
|
macro(log msg)
|
||||||
|
message(STATUS "${msg}")
|
||||||
|
endmacro()
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# 1) ZLIB (zlib131.zip → zlib-1.3.1) - без изменений
|
||||||
|
# ===========================================
|
||||||
|
set(ZLIB_ARCHIVE "${THIRDPARTY_DIR}/zlib131.zip")
|
||||||
|
set(ZLIB_SRC_DIR "${THIRDPARTY_DIR}/zlib-1.3.1")
|
||||||
|
set(ZLIB_BUILD_DIR "${ZLIB_SRC_DIR}/build")
|
||||||
|
set(ZLIB_INSTALL_DIR "${ZLIB_SRC_DIR}/install")
|
||||||
|
|
||||||
|
if(NOT EXISTS "${ZLIB_ARCHIVE}")
|
||||||
|
log("Downloading zlib131.zip ...")
|
||||||
|
file(DOWNLOAD
|
||||||
|
"https://www.zlib.net/zlib131.zip"
|
||||||
|
"${ZLIB_ARCHIVE}"
|
||||||
|
SHOW_PROGRESS
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT EXISTS "${ZLIB_SRC_DIR}/CMakeLists.txt")
|
||||||
|
log("Extracting zlib131.zip to zlib-1.3.1 ...")
|
||||||
|
execute_process(
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E tar xvf "${ZLIB_ARCHIVE}"
|
||||||
|
WORKING_DIRECTORY "${THIRDPARTY_DIR}"
|
||||||
|
RESULT_VARIABLE _zlib_extract_res
|
||||||
|
)
|
||||||
|
if(NOT _zlib_extract_res EQUAL 0)
|
||||||
|
message(FATAL_ERROR "Failed to extract zlib archive")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
file(MAKE_DIRECTORY "${ZLIB_BUILD_DIR}")
|
||||||
|
|
||||||
|
# проверяем, собран ли уже zlib
|
||||||
|
set(_have_zlib FALSE)
|
||||||
|
foreach(candidate
|
||||||
|
"${ZLIB_INSTALL_DIR}/lib/zlibstatic.lib"
|
||||||
|
)
|
||||||
|
if(EXISTS "${candidate}")
|
||||||
|
set(_have_zlib TRUE)
|
||||||
|
break()
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
|
||||||
|
if(NOT _have_zlib)
|
||||||
|
log("Configuring zlib ...")
|
||||||
|
execute_process(
|
||||||
|
COMMAND ${CMAKE_COMMAND}
|
||||||
|
-G "${CMAKE_GENERATOR}"
|
||||||
|
-S "${ZLIB_SRC_DIR}"
|
||||||
|
-B "${ZLIB_BUILD_DIR}"
|
||||||
|
-DCMAKE_INSTALL_PREFIX=${ZLIB_INSTALL_DIR}
|
||||||
|
RESULT_VARIABLE _zlib_cfg_res
|
||||||
|
)
|
||||||
|
if(NOT _zlib_cfg_res EQUAL 0)
|
||||||
|
message(FATAL_ERROR "zlib configure failed")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
foreach(cfg IN LISTS BUILD_CONFIGS)
|
||||||
|
log("Building ZLIB (${cfg}) ...")
|
||||||
|
execute_process(
|
||||||
|
COMMAND ${CMAKE_COMMAND}
|
||||||
|
--build "${ZLIB_BUILD_DIR}" --config ${cfg}
|
||||||
|
RESULT_VARIABLE _zlib_build_res
|
||||||
|
)
|
||||||
|
if(NOT _zlib_build_res EQUAL 0)
|
||||||
|
message(FATAL_ERROR "ZLIB build failed for configuration ${cfg}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
log("Installing ZLIB (${cfg}) ...")
|
||||||
|
execute_process(
|
||||||
|
COMMAND ${CMAKE_COMMAND}
|
||||||
|
--install "${ZLIB_BUILD_DIR}" --config ${cfg}
|
||||||
|
RESULT_VARIABLE _zlib_inst_res
|
||||||
|
)
|
||||||
|
if(NOT _zlib_inst_res EQUAL 0)
|
||||||
|
message(FATAL_ERROR "ZLIB install failed for configuration ${cfg}")
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# ИСПРАВЛЕНИЕ: Используем свойства для конкретных конфигураций
|
||||||
|
add_library(zlib_external_lib UNKNOWN IMPORTED GLOBAL)
|
||||||
|
set_target_properties(zlib_external_lib PROPERTIES
|
||||||
|
# Динамическая линковка (если zlib.lib - это импорт-библиотека для zlibd.dll)
|
||||||
|
#IMPORTED_LOCATION_DEBUG "${ZLIB_INSTALL_DIR}/lib/zlibd.lib"
|
||||||
|
#IMPORTED_LOCATION_RELEASE "${ZLIB_INSTALL_DIR}/lib/zlib.lib"
|
||||||
|
|
||||||
|
# Можно также указать статические библиотеки, если вы хотите их использовать
|
||||||
|
IMPORTED_LOCATION_DEBUG "${ZLIB_INSTALL_DIR}/lib/zlibstaticd.lib"
|
||||||
|
IMPORTED_LOCATION_RELEASE "${ZLIB_INSTALL_DIR}/lib/zlibstatic.lib"
|
||||||
|
|
||||||
|
INTERFACE_INCLUDE_DIRECTORIES "${ZLIB_INSTALL_DIR}/include"
|
||||||
|
)
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# 2) SDL2 (release-2.32.10.zip → SDL-release-2.32.10) - без изменений
|
||||||
|
# ===========================================
|
||||||
|
set(SDL2_ARCHIVE "${THIRDPARTY_DIR}/release-2.32.10.zip")
|
||||||
|
set(SDL2_SRC_DIR "${THIRDPARTY_DIR}/SDL-release-2.32.10")
|
||||||
|
set(SDL2_BUILD_DIR "${SDL2_SRC_DIR}/build")
|
||||||
|
set(SDL2_INSTALL_DIR "${SDL2_SRC_DIR}/install")
|
||||||
|
|
||||||
|
if(NOT EXISTS "${SDL2_ARCHIVE}")
|
||||||
|
log("Downloading SDL2 release-2.32.10.zip ...")
|
||||||
|
file(DOWNLOAD
|
||||||
|
"https://github.com/libsdl-org/SDL/archive/refs/tags/release-2.32.10.zip"
|
||||||
|
"${SDL2_ARCHIVE}"
|
||||||
|
SHOW_PROGRESS
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT EXISTS "${SDL2_SRC_DIR}/CMakeLists.txt")
|
||||||
|
log("Extracting SDL2 archive to SDL-release-2.32.10 ...")
|
||||||
|
execute_process(
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E tar xvf "${SDL2_ARCHIVE}"
|
||||||
|
WORKING_DIRECTORY "${THIRDPARTY_DIR}"
|
||||||
|
RESULT_VARIABLE _sdl_extract_res
|
||||||
|
)
|
||||||
|
if(NOT _sdl_extract_res EQUAL 0)
|
||||||
|
message(FATAL_ERROR "Failed to extract SDL2 archive")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
file(MAKE_DIRECTORY "${SDL2_BUILD_DIR}")
|
||||||
|
|
||||||
|
set(_have_sdl2 FALSE)
|
||||||
|
foreach(candidate
|
||||||
|
"${SDL2_INSTALL_DIR}/lib/SDL2.lib"
|
||||||
|
"${SDL2_INSTALL_DIR}/lib/SDL2-static.lib"
|
||||||
|
"${SDL2_INSTALL_DIR}/lib/SDL2d.lib"
|
||||||
|
)
|
||||||
|
if(EXISTS "${candidate}")
|
||||||
|
set(_have_sdl2 TRUE)
|
||||||
|
break()
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
if(NOT _have_sdl2)
|
||||||
|
log("Configuring SDL2 ...")
|
||||||
|
execute_process(
|
||||||
|
COMMAND ${CMAKE_COMMAND}
|
||||||
|
-G "${CMAKE_GENERATOR}"
|
||||||
|
-S "${SDL2_SRC_DIR}"
|
||||||
|
-B "${SDL2_BUILD_DIR}"
|
||||||
|
-DCMAKE_INSTALL_PREFIX=${SDL2_INSTALL_DIR}
|
||||||
|
-DCMAKE_PREFIX_PATH=${ZLIB_INSTALL_DIR} # путь к zlib для SDL2
|
||||||
|
RESULT_VARIABLE _sdl_cfg_res
|
||||||
|
)
|
||||||
|
if(NOT _sdl_cfg_res EQUAL 0)
|
||||||
|
message(FATAL_ERROR "SDL2 configure failed")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# --- ИЗМЕНЕНИЕ: Цикл по конфигурациям Debug и Release ---
|
||||||
|
foreach(cfg IN LISTS BUILD_CONFIGS)
|
||||||
|
log("Building SDL2 (${cfg}) ...")
|
||||||
|
execute_process(
|
||||||
|
COMMAND ${CMAKE_COMMAND}
|
||||||
|
--build "${SDL2_BUILD_DIR}" --config ${cfg}
|
||||||
|
RESULT_VARIABLE _sdl_build_res
|
||||||
|
)
|
||||||
|
if(NOT _sdl_build_res EQUAL 0)
|
||||||
|
message(FATAL_ERROR "SDL2 build failed for configuration ${cfg}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
log("Installing SDL2 (${cfg}) ...")
|
||||||
|
execute_process(
|
||||||
|
COMMAND ${CMAKE_COMMAND}
|
||||||
|
--install "${SDL2_BUILD_DIR}" --config ${cfg}
|
||||||
|
RESULT_VARIABLE _sdl_inst_res
|
||||||
|
)
|
||||||
|
if(NOT _sdl_inst_res EQUAL 0)
|
||||||
|
message(FATAL_ERROR "SDL2 install failed for configuration ${cfg}")
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
# ------------------------------------------------------
|
||||||
|
endif()
|
||||||
|
|
||||||
|
|
||||||
|
# ИСПРАВЛЕНИЕ: SDL2: Используем свойства для конкретных конфигураций
|
||||||
|
add_library(SDL2_external_lib UNKNOWN IMPORTED GLOBAL)
|
||||||
|
set_target_properties(SDL2_external_lib PROPERTIES
|
||||||
|
# Динамическая линковка SDL2
|
||||||
|
IMPORTED_LOCATION_DEBUG "${SDL2_INSTALL_DIR}/lib/SDL2d.lib"
|
||||||
|
IMPORTED_LOCATION_RELEASE "${SDL2_INSTALL_DIR}/lib/SDL2.lib"
|
||||||
|
# Оба include-пути: и include, и include/SDL2
|
||||||
|
INTERFACE_INCLUDE_DIRECTORIES "${SDL2_INSTALL_DIR}/include;${SDL2_INSTALL_DIR}/include/SDL2"
|
||||||
|
)
|
||||||
|
|
||||||
|
# SDL2main (обычно статическая)
|
||||||
|
add_library(SDL2main_external_lib UNKNOWN IMPORTED GLOBAL)
|
||||||
|
set_target_properties(SDL2main_external_lib PROPERTIES
|
||||||
|
# ИСПРАВЛЕНО: Указываем пути для Debug и Release, используя
|
||||||
|
# соглашение, что Debug имеет суффикс 'd', а Release — нет.
|
||||||
|
IMPORTED_LOCATION_DEBUG "${SDL2_INSTALL_DIR}/lib/SDL2maind.lib"
|
||||||
|
IMPORTED_LOCATION_RELEASE "${SDL2_INSTALL_DIR}/lib/SDL2main.lib"
|
||||||
|
INTERFACE_INCLUDE_DIRECTORIES "${SDL2_INSTALL_DIR}/include"
|
||||||
|
)
|
||||||
|
|
||||||
|
log("-----${SDL2_INSTALL_DIR}/lib/SDL2maind.lib")
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# 3) libpng (v1.6.51.zip → libpng-1.6.51) - без изменений
|
||||||
|
# ===========================================
|
||||||
|
set(LIBPNG_ARCHIVE "${THIRDPARTY_DIR}/v1.6.51.zip")
|
||||||
|
set(LIBPNG_SRC_DIR "${THIRDPARTY_DIR}/libpng-1.6.51")
|
||||||
|
set(LIBPNG_BUILD_DIR "${LIBPNG_SRC_DIR}/build")
|
||||||
|
set(LIBPNG_INSTALL_DIR "${LIBPNG_SRC_DIR}/install") # на будущее
|
||||||
|
|
||||||
|
if(NOT EXISTS "${LIBPNG_ARCHIVE}")
|
||||||
|
log("Downloading libpng v1.6.51.zip ...")
|
||||||
|
file(DOWNLOAD
|
||||||
|
"https://github.com/pnggroup/libpng/archive/refs/tags/v1.6.51.zip"
|
||||||
|
"${LIBPNG_ARCHIVE}"
|
||||||
|
SHOW_PROGRESS
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT EXISTS "${LIBPNG_SRC_DIR}/CMakeLists.txt")
|
||||||
|
log("Extracting libpng v1.6.51.zip to libpng-1.6.51 ...")
|
||||||
|
execute_process(
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E tar xvf "${LIBPNG_ARCHIVE}"
|
||||||
|
WORKING_DIRECTORY "${THIRDPARTY_DIR}"
|
||||||
|
RESULT_VARIABLE _png_extract_res
|
||||||
|
)
|
||||||
|
if(NOT _png_extract_res EQUAL 0)
|
||||||
|
message(FATAL_ERROR "Failed to extract libpng archive")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
file(MAKE_DIRECTORY "${LIBPNG_BUILD_DIR}")
|
||||||
|
|
||||||
|
# Проверяем, есть ли уже .lib (build/Debug или install/lib)
|
||||||
|
set(_libpng_candidates
|
||||||
|
"${LIBPNG_BUILD_DIR}/Debug/libpng16_staticd.lib"
|
||||||
|
"${LIBPNG_BUILD_DIR}/Release/libpng16_static.lib"
|
||||||
|
)
|
||||||
|
|
||||||
|
set(_have_png FALSE)
|
||||||
|
foreach(candidate IN LISTS _libpng_candidates)
|
||||||
|
if(EXISTS "${candidate}")
|
||||||
|
set(_have_png TRUE)
|
||||||
|
break()
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
if(NOT _have_png)
|
||||||
|
log("Configuring libpng ...")
|
||||||
|
execute_process(
|
||||||
|
COMMAND ${CMAKE_COMMAND}
|
||||||
|
-G "${CMAKE_GENERATOR}"
|
||||||
|
-S "${LIBPNG_SRC_DIR}"
|
||||||
|
-B "${LIBPNG_BUILD_DIR}"
|
||||||
|
-DCMAKE_INSTALL_PREFIX=${LIBPNG_INSTALL_DIR}
|
||||||
|
-DCMAKE_PREFIX_PATH=${ZLIB_INSTALL_DIR}
|
||||||
|
-DZLIB_ROOT=${ZLIB_INSTALL_DIR}
|
||||||
|
RESULT_VARIABLE _png_cfg_res
|
||||||
|
)
|
||||||
|
if(NOT _png_cfg_res EQUAL 0)
|
||||||
|
message(FATAL_ERROR "libpng configure failed")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# --- ИЗМЕНЕНИЕ: Цикл по конфигурациям Debug и Release ---
|
||||||
|
foreach(cfg IN LISTS BUILD_CONFIGS)
|
||||||
|
log("Building libpng (${cfg}) ...")
|
||||||
|
execute_process(
|
||||||
|
COMMAND ${CMAKE_COMMAND}
|
||||||
|
--build "${LIBPNG_BUILD_DIR}" --config ${cfg}
|
||||||
|
RESULT_VARIABLE _png_build_res
|
||||||
|
)
|
||||||
|
if(NOT _png_build_res EQUAL 0)
|
||||||
|
message(FATAL_ERROR "libpng build failed for configuration ${cfg}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Поскольку вы не используете "cmake --install" для libpng,
|
||||||
|
# здесь нет необходимости в дополнительном шаге установки.
|
||||||
|
# Файлы .lib будут сгенерированы в подкаталоге ${LIBPNG_BUILD_DIR}/${cfg} (например, build/Debug или build/Release).
|
||||||
|
|
||||||
|
endforeach()
|
||||||
|
# ------------------------------------------------------
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_library(libpng_external_lib UNKNOWN IMPORTED GLOBAL)
|
||||||
|
set_target_properties(libpng_external_lib PROPERTIES
|
||||||
|
# Предполагая, что libpng использует статический вариант
|
||||||
|
IMPORTED_LOCATION_DEBUG "${LIBPNG_BUILD_DIR}/Debug/libpng16_staticd.lib"
|
||||||
|
IMPORTED_LOCATION_RELEASE "${LIBPNG_BUILD_DIR}/Release/libpng16_static.lib"
|
||||||
|
|
||||||
|
# png.h, pngconf.h – в SRC, pnglibconf.h – в BUILD
|
||||||
|
INTERFACE_INCLUDE_DIRECTORIES "${LIBPNG_SRC_DIR};${LIBPNG_BUILD_DIR}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# 4) libzip (v1.11.4.zip → libzip-1.11.4) - НОВАЯ ЗАВИСИМОСТЬ
|
||||||
|
# ===========================================
|
||||||
|
set(LIBZIP_ARCHIVE "${THIRDPARTY_DIR}/v1.11.4.zip")
|
||||||
|
set(LIBZIP_SRC_DIR "${THIRDPARTY_DIR}/libzip-1.11.4")
|
||||||
|
set(LIBZIP_BUILD_DIR "${LIBZIP_SRC_DIR}/build")
|
||||||
|
#set(LIBZIP_INSTALL_DIR "${LIBZIP_SRC_DIR}/install")
|
||||||
|
set(LIBZIP_BASE_DIR "${LIBZIP_SRC_DIR}/install")
|
||||||
|
|
||||||
|
if(NOT EXISTS "${LIBZIP_ARCHIVE}")
|
||||||
|
log("Downloading libzip v1.11.4.zip ...")
|
||||||
|
file(DOWNLOAD
|
||||||
|
"https://github.com/nih-at/libzip/archive/refs/tags/v1.11.4.zip"
|
||||||
|
"${LIBZIP_ARCHIVE}"
|
||||||
|
SHOW_PROGRESS
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT EXISTS "${LIBZIP_SRC_DIR}/CMakeLists.txt")
|
||||||
|
log("Extracting libzip v1.11.4.zip to libzip-1.11.4 ...")
|
||||||
|
execute_process(
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E tar xvf "${LIBZIP_ARCHIVE}"
|
||||||
|
WORKING_DIRECTORY "${THIRDPARTY_DIR}"
|
||||||
|
RESULT_VARIABLE _zip_extract_res
|
||||||
|
)
|
||||||
|
if(NOT _zip_extract_res EQUAL 0)
|
||||||
|
message(FATAL_ERROR "Failed to extract libzip archive")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
file(MAKE_DIRECTORY "${LIBZIP_BUILD_DIR}")
|
||||||
|
|
||||||
|
# Проверяем, собран ли уже libzip
|
||||||
|
set(_have_zip FALSE)
|
||||||
|
foreach(candidate
|
||||||
|
"${LIBZIP_BASE_DIR}-Debug/lib/zip.lib"
|
||||||
|
"${LIBZIP_BASE_DIR}-Release/lib/zip.lib"
|
||||||
|
)
|
||||||
|
if(EXISTS "${candidate}")
|
||||||
|
set(_have_zip TRUE)
|
||||||
|
break()
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
|
||||||
|
if(NOT _have_zip)
|
||||||
|
foreach(cfg IN LISTS BUILD_CONFIGS)
|
||||||
|
log("Configuring libzip (${cfg})...")
|
||||||
|
execute_process(
|
||||||
|
COMMAND ${CMAKE_COMMAND}
|
||||||
|
-G "${CMAKE_GENERATOR}"
|
||||||
|
-S "${LIBZIP_SRC_DIR}"
|
||||||
|
-B "${LIBZIP_SRC_DIR}/build-${cfg}"
|
||||||
|
-DCMAKE_INSTALL_PREFIX=${LIBZIP_BASE_DIR}-${cfg}
|
||||||
|
-DCMAKE_PREFIX_PATH=${ZLIB_INSTALL_DIR}
|
||||||
|
-DZLIB_ROOT=${ZLIB_INSTALL_DIR}
|
||||||
|
-DENABLE_COMMONCRYPTO=OFF
|
||||||
|
-DENABLE_GNUTLS=OFF
|
||||||
|
-DENABLE_MBEDTLS=OFF
|
||||||
|
-DENABLE_OPENSSL=OFF
|
||||||
|
-DENABLE_WINDOWS_CRYPTO=OFF
|
||||||
|
-DENABLE_FUZZ=OFF
|
||||||
|
RESULT_VARIABLE _zip_cfg_res
|
||||||
|
)
|
||||||
|
if(NOT _zip_cfg_res EQUAL 0)
|
||||||
|
message(FATAL_ERROR "libzip configure failed")
|
||||||
|
endif()
|
||||||
|
log("Building libzip (${cfg}) ...")
|
||||||
|
|
||||||
|
|
||||||
|
execute_process(
|
||||||
|
COMMAND ${CMAKE_COMMAND} --build "${LIBZIP_SRC_DIR}/build-${cfg}" --config ${cfg} -v
|
||||||
|
RESULT_VARIABLE _zip_build_res
|
||||||
|
)
|
||||||
|
if(NOT _zip_build_res EQUAL 0)
|
||||||
|
message(FATAL_ERROR "libzip build failed for configuration ${cfg}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
log("Installing libzip (${cfg}) ...")
|
||||||
|
|
||||||
|
execute_process(
|
||||||
|
COMMAND ${CMAKE_COMMAND} --install "${LIBZIP_SRC_DIR}/build-${cfg}" --config ${cfg} -v
|
||||||
|
RESULT_VARIABLE _zip_inst_res
|
||||||
|
)
|
||||||
|
if(NOT _zip_inst_res EQUAL 0)
|
||||||
|
message(FATAL_ERROR "libzip install failed for configuration ${cfg}")
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
|
||||||
|
add_library(libzip_external_lib UNKNOWN IMPORTED GLOBAL)
|
||||||
|
set_target_properties(libzip_external_lib PROPERTIES
|
||||||
|
IMPORTED_LOCATION_DEBUG "${LIBZIP_BASE_DIR}-Debug/lib/zip.lib" # ИСПРАВЛЕНО
|
||||||
|
IMPORTED_LOCATION_RELEASE "${LIBZIP_BASE_DIR}-Release/lib/zip.lib" # ИСПРАВЛЕНО
|
||||||
|
|
||||||
|
INTERFACE_INCLUDE_DIRECTORIES "$<IF:$<CONFIG:Debug>,${LIBZIP_BASE_DIR}-Debug/include,${LIBZIP_BASE_DIR}-Release/include>"
|
||||||
|
# libzip требует zlib для линковки
|
||||||
|
INTERFACE_LINK_LIBRARIES zlib_external_lib
|
||||||
|
)
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# Основной проект space-game001
|
||||||
|
# ===========================================
|
||||||
|
add_executable(space-game001
|
||||||
|
main.cpp
|
||||||
|
Game.cpp
|
||||||
|
Game.h
|
||||||
|
Environment.cpp
|
||||||
|
Environment.h
|
||||||
|
Renderer.cpp
|
||||||
|
Renderer.h
|
||||||
|
ShaderManager.cpp
|
||||||
|
ShaderManager.h
|
||||||
|
TextureManager.cpp
|
||||||
|
TextureManager.h
|
||||||
|
TextModel.cpp
|
||||||
|
TextModel.h
|
||||||
|
AudioPlayerAsync.cpp
|
||||||
|
AudioPlayerAsync.h
|
||||||
|
BoneAnimatedModel.cpp
|
||||||
|
BoneAnimatedModel.h
|
||||||
|
ZLMath.cpp
|
||||||
|
ZLMath.h
|
||||||
|
OpenGlExtensions.cpp
|
||||||
|
OpenGlExtensions.h
|
||||||
|
Utils.cpp
|
||||||
|
Utils.h
|
||||||
|
SparkEmitter.cpp
|
||||||
|
SparkEmitter.h
|
||||||
|
PlanetObject.cpp
|
||||||
|
PlanetObject.h
|
||||||
|
PlanetData.cpp
|
||||||
|
PlanetData.h
|
||||||
|
Perlin.cpp
|
||||||
|
Perlin.h
|
||||||
|
StoneObject.cpp
|
||||||
|
StoneObject.h
|
||||||
|
FrameBuffer.cpp
|
||||||
|
FrameBuffer.h
|
||||||
|
)
|
||||||
|
|
||||||
|
# Установка проекта по умолчанию для Visual Studio
|
||||||
|
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT space-game001)
|
||||||
|
|
||||||
|
# include-пути проекта
|
||||||
|
target_include_directories(space-game001 PRIVATE
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}"
|
||||||
|
#"${CMAKE_CURRENT_SOURCE_DIR}/gl"
|
||||||
|
#"${CMAKE_CURRENT_SOURCE_DIR}/cmakeaudioplayer/include"
|
||||||
|
#"${SDL2_INSTALL_DIR}/include"
|
||||||
|
#"${SDL2_INSTALL_DIR}/include/SDL2"
|
||||||
|
#"${LIBZIP_INSTALL_DIR}-Release/include" # Добавил include-путь для libzip
|
||||||
|
)
|
||||||
|
|
||||||
|
set_target_properties(space-game001 PROPERTIES
|
||||||
|
OUTPUT_NAME "space-game001"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Определения препроцессора:
|
||||||
|
# PNG_ENABLED – включает код PNG в TextureManager
|
||||||
|
# SDL_MAIN_HANDLED – отключает переопределение main -> SDL_main
|
||||||
|
target_compile_definitions(space-game001 PRIVATE
|
||||||
|
PNG_ENABLED
|
||||||
|
SDL_MAIN_HANDLED
|
||||||
|
)
|
||||||
|
|
||||||
|
# Линкуем с SDL2main, если он вообще установлен
|
||||||
|
target_link_libraries(space-game001 PRIVATE SDL2main_external_lib)
|
||||||
|
|
||||||
|
# Линкуем сторонние библиотеки
|
||||||
|
target_link_libraries(space-game001 PRIVATE
|
||||||
|
SDL2_external_lib
|
||||||
|
libpng_external_lib
|
||||||
|
zlib_external_lib
|
||||||
|
libzip_external_lib
|
||||||
|
)
|
||||||
|
|
||||||
|
# Линкуем OpenGL (Windows)
|
||||||
|
if(WIN32)
|
||||||
|
target_link_libraries(space-game001 PRIVATE opengl32)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# Копирование SDL2d.dll и zlibd.dll рядом с exe
|
||||||
|
# ===========================================
|
||||||
|
if (WIN32)
|
||||||
|
|
||||||
|
# SDL2: в Debug - SDL2d.dll, в Release - SDL2.dll
|
||||||
|
set(SDL2_DLL_SRC "$<IF:$<CONFIG:Debug>,${SDL2_INSTALL_DIR}/bin/SDL2d.dll,${SDL2_INSTALL_DIR}/bin/SDL2.dll>")
|
||||||
|
set(SDL2_DLL_DST "$<IF:$<CONFIG:Debug>,$<TARGET_FILE_DIR:space-game001>/SDL2d.dll,$<TARGET_FILE_DIR:space-game001>/SDL2.dll>")
|
||||||
|
|
||||||
|
|
||||||
|
set(LIBZIP_DLL_SRC "$<IF:$<CONFIG:Debug>,${LIBZIP_BASE_DIR}-Debug/bin/zip.dll,${LIBZIP_BASE_DIR}-Release/bin/zip.dll>")
|
||||||
|
|
||||||
|
add_custom_command(TARGET space-game001 POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E echo "Copying DLLs to output folder..."
|
||||||
|
|
||||||
|
# Копируем SDL2 (целевое имя всегда SDL2.dll)
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
"${SDL2_DLL_SRC}"
|
||||||
|
"${SDL2_DLL_DST}"
|
||||||
|
|
||||||
|
# Копируем LIBZIP (целевое имя всегда zip.dll)
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
"${LIBZIP_DLL_SRC}"
|
||||||
|
"$<TARGET_FILE_DIR:space-game001>/zip.dll"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# Копирование ресурсов после сборки
|
||||||
|
# ===========================================
|
||||||
|
|
||||||
|
# Какие папки с ресурсами нужно копировать
|
||||||
|
set(RUNTIME_RESOURCE_DIRS
|
||||||
|
"resources"
|
||||||
|
"shaders"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Копируем ресурсы и шейдеры в папку exe и в корень build/
|
||||||
|
foreach(resdir IN LISTS RUNTIME_RESOURCE_DIRS)
|
||||||
|
add_custom_command(TARGET space-game001 POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E echo "Copying ${resdir} to runtime folders..."
|
||||||
|
# 1) туда, где лежит exe (build/Debug, build/Release и т.п.)
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||||
|
"${CMAKE_SOURCE_DIR}/${resdir}"
|
||||||
|
"$<TARGET_FILE_DIR:space-game001>/${resdir}"
|
||||||
|
# 2) в корень build, если захочешь запускать из этой папки
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||||
|
"${CMAKE_SOURCE_DIR}/${resdir}"
|
||||||
|
"${CMAKE_BINARY_DIR}/${resdir}"
|
||||||
|
)
|
||||||
|
endforeach()
|
||||||
43
Environment.cpp
Normal file
43
Environment.cpp
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#include "Environment.h"
|
||||||
|
|
||||||
|
#include "Utils.h"
|
||||||
|
#include <GL/gl.h>
|
||||||
|
|
||||||
|
namespace ZL {
|
||||||
|
|
||||||
|
int Environment::windowHeaderHeight = 0;
|
||||||
|
int Environment::width = 0;
|
||||||
|
int Environment::height = 0;
|
||||||
|
float Environment::zoom = 18.f;
|
||||||
|
|
||||||
|
bool Environment::leftPressed = false;
|
||||||
|
bool Environment::rightPressed = false;
|
||||||
|
bool Environment::upPressed = false;
|
||||||
|
bool Environment::downPressed = false;
|
||||||
|
|
||||||
|
bool Environment::settings_inverseVertical = false;
|
||||||
|
|
||||||
|
SDL_Window* Environment::window = nullptr;
|
||||||
|
|
||||||
|
bool Environment::showMouse = false;
|
||||||
|
|
||||||
|
bool Environment::exitGameLoop = false;
|
||||||
|
|
||||||
|
Matrix3f Environment::shipMatrix = Matrix3f::Identity();
|
||||||
|
Matrix3f Environment::inverseShipMatrix = Matrix3f::Identity();
|
||||||
|
|
||||||
|
|
||||||
|
bool Environment::tapDownHold = false;
|
||||||
|
Vector2f Environment::tapDownStartPos = { 0, 0 };
|
||||||
|
Vector2f Environment::tapDownCurrentPos = { 0, 0 };
|
||||||
|
|
||||||
|
Vector3f Environment::shipPosition = {0,0,45000.f};
|
||||||
|
|
||||||
|
float Environment::shipVelocity = 0.f;
|
||||||
|
|
||||||
|
|
||||||
|
const float Environment::CONST_Z_NEAR = 5.f;
|
||||||
|
const float Environment::CONST_Z_FAR = 5000.f;
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace ZL
|
||||||
47
Environment.h
Normal file
47
Environment.h
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "ZLMath.h"
|
||||||
|
#ifdef __linux__
|
||||||
|
#include <SDL2/SDL.h>
|
||||||
|
#endif
|
||||||
|
#include "OpenGlExtensions.h"
|
||||||
|
|
||||||
|
namespace ZL {
|
||||||
|
|
||||||
|
class Environment {
|
||||||
|
public:
|
||||||
|
static int windowHeaderHeight;
|
||||||
|
static int width;
|
||||||
|
static int height;
|
||||||
|
static float zoom;
|
||||||
|
|
||||||
|
static bool leftPressed;
|
||||||
|
static bool rightPressed;
|
||||||
|
static bool upPressed;
|
||||||
|
static bool downPressed;
|
||||||
|
|
||||||
|
static bool settings_inverseVertical;
|
||||||
|
|
||||||
|
static Matrix3f shipMatrix;
|
||||||
|
static Matrix3f inverseShipMatrix;
|
||||||
|
|
||||||
|
static SDL_Window* window;
|
||||||
|
|
||||||
|
static bool showMouse;
|
||||||
|
static bool exitGameLoop;
|
||||||
|
|
||||||
|
|
||||||
|
static bool tapDownHold;
|
||||||
|
static Vector2f tapDownStartPos;
|
||||||
|
static Vector2f tapDownCurrentPos;
|
||||||
|
|
||||||
|
static Vector3f shipPosition;
|
||||||
|
static float shipVelocity;
|
||||||
|
|
||||||
|
static const float CONST_Z_NEAR;
|
||||||
|
static const float CONST_Z_FAR;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ZL
|
||||||
49
FrameBuffer.cpp
Normal file
49
FrameBuffer.cpp
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
#include "FrameBuffer.h"
|
||||||
|
#include <iostream>
|
||||||
|
#include "Environment.h"
|
||||||
|
|
||||||
|
namespace ZL {
|
||||||
|
|
||||||
|
FrameBuffer::FrameBuffer(int w, int h) : width(w), height(h) {
|
||||||
|
// 1. Ñîçäàåì FBO
|
||||||
|
glGenFramebuffers(1, &fbo);
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||||
|
|
||||||
|
// 2. Ñîçäàåì òåêñòóðó, êóäà áóäåì "ôîòîãðàôèðîâàòü"
|
||||||
|
glGenTextures(1, &textureID);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, textureID);
|
||||||
|
|
||||||
|
// Óñòàíàâëèâàåì ïàðàìåòðû òåêñòóðû
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
|
|
||||||
|
// 3. Ïðèâÿçûâàåì òåêñòóðó ê FBO
|
||||||
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureID, 0);
|
||||||
|
|
||||||
|
// Ïðîâåðêà ãîòîâíîñòè
|
||||||
|
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
|
||||||
|
std::cerr << "Error: Framebuffer is not complete!" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
FrameBuffer::~FrameBuffer() {
|
||||||
|
glDeleteFramebuffers(1, &fbo);
|
||||||
|
glDeleteTextures(1, &textureID);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrameBuffer::Bind() {
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||||
|
glViewport(0, 0, width, height); // Âàæíî: óñòàíàâëèâàåì âüþïîðò ïîä ðàçìåð òåêñòóðû
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrameBuffer::Unbind() {
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
|
// Çäåñü æåëàòåëüíî âîçâðàùàòü âüþïîðò ê ðàçìåðàì ýêðàíà,
|
||||||
|
// íàïðèìåð, ÷åðåç Environment::width/height
|
||||||
|
glViewport(0, 0, Environment::width, Environment::height);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ZL
|
||||||
29
FrameBuffer.h
Normal file
29
FrameBuffer.h
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "OpenGlExtensions.h"
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace ZL {
|
||||||
|
|
||||||
|
class FrameBuffer {
|
||||||
|
private:
|
||||||
|
GLuint fbo = 0;
|
||||||
|
GLuint textureID = 0;
|
||||||
|
int width, height;
|
||||||
|
|
||||||
|
public:
|
||||||
|
FrameBuffer(int w, int h);
|
||||||
|
~FrameBuffer();
|
||||||
|
|
||||||
|
// Çàïðåùàåì êîïèðîâàíèå, êàê â VBOHolder
|
||||||
|
FrameBuffer(const FrameBuffer&) = delete;
|
||||||
|
FrameBuffer& operator=(const FrameBuffer&) = delete;
|
||||||
|
|
||||||
|
void Bind(); // Íà÷àòü ðåíäåð â ýòîò áóôåð
|
||||||
|
void Unbind(); // Âåðíóòüñÿ ê îáû÷íîìó ðåíäåðó â ýêðàí
|
||||||
|
|
||||||
|
GLuint getTextureID() const { return textureID; }
|
||||||
|
int getWidth() const { return width; }
|
||||||
|
int getHeight() const { return height; }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ZL
|
||||||
655
Game.cpp
Executable file
655
Game.cpp
Executable file
@ -0,0 +1,655 @@
|
|||||||
|
#include "Game.h"
|
||||||
|
#include "AnimatedModel.h"
|
||||||
|
#include "BoneAnimatedModel.h"
|
||||||
|
#include "Utils.h"
|
||||||
|
#include "OpenGlExtensions.h"
|
||||||
|
#include <iostream>
|
||||||
|
#include "TextureManager.h"
|
||||||
|
#include "TextModel.h"
|
||||||
|
#include "StoneObject.h"
|
||||||
|
#include <random>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
namespace ZL
|
||||||
|
{
|
||||||
|
#ifdef EMSCRIPTEN
|
||||||
|
const char* CONST_ZIP_FILE = "space-game001.zip";
|
||||||
|
#else
|
||||||
|
const char* CONST_ZIP_FILE = "";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Vector4f generateRandomQuaternion(std::mt19937& gen)
|
||||||
|
{
|
||||||
|
|
||||||
|
std::normal_distribution<> distrib(0.0, 1.0);
|
||||||
|
|
||||||
|
Vector4f randomQuat = {
|
||||||
|
(float)distrib(gen),
|
||||||
|
(float)distrib(gen),
|
||||||
|
(float)distrib(gen),
|
||||||
|
(float)distrib(gen)
|
||||||
|
};
|
||||||
|
|
||||||
|
return randomQuat.normalized();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<BoxCoords> generateRandomBoxCoords(int N)
|
||||||
|
{
|
||||||
|
const float MIN_DISTANCE = 3.0f;
|
||||||
|
const float MIN_DISTANCE_SQUARED = MIN_DISTANCE * MIN_DISTANCE;
|
||||||
|
const float MIN_COORD = -100.0f;
|
||||||
|
const float MAX_COORD = 100.0f;
|
||||||
|
const int MAX_ATTEMPTS = 1000;
|
||||||
|
std::vector<BoxCoords> boxCoordsArr;
|
||||||
|
|
||||||
|
std::random_device rd;
|
||||||
|
std::mt19937 gen(rd());
|
||||||
|
|
||||||
|
std::uniform_real_distribution<> distrib(MIN_COORD, MAX_COORD);
|
||||||
|
|
||||||
|
int generatedCount = 0;
|
||||||
|
|
||||||
|
while (generatedCount < N)
|
||||||
|
{
|
||||||
|
bool accepted = false;
|
||||||
|
int attempts = 0;
|
||||||
|
|
||||||
|
while (!accepted && attempts < MAX_ATTEMPTS)
|
||||||
|
{
|
||||||
|
Vector3f newPos(
|
||||||
|
(float)distrib(gen),
|
||||||
|
(float)distrib(gen),
|
||||||
|
(float)distrib(gen)
|
||||||
|
);
|
||||||
|
|
||||||
|
accepted = true;
|
||||||
|
for (const auto& existingBox : boxCoordsArr)
|
||||||
|
{
|
||||||
|
|
||||||
|
Vector3f diff = newPos - existingBox.pos;
|
||||||
|
|
||||||
|
float distanceSquared = diff.squaredNorm();
|
||||||
|
|
||||||
|
if (distanceSquared < MIN_DISTANCE_SQUARED)
|
||||||
|
{
|
||||||
|
accepted = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accepted)
|
||||||
|
{
|
||||||
|
Vector4f randomQuat = generateRandomQuaternion(gen);
|
||||||
|
|
||||||
|
Matrix3f randomMatrix = QuatToMatrix(randomQuat);
|
||||||
|
|
||||||
|
boxCoordsArr.emplace_back(BoxCoords{ newPos, randomMatrix });
|
||||||
|
generatedCount++;
|
||||||
|
}
|
||||||
|
attempts++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!accepted) {
|
||||||
|
std::cerr << "Ïðåäóïðåæäåíèå: Íå óäàëîñü ñãåíåðèðîâàòü " << N << " îáúåêòîâ. Ñãåíåðèðîâàíî: " << generatedCount << std::endl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return boxCoordsArr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Game::Game()
|
||||||
|
: window(nullptr)
|
||||||
|
, glContext(nullptr)
|
||||||
|
, newTickCount(0)
|
||||||
|
, lastTickCount(0)
|
||||||
|
{
|
||||||
|
std::vector<Vector3f> emissionPoints = {
|
||||||
|
Vector3f{-2.1f, 0.9f, 5.0f},
|
||||||
|
Vector3f{2.1f, 0.9f, 5.0f}
|
||||||
|
};
|
||||||
|
sparkEmitter = SparkEmitter(emissionPoints, 100.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
Game::~Game() {
|
||||||
|
if (glContext) {
|
||||||
|
SDL_GL_DeleteContext(glContext);
|
||||||
|
}
|
||||||
|
if (window) {
|
||||||
|
SDL_DestroyWindow(window);
|
||||||
|
}
|
||||||
|
SDL_Quit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Game::setup() {
|
||||||
|
glContext = SDL_GL_CreateContext(ZL::Environment::window);
|
||||||
|
|
||||||
|
ZL::BindOpenGlFunctions();
|
||||||
|
ZL::CheckGlError();
|
||||||
|
|
||||||
|
// Initialize renderer
|
||||||
|
|
||||||
|
#ifdef EMSCRIPTEN
|
||||||
|
renderer.shaderManager.AddShaderFromFiles("default", "./shaders/default.vertex", "./shaders/default_web.fragment", CONST_ZIP_FILE);
|
||||||
|
renderer.shaderManager.AddShaderFromFiles("defaultColor", "./shaders/defaultColor.vertex", "./shaders/defaultColor_web.fragment", CONST_ZIP_FILE);
|
||||||
|
renderer.shaderManager.AddShaderFromFiles("env", "./shaders/env.vertex", "./shaders/env_web.fragment", CONST_ZIP_FILE);
|
||||||
|
renderer.shaderManager.AddShaderFromFiles("defaultAtmosphere", "./shaders/defaultAtmosphere.vertex", "./shaders/defaultAtmosphere.fragment", CONST_ZIP_FILE);
|
||||||
|
renderer.shaderManager.AddShaderFromFiles("defaultColorPlanet", "./shaders/defaultColorPlanet.vertex", "./shaders/defaultColorPlanet_web.fragment", CONST_ZIP_FILE);
|
||||||
|
|
||||||
|
#else
|
||||||
|
renderer.shaderManager.AddShaderFromFiles("default", "./shaders/default.vertex", "./shaders/default_desktop.fragment", CONST_ZIP_FILE);
|
||||||
|
renderer.shaderManager.AddShaderFromFiles("defaultColor", "./shaders/defaultColor_fog.vertex", "./shaders/defaultColor_fog_desktop.fragment", CONST_ZIP_FILE);
|
||||||
|
renderer.shaderManager.AddShaderFromFiles("env", "./shaders/env_sky.vertex", "./shaders/env_sky_desktop.fragment", CONST_ZIP_FILE);
|
||||||
|
renderer.shaderManager.AddShaderFromFiles("defaultAtmosphere", "./shaders/defaultAtmosphere.vertex", "./shaders/defaultAtmosphere.fragment", CONST_ZIP_FILE);
|
||||||
|
renderer.shaderManager.AddShaderFromFiles("defaultColor2", "./shaders/defaultColor_fog2.vertex", "./shaders/defaultColor_fog2_desktop.fragment", CONST_ZIP_FILE);
|
||||||
|
renderer.shaderManager.AddShaderFromFiles("defaultColorStones", "./shaders/defaultColor_fog_stones.vertex", "./shaders/defaultColor_fog_stones_desktop.fragment", CONST_ZIP_FILE);
|
||||||
|
renderer.shaderManager.AddShaderFromFiles("planetBake", "./shaders/planet_bake.vertex", "./shaders/planet_bake_desktop.fragment", CONST_ZIP_FILE);
|
||||||
|
renderer.shaderManager.AddShaderFromFiles("planetStone", "./shaders/planet_stone.vertex", "./shaders/planet_stone_desktop.fragment", CONST_ZIP_FILE);
|
||||||
|
renderer.shaderManager.AddShaderFromFiles("planetLand", "./shaders/planet_land.vertex", "./shaders/planet_land_desktop.fragment", CONST_ZIP_FILE);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
cubemapTexture = std::make_shared<Texture>(
|
||||||
|
std::array<TextureDataStruct, 6>{
|
||||||
|
CreateTextureDataFromBmp24("./resources/sky/space_rt.bmp", CONST_ZIP_FILE),
|
||||||
|
CreateTextureDataFromBmp24("./resources/sky/space_lf.bmp", CONST_ZIP_FILE),
|
||||||
|
CreateTextureDataFromBmp24("./resources/sky/space_up.bmp", CONST_ZIP_FILE),
|
||||||
|
CreateTextureDataFromBmp24("./resources/sky/space_dn.bmp", CONST_ZIP_FILE),
|
||||||
|
CreateTextureDataFromBmp24("./resources/sky/space_bk.bmp", CONST_ZIP_FILE),
|
||||||
|
CreateTextureDataFromBmp24("./resources/sky/space_ft.bmp", CONST_ZIP_FILE)
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
cubemap.data = ZL::CreateCubemap(500);
|
||||||
|
cubemap.RefreshVBO();
|
||||||
|
|
||||||
|
//Load texture
|
||||||
|
spaceshipTexture = std::make_unique<Texture>(CreateTextureDataFromPng("./resources/DefaultMaterial_BaseColor_shine.png", CONST_ZIP_FILE));
|
||||||
|
spaceshipBase = LoadFromTextFile02("./resources/spaceship006.txt", CONST_ZIP_FILE);
|
||||||
|
spaceshipBase.RotateByMatrix(QuatToMatrix(QuatFromRotateAroundY(M_PI / 2.0)));
|
||||||
|
//spaceshipBase.Move(Vector3f{ -0.52998, -13, 0 });
|
||||||
|
//spaceshipBase.Move(Vector3f{ -0.52998, -10, 10 });
|
||||||
|
|
||||||
|
spaceship.AssignFrom(spaceshipBase);
|
||||||
|
spaceship.RefreshVBO();
|
||||||
|
|
||||||
|
|
||||||
|
//Boxes
|
||||||
|
boxTexture = std::make_unique<Texture>(CreateTextureDataFromPng("./resources/box/box.png", CONST_ZIP_FILE));
|
||||||
|
boxBase = LoadFromTextFile02("./resources/box/box.txt", CONST_ZIP_FILE);
|
||||||
|
|
||||||
|
boxCoordsArr = generateRandomBoxCoords(50);
|
||||||
|
|
||||||
|
boxRenderArr.resize(boxCoordsArr.size());
|
||||||
|
|
||||||
|
for (int i = 0; i < boxCoordsArr.size(); i++)
|
||||||
|
{
|
||||||
|
boxRenderArr[i].AssignFrom(boxBase);
|
||||||
|
//boxRenderArr[i].data = CreateBaseConvexPolyhedron(1999);
|
||||||
|
boxRenderArr[i].RefreshVBO();
|
||||||
|
}
|
||||||
|
|
||||||
|
sparkTexture = std::make_unique<Texture>(CreateTextureDataFromPng("./resources/spark.png", CONST_ZIP_FILE));
|
||||||
|
|
||||||
|
sparkEmitter.setTexture(sparkTexture);
|
||||||
|
|
||||||
|
|
||||||
|
buttonTexture = std::make_unique<Texture>(CreateTextureDataFromPng("./resources/button.png", CONST_ZIP_FILE));
|
||||||
|
|
||||||
|
button.data.PositionData.push_back({ 100, 100, 0 });
|
||||||
|
button.data.PositionData.push_back({ 100, 150, 0 });
|
||||||
|
button.data.PositionData.push_back({ 300, 150, 0 });
|
||||||
|
button.data.PositionData.push_back({ 100, 100, 0 });
|
||||||
|
button.data.PositionData.push_back({ 300, 150, 0 });
|
||||||
|
button.data.PositionData.push_back({ 300, 100, 0 });
|
||||||
|
|
||||||
|
button.data.TexCoordData.push_back({ 0,0 });
|
||||||
|
button.data.TexCoordData.push_back({ 0,1 });
|
||||||
|
button.data.TexCoordData.push_back({ 1,1 });
|
||||||
|
button.data.TexCoordData.push_back({ 0,0 });
|
||||||
|
button.data.TexCoordData.push_back({ 1,1 });
|
||||||
|
button.data.TexCoordData.push_back({ 1,0 });
|
||||||
|
|
||||||
|
button.RefreshVBO();
|
||||||
|
|
||||||
|
musicVolumeBarTexture = std::make_unique<Texture>(CreateTextureDataFromPng("./resources/musicVolumeBarTexture.png", CONST_ZIP_FILE));
|
||||||
|
|
||||||
|
musicVolumeBar.data.PositionData.push_back({ 1190, 100, 0 });
|
||||||
|
musicVolumeBar.data.PositionData.push_back({ 1190, 600, 0 });
|
||||||
|
musicVolumeBar.data.PositionData.push_back({ 1200, 600, 0 });
|
||||||
|
musicVolumeBar.data.PositionData.push_back({ 1190, 100, 0 });
|
||||||
|
musicVolumeBar.data.PositionData.push_back({ 1200, 600, 0 });
|
||||||
|
musicVolumeBar.data.PositionData.push_back({ 1200, 100, 0 });
|
||||||
|
|
||||||
|
musicVolumeBar.data.TexCoordData.push_back({ 0,0 });
|
||||||
|
musicVolumeBar.data.TexCoordData.push_back({ 0,1 });
|
||||||
|
musicVolumeBar.data.TexCoordData.push_back({ 1,1 });
|
||||||
|
musicVolumeBar.data.TexCoordData.push_back({ 0,0 });
|
||||||
|
musicVolumeBar.data.TexCoordData.push_back({ 1,1 });
|
||||||
|
musicVolumeBar.data.TexCoordData.push_back({ 1,0 });
|
||||||
|
|
||||||
|
musicVolumeBar.RefreshVBO();
|
||||||
|
|
||||||
|
|
||||||
|
musicVolumeBarButtonTexture = std::make_unique<Texture>(CreateTextureDataFromPng("./resources/musicVolumeBarButton.png", CONST_ZIP_FILE));
|
||||||
|
|
||||||
|
float musicVolumeBarButtonButtonCenterY = 350.0f;
|
||||||
|
|
||||||
|
musicVolumeBarButton.data.PositionData.push_back({ musicVolumeBarButtonButtonCenterX - musicVolumeBarButtonButtonRadius, musicVolumeBarButtonButtonCenterY - musicVolumeBarButtonButtonRadius, 0 });
|
||||||
|
musicVolumeBarButton.data.PositionData.push_back({ musicVolumeBarButtonButtonCenterX - musicVolumeBarButtonButtonRadius, musicVolumeBarButtonButtonCenterY + musicVolumeBarButtonButtonRadius, 0 });
|
||||||
|
musicVolumeBarButton.data.PositionData.push_back({ musicVolumeBarButtonButtonCenterX + musicVolumeBarButtonButtonRadius, musicVolumeBarButtonButtonCenterY + musicVolumeBarButtonButtonRadius, 0 });
|
||||||
|
musicVolumeBarButton.data.PositionData.push_back({ musicVolumeBarButtonButtonCenterX - musicVolumeBarButtonButtonRadius, musicVolumeBarButtonButtonCenterY - musicVolumeBarButtonButtonRadius, 0 });
|
||||||
|
musicVolumeBarButton.data.PositionData.push_back({ musicVolumeBarButtonButtonCenterX + musicVolumeBarButtonButtonRadius, musicVolumeBarButtonButtonCenterY + musicVolumeBarButtonButtonRadius, 0 });
|
||||||
|
musicVolumeBarButton.data.PositionData.push_back({ musicVolumeBarButtonButtonCenterX + musicVolumeBarButtonButtonRadius, musicVolumeBarButtonButtonCenterY - musicVolumeBarButtonButtonRadius, 0 });
|
||||||
|
|
||||||
|
musicVolumeBarButton.data.TexCoordData.push_back({ 0,0 });
|
||||||
|
musicVolumeBarButton.data.TexCoordData.push_back({ 0,1 });
|
||||||
|
musicVolumeBarButton.data.TexCoordData.push_back({ 1,1 });
|
||||||
|
musicVolumeBarButton.data.TexCoordData.push_back({ 0,0 });
|
||||||
|
musicVolumeBarButton.data.TexCoordData.push_back({ 1,1 });
|
||||||
|
musicVolumeBarButton.data.TexCoordData.push_back({ 1,0 });
|
||||||
|
|
||||||
|
musicVolumeBarButton.RefreshVBO();
|
||||||
|
renderer.InitOpenGL();
|
||||||
|
glEnable(GL_BLEND);
|
||||||
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
|
|
||||||
|
planetObject.init();
|
||||||
|
|
||||||
|
rockTexture = std::make_unique<Texture>(CreateTextureDataFromPng("./resources/rock.png", ""));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Game::drawCubemap(float skyPercent)
|
||||||
|
{
|
||||||
|
static const std::string defaultShaderName = "default";
|
||||||
|
static const std::string envShaderName = "env";
|
||||||
|
static const std::string vPositionName = "vPosition";
|
||||||
|
static const std::string vTexCoordName = "vTexCoord";
|
||||||
|
static const std::string textureUniformName = "Texture";
|
||||||
|
static const std::string skyPercentUniformName = "skyPercent";
|
||||||
|
|
||||||
|
renderer.shaderManager.PushShader(envShaderName);
|
||||||
|
renderer.RenderUniform1i(textureUniformName, 0);
|
||||||
|
renderer.RenderUniform1f(skyPercentUniformName, skyPercent);
|
||||||
|
renderer.EnableVertexAttribArray(vPositionName);
|
||||||
|
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
|
||||||
|
static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
|
||||||
|
Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
|
||||||
|
renderer.PushMatrix();
|
||||||
|
renderer.LoadIdentity();
|
||||||
|
renderer.RotateMatrix(Environment::inverseShipMatrix);
|
||||||
|
|
||||||
|
CheckGlError();
|
||||||
|
|
||||||
|
glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture->getTexID());
|
||||||
|
renderer.DrawVertexRenderStruct(cubemap);
|
||||||
|
|
||||||
|
CheckGlError();
|
||||||
|
|
||||||
|
|
||||||
|
renderer.PopMatrix();
|
||||||
|
renderer.PopProjectionMatrix();
|
||||||
|
renderer.DisableVertexAttribArray(vPositionName);
|
||||||
|
|
||||||
|
renderer.shaderManager.PopShader();
|
||||||
|
CheckGlError();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Game::drawShip()
|
||||||
|
{
|
||||||
|
static const std::string defaultShaderName = "default";
|
||||||
|
static const std::string envShaderName = "env";
|
||||||
|
static const std::string vPositionName = "vPosition";
|
||||||
|
static const std::string vTexCoordName = "vTexCoord";
|
||||||
|
static const std::string textureUniformName = "Texture";
|
||||||
|
|
||||||
|
renderer.shaderManager.PushShader(defaultShaderName);
|
||||||
|
renderer.RenderUniform1i(textureUniformName, 0);
|
||||||
|
renderer.EnableVertexAttribArray(vPositionName);
|
||||||
|
renderer.EnableVertexAttribArray(vTexCoordName);
|
||||||
|
|
||||||
|
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
|
||||||
|
static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
|
||||||
|
Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
|
||||||
|
renderer.PushMatrix();
|
||||||
|
|
||||||
|
renderer.LoadIdentity();
|
||||||
|
renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
|
||||||
|
|
||||||
|
|
||||||
|
glBindTexture(GL_TEXTURE_2D, spaceshipTexture->getTexID());
|
||||||
|
renderer.DrawVertexRenderStruct(spaceship);
|
||||||
|
sparkEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height);
|
||||||
|
|
||||||
|
renderer.PopMatrix();
|
||||||
|
renderer.PopProjectionMatrix();
|
||||||
|
renderer.DisableVertexAttribArray(vPositionName);
|
||||||
|
renderer.DisableVertexAttribArray(vTexCoordName);
|
||||||
|
|
||||||
|
renderer.shaderManager.PopShader();
|
||||||
|
CheckGlError();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Game::drawBoxes()
|
||||||
|
{
|
||||||
|
static const std::string defaultShaderName = "default";
|
||||||
|
static const std::string envShaderName = "env";
|
||||||
|
static const std::string vPositionName = "vPosition";
|
||||||
|
static const std::string vTexCoordName = "vTexCoord";
|
||||||
|
static const std::string textureUniformName = "Texture";
|
||||||
|
|
||||||
|
renderer.shaderManager.PushShader(defaultShaderName);
|
||||||
|
renderer.RenderUniform1i(textureUniformName, 0);
|
||||||
|
renderer.EnableVertexAttribArray(vPositionName);
|
||||||
|
renderer.EnableVertexAttribArray(vTexCoordName);
|
||||||
|
|
||||||
|
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
|
||||||
|
static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
|
||||||
|
Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
|
||||||
|
|
||||||
|
for (int i = 0; i < boxCoordsArr.size(); i++)
|
||||||
|
{
|
||||||
|
renderer.PushMatrix();
|
||||||
|
|
||||||
|
renderer.LoadIdentity();
|
||||||
|
renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
|
||||||
|
renderer.RotateMatrix(Environment::inverseShipMatrix);
|
||||||
|
renderer.TranslateMatrix(-Environment::shipPosition);
|
||||||
|
renderer.TranslateMatrix({ 0.f, 0.f, 45000.f });
|
||||||
|
renderer.TranslateMatrix(boxCoordsArr[i].pos);
|
||||||
|
renderer.RotateMatrix(boxCoordsArr[i].m);
|
||||||
|
|
||||||
|
glBindTexture(GL_TEXTURE_2D, boxTexture->getTexID());
|
||||||
|
//glBindTexture(GL_TEXTURE_2D, rockTexture->getTexID());
|
||||||
|
renderer.DrawVertexRenderStruct(boxRenderArr[i]);
|
||||||
|
|
||||||
|
renderer.PopMatrix();
|
||||||
|
}
|
||||||
|
renderer.PopProjectionMatrix();
|
||||||
|
renderer.DisableVertexAttribArray(vPositionName);
|
||||||
|
renderer.DisableVertexAttribArray(vTexCoordName);
|
||||||
|
|
||||||
|
renderer.shaderManager.PopShader();
|
||||||
|
CheckGlError();
|
||||||
|
}
|
||||||
|
void Game::UpdateVolumeKnob() {
|
||||||
|
float musicVolumeBarButtonButtonCenterY = volumeBarMinY + musicVolume * (volumeBarMaxY - volumeBarMinY);
|
||||||
|
|
||||||
|
auto& pos = musicVolumeBarButton.data.PositionData;
|
||||||
|
|
||||||
|
pos[0] = { musicVolumeBarButtonButtonCenterX - musicVolumeBarButtonButtonRadius, musicVolumeBarButtonButtonCenterY - musicVolumeBarButtonButtonRadius, 0 };
|
||||||
|
pos[1] = { musicVolumeBarButtonButtonCenterX - musicVolumeBarButtonButtonRadius, musicVolumeBarButtonButtonCenterY + musicVolumeBarButtonButtonRadius, 0 };
|
||||||
|
pos[2] = { musicVolumeBarButtonButtonCenterX + musicVolumeBarButtonButtonRadius, musicVolumeBarButtonButtonCenterY + musicVolumeBarButtonButtonRadius, 0 };
|
||||||
|
pos[3] = { musicVolumeBarButtonButtonCenterX - musicVolumeBarButtonButtonRadius, musicVolumeBarButtonButtonCenterY - musicVolumeBarButtonButtonRadius, 0 };
|
||||||
|
pos[4] = { musicVolumeBarButtonButtonCenterX + musicVolumeBarButtonButtonRadius, musicVolumeBarButtonButtonCenterY + musicVolumeBarButtonButtonRadius, 0 };
|
||||||
|
pos[5] = { musicVolumeBarButtonButtonCenterX + musicVolumeBarButtonButtonRadius, musicVolumeBarButtonButtonCenterY - musicVolumeBarButtonButtonRadius, 0 };
|
||||||
|
|
||||||
|
musicVolumeBarButton.RefreshVBO();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Game::UpdateVolumeFromMouse(int mouseX, int mouseY) {
|
||||||
|
|
||||||
|
int uiX = mouseX;
|
||||||
|
int uiY = Environment::height - mouseY;
|
||||||
|
Environment::shipVelocity = (musicVolume) * (20.0);
|
||||||
|
if (uiY < volumeBarMinY || uiY > volumeBarMaxY)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float t = (uiY - volumeBarMinY) / (volumeBarMaxY - volumeBarMinY);
|
||||||
|
if (t < 0.0f) t = 0.0f;
|
||||||
|
if (t > 1.0f) t = 1.0f;
|
||||||
|
musicVolume = t;
|
||||||
|
UpdateVolumeKnob();
|
||||||
|
}
|
||||||
|
void Game::drawUI()
|
||||||
|
{
|
||||||
|
static const std::string defaultShaderName = "default";
|
||||||
|
static const std::string envShaderName = "env";
|
||||||
|
static const std::string vPositionName = "vPosition";
|
||||||
|
static const std::string vTexCoordName = "vTexCoord";
|
||||||
|
static const std::string textureUniformName = "Texture";
|
||||||
|
|
||||||
|
|
||||||
|
glClear(GL_DEPTH_BUFFER_BIT);
|
||||||
|
|
||||||
|
renderer.shaderManager.PushShader(defaultShaderName);
|
||||||
|
renderer.RenderUniform1i(textureUniformName, 0);
|
||||||
|
renderer.EnableVertexAttribArray(vPositionName);
|
||||||
|
renderer.EnableVertexAttribArray(vTexCoordName);
|
||||||
|
|
||||||
|
renderer.PushProjectionMatrix(Environment::width, Environment::height, -1, 1);
|
||||||
|
renderer.PushMatrix();
|
||||||
|
|
||||||
|
renderer.LoadIdentity();
|
||||||
|
|
||||||
|
|
||||||
|
glBindTexture(GL_TEXTURE_2D, buttonTexture->getTexID());
|
||||||
|
renderer.DrawVertexRenderStruct(button);
|
||||||
|
|
||||||
|
glBindTexture(GL_TEXTURE_2D, musicVolumeBarTexture->getTexID());
|
||||||
|
renderer.DrawVertexRenderStruct(musicVolumeBar);
|
||||||
|
|
||||||
|
glBindTexture(GL_TEXTURE_2D, musicVolumeBarButtonTexture->getTexID());
|
||||||
|
renderer.DrawVertexRenderStruct(musicVolumeBarButton);
|
||||||
|
|
||||||
|
renderer.PopMatrix();
|
||||||
|
renderer.PopProjectionMatrix();
|
||||||
|
renderer.DisableVertexAttribArray(vPositionName);
|
||||||
|
renderer.DisableVertexAttribArray(vTexCoordName);
|
||||||
|
|
||||||
|
renderer.shaderManager.PopShader();
|
||||||
|
CheckGlError();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Game::drawScene() {
|
||||||
|
static const std::string defaultShaderName = "default";
|
||||||
|
static const std::string envShaderName = "env";
|
||||||
|
static const std::string vPositionName = "vPosition";
|
||||||
|
static const std::string vTexCoordName = "vTexCoord";
|
||||||
|
static const std::string textureUniformName = "Texture";
|
||||||
|
|
||||||
|
glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
|
||||||
|
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
|
glViewport(0, 0, Environment::width, Environment::height);
|
||||||
|
|
||||||
|
CheckGlError();
|
||||||
|
|
||||||
|
float skyPercent = 0.0;
|
||||||
|
float distance = planetObject.distanceToPlanetSurface(Environment::shipPosition);
|
||||||
|
if (distance > 1900.f)
|
||||||
|
{
|
||||||
|
skyPercent = 0.0f;
|
||||||
|
}
|
||||||
|
else if (distance < 1000.f)
|
||||||
|
{
|
||||||
|
skyPercent = 1.0f;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
skyPercent = (1900.f - distance) / 900.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
drawCubemap(skyPercent);
|
||||||
|
planetObject.draw(renderer);
|
||||||
|
if (planetObject.distanceToPlanetSurface(Environment::shipPosition) > 100.f)
|
||||||
|
{
|
||||||
|
glClear(GL_DEPTH_BUFFER_BIT);
|
||||||
|
}
|
||||||
|
drawShip();
|
||||||
|
drawBoxes();
|
||||||
|
|
||||||
|
drawUI();
|
||||||
|
|
||||||
|
CheckGlError();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Game::processTickCount() {
|
||||||
|
|
||||||
|
if (lastTickCount == 0) {
|
||||||
|
lastTickCount = SDL_GetTicks64();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
newTickCount = SDL_GetTicks64();
|
||||||
|
if (newTickCount - lastTickCount > CONST_TIMER_INTERVAL) {
|
||||||
|
size_t delta = (newTickCount - lastTickCount > CONST_MAX_TIME_INTERVAL) ?
|
||||||
|
CONST_MAX_TIME_INTERVAL : newTickCount - lastTickCount;
|
||||||
|
|
||||||
|
//gameObjects.updateScene(delta);
|
||||||
|
sparkEmitter.update(static_cast<float>(delta));
|
||||||
|
planetObject.update(static_cast<float>(delta));
|
||||||
|
|
||||||
|
if (Environment::tapDownHold) {
|
||||||
|
|
||||||
|
float diffx = Environment::tapDownCurrentPos.v[0] - Environment::tapDownStartPos.v[0];
|
||||||
|
float diffy = Environment::tapDownCurrentPos.v[1] - Environment::tapDownStartPos.v[1];
|
||||||
|
|
||||||
|
|
||||||
|
if (abs(diffy) > 5.0 || abs(diffx) > 5.0) //threshold
|
||||||
|
{
|
||||||
|
|
||||||
|
float rotationPower = sqrtf(diffx * diffx + diffy * diffy);
|
||||||
|
|
||||||
|
//std::cout << rotationPower << std::endl;
|
||||||
|
|
||||||
|
float deltaAlpha = rotationPower * delta * M_PI / 500000.f;
|
||||||
|
|
||||||
|
Vector3f rotationDirection = { diffy, diffx, 0 };
|
||||||
|
|
||||||
|
rotationDirection = rotationDirection.normalized();
|
||||||
|
|
||||||
|
Vector4f rotateQuat = {
|
||||||
|
rotationDirection.v[0] * sin(deltaAlpha * 0.5f),
|
||||||
|
rotationDirection.v[1] * sin(deltaAlpha * 0.5f),
|
||||||
|
rotationDirection.v[2] * sin(deltaAlpha * 0.5f),
|
||||||
|
cos(deltaAlpha * 0.5f) };
|
||||||
|
|
||||||
|
Matrix3f rotateMat = QuatToMatrix(rotateQuat);
|
||||||
|
|
||||||
|
Environment::shipMatrix = MultMatrixMatrix(Environment::shipMatrix, rotateMat);
|
||||||
|
Environment::inverseShipMatrix = InverseMatrix(Environment::shipMatrix);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fabs(Environment::shipVelocity) > 0.01f)
|
||||||
|
{
|
||||||
|
Vector3f velocityDirection = { 0,0, -Environment::shipVelocity * delta / 1000.f };
|
||||||
|
Vector3f velocityDirectionAdjusted = MultMatrixVector(Environment::shipMatrix, velocityDirection);
|
||||||
|
|
||||||
|
Environment::shipPosition = Environment::shipPosition + velocityDirectionAdjusted;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastTickCount = newTickCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Game::render() {
|
||||||
|
SDL_GL_MakeCurrent(ZL::Environment::window, glContext);
|
||||||
|
ZL::CheckGlError();
|
||||||
|
|
||||||
|
glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||||
|
|
||||||
|
drawScene();
|
||||||
|
processTickCount();
|
||||||
|
|
||||||
|
SDL_GL_SwapWindow(ZL::Environment::window);
|
||||||
|
}
|
||||||
|
void Game::update() {
|
||||||
|
SDL_Event event;
|
||||||
|
while (SDL_PollEvent(&event)) {
|
||||||
|
if (event.type == SDL_QUIT) {
|
||||||
|
Environment::exitGameLoop = true;
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (event.type == SDL_MOUSEBUTTONDOWN) {
|
||||||
|
// 1. Îáðàáîòêà íàæàòèÿ êíîïêè ìûøè
|
||||||
|
|
||||||
|
int mx = event.button.x;
|
||||||
|
int my = event.button.y;
|
||||||
|
|
||||||
|
std::cout << mx << " " << my << '\n';
|
||||||
|
int uiX = mx;
|
||||||
|
int uiY = Environment::height - my;
|
||||||
|
if (uiX >= volumeBarMinX - 40 && uiX <= volumeBarMaxX + 40 &&
|
||||||
|
uiY >= volumeBarMinY - 40 && uiY <= volumeBarMaxY + 40) {
|
||||||
|
isDraggingVolume = true;
|
||||||
|
UpdateVolumeFromMouse(mx, my);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Environment::tapDownHold = true;
|
||||||
|
// Êîîðäèíàòû íà÷àëüíîãî íàæàòèÿ
|
||||||
|
Environment::tapDownStartPos.v[0] = event.button.x;
|
||||||
|
Environment::tapDownStartPos.v[1] = event.button.y;
|
||||||
|
// Íà÷àëüíàÿ ïîçèöèÿ òàêæå ñòàíîâèòñÿ òåêóùåé
|
||||||
|
Environment::tapDownCurrentPos.v[0] = event.button.x;
|
||||||
|
Environment::tapDownCurrentPos.v[1] = event.button.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (event.type == SDL_MOUSEBUTTONUP) {
|
||||||
|
// 2. Îáðàáîòêà îòïóñêàíèÿ êíîïêè ìûøè
|
||||||
|
isDraggingVolume = false;
|
||||||
|
Environment::tapDownHold = false;
|
||||||
|
}
|
||||||
|
else if (event.type == SDL_MOUSEMOTION) {
|
||||||
|
// 3. Îáðàáîòêà ïåðåìåùåíèÿ ìûøè
|
||||||
|
int mx = event.motion.x;
|
||||||
|
int my = event.motion.y;
|
||||||
|
|
||||||
|
if (isDraggingVolume) {
|
||||||
|
// Äâèãàåì ìûøü ïî ñëàéäåðó — ìåíÿåì ãðîìêîñòü è ïîçèöèþ êðóæêà
|
||||||
|
UpdateVolumeFromMouse(mx, my);
|
||||||
|
}
|
||||||
|
if (Environment::tapDownHold) {
|
||||||
|
// Îáíîâëåíèå òåêóùåé ïîçèöèè, åñëè êíîïêà óäåðæèâàåòñÿ
|
||||||
|
Environment::tapDownCurrentPos.v[0] = event.motion.x;
|
||||||
|
Environment::tapDownCurrentPos.v[1] = event.motion.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (event.type == SDL_MOUSEWHEEL) {
|
||||||
|
static const float zoomstep = 2.0f;
|
||||||
|
if (event.wheel.y > 0) {
|
||||||
|
Environment::zoom -= zoomstep;
|
||||||
|
}
|
||||||
|
else if (event.wheel.y < 0) {
|
||||||
|
Environment::zoom += zoomstep;
|
||||||
|
}
|
||||||
|
if (Environment::zoom < zoomstep) {
|
||||||
|
Environment::zoom = zoomstep;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (event.type == SDL_KEYUP)
|
||||||
|
{
|
||||||
|
if (event.key.keysym.sym == SDLK_i)
|
||||||
|
{
|
||||||
|
Environment::shipVelocity += 500.f;
|
||||||
|
}
|
||||||
|
if (event.key.keysym.sym == SDLK_k)
|
||||||
|
{
|
||||||
|
Environment::shipVelocity -= 500.f;
|
||||||
|
}
|
||||||
|
if (event.key.keysym.sym == SDLK_o)
|
||||||
|
{
|
||||||
|
Environment::shipVelocity += 50.f;
|
||||||
|
}
|
||||||
|
if (event.key.keysym.sym == SDLK_l)
|
||||||
|
{
|
||||||
|
Environment::shipVelocity -= 50.f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ZL
|
||||||
95
Game.h
Executable file
95
Game.h
Executable file
@ -0,0 +1,95 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "OpenGlExtensions.h"
|
||||||
|
#include "Renderer.h"
|
||||||
|
#include "Environment.h"
|
||||||
|
#include "TextureManager.h"
|
||||||
|
#include "SparkEmitter.h"
|
||||||
|
#include "PlanetObject.h"
|
||||||
|
|
||||||
|
namespace ZL {
|
||||||
|
|
||||||
|
|
||||||
|
struct BoxCoords
|
||||||
|
{
|
||||||
|
Vector3f pos;
|
||||||
|
Matrix3f m;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class Game {
|
||||||
|
public:
|
||||||
|
Game();
|
||||||
|
~Game();
|
||||||
|
|
||||||
|
void setup();
|
||||||
|
void update();
|
||||||
|
void render();
|
||||||
|
|
||||||
|
|
||||||
|
bool shouldExit() const { return Environment::exitGameLoop; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void processTickCount();
|
||||||
|
void drawScene();
|
||||||
|
void drawCubemap(float skyPercent);
|
||||||
|
void drawShip();
|
||||||
|
void drawBoxes();
|
||||||
|
void drawUI();
|
||||||
|
|
||||||
|
SDL_Window* window;
|
||||||
|
SDL_GLContext glContext;
|
||||||
|
Renderer renderer;
|
||||||
|
|
||||||
|
size_t newTickCount;
|
||||||
|
size_t lastTickCount;
|
||||||
|
|
||||||
|
std::shared_ptr<Texture> rockTexture;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<BoxCoords> boxCoordsArr;
|
||||||
|
std::vector<VertexRenderStruct> boxRenderArr;
|
||||||
|
|
||||||
|
|
||||||
|
std::shared_ptr<Texture> buttonTexture;
|
||||||
|
VertexRenderStruct button;
|
||||||
|
|
||||||
|
std::shared_ptr<Texture> musicVolumeBarTexture;
|
||||||
|
VertexRenderStruct musicVolumeBar;
|
||||||
|
|
||||||
|
std::shared_ptr<Texture> musicVolumeBarButtonTexture;
|
||||||
|
VertexRenderStruct musicVolumeBarButton;
|
||||||
|
|
||||||
|
|
||||||
|
bool isDraggingVolume = false;
|
||||||
|
float musicVolume = 0.0f;
|
||||||
|
float volumeBarMinX = 1190.0f;
|
||||||
|
float volumeBarMaxX = 1200.0f;
|
||||||
|
float volumeBarMinY = 100.0f;
|
||||||
|
float volumeBarMaxY = 600.0f;
|
||||||
|
float musicVolumeBarButtonButtonCenterX = 1195.0f;
|
||||||
|
float musicVolumeBarButtonButtonRadius = 25.0f;
|
||||||
|
void UpdateVolumeFromMouse(int mouseX, int mouseY);
|
||||||
|
void UpdateVolumeKnob();
|
||||||
|
|
||||||
|
static const size_t CONST_TIMER_INTERVAL = 10;
|
||||||
|
static const size_t CONST_MAX_TIME_INTERVAL = 1000;
|
||||||
|
|
||||||
|
std::shared_ptr<Texture> sparkTexture;
|
||||||
|
std::shared_ptr<Texture> spaceshipTexture;
|
||||||
|
std::shared_ptr<Texture> cubemapTexture;
|
||||||
|
VertexDataStruct spaceshipBase;
|
||||||
|
VertexRenderStruct spaceship;
|
||||||
|
|
||||||
|
VertexRenderStruct cubemap;
|
||||||
|
|
||||||
|
std::shared_ptr<Texture> boxTexture;
|
||||||
|
VertexDataStruct boxBase;
|
||||||
|
|
||||||
|
SparkEmitter sparkEmitter;
|
||||||
|
PlanetObject planetObject;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace ZL
|
||||||
335
OpenGlExtensions.cpp
Executable file
335
OpenGlExtensions.cpp
Executable file
@ -0,0 +1,335 @@
|
|||||||
|
#include "OpenGlExtensions.h"
|
||||||
|
|
||||||
|
#include "Utils.h"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__)
|
||||||
|
|
||||||
|
//====================================================
|
||||||
|
//===================== GLSL Shaders =================
|
||||||
|
//====================================================
|
||||||
|
|
||||||
|
//Requires GL_VERSION_2_0
|
||||||
|
PFNGLCREATEPROGRAMPROC glCreateProgram = NULL;
|
||||||
|
PFNGLDELETEPROGRAMPROC glDeleteProgram = NULL;
|
||||||
|
PFNGLLINKPROGRAMPROC glLinkProgram = NULL;
|
||||||
|
PFNGLVALIDATEPROGRAMPROC glValidateProgram = NULL;
|
||||||
|
PFNGLUSEPROGRAMPROC glUseProgram = NULL;
|
||||||
|
PFNGLGETPROGRAMIVPROC glGetProgramiv = NULL;
|
||||||
|
PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog = NULL;
|
||||||
|
PFNGLCREATESHADERPROC glCreateShader = NULL;
|
||||||
|
PFNGLDELETESHADERPROC glDeleteShader = NULL;
|
||||||
|
PFNGLSHADERSOURCEPROC glShaderSource = NULL;
|
||||||
|
PFNGLCOMPILESHADERPROC glCompileShader = NULL;
|
||||||
|
PFNGLATTACHSHADERPROC glAttachShader = NULL;
|
||||||
|
PFNGLDETACHSHADERPROC glDetachShader = NULL;
|
||||||
|
PFNGLGETSHADERIVPROC glGetShaderiv = NULL;
|
||||||
|
PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog = NULL;
|
||||||
|
PFNGLGETATTRIBLOCATIONPROC glGetAttribLocation = NULL;
|
||||||
|
PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer = NULL;
|
||||||
|
PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray = NULL;
|
||||||
|
PFNGLDISABLEVERTEXATTRIBARRAYPROC glDisableVertexAttribArray = NULL;
|
||||||
|
PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation = NULL;
|
||||||
|
PFNGLUNIFORMMATRIX3FVPROC glUniformMatrix3fv = NULL;
|
||||||
|
PFNGLUNIFORMMATRIX4FVPROC glUniformMatrix4fv = NULL;
|
||||||
|
PFNGLUNIFORM1IPROC glUniform1i = NULL;
|
||||||
|
PFNGLUNIFORM1FVPROC glUniform1fv = NULL;
|
||||||
|
PFNGLUNIFORM3FVPROC glUniform2fv = NULL;
|
||||||
|
PFNGLUNIFORM3FVPROC glUniform3fv = NULL;
|
||||||
|
PFNGLUNIFORM4FVPROC glUniform4fv = NULL;
|
||||||
|
PFNGLVERTEXATTRIB1FPROC glVertexAttrib1f = NULL;
|
||||||
|
PFNGLVERTEXATTRIB2FPROC glVertexAttrib2f = NULL;
|
||||||
|
PFNGLVERTEXATTRIB3FPROC glVertexAttrib3f = NULL;
|
||||||
|
PFNGLVERTEXATTRIB4FPROC glVertexAttrib4f = NULL;
|
||||||
|
PFNGLVERTEXATTRIB2FVPROC glVertexAttrib2fv = NULL;
|
||||||
|
PFNGLVERTEXATTRIB3FVPROC glVertexAttrib3fv = NULL;
|
||||||
|
PFNGLVERTEXATTRIB4FVPROC glVertexAttrib4fv = NULL;
|
||||||
|
PFNGLGETACTIVEATTRIBPROC glGetActiveAttrib = NULL;
|
||||||
|
PFNGLGETACTIVEUNIFORMPROC glGetActiveUniform = NULL;
|
||||||
|
|
||||||
|
|
||||||
|
//=======================================
|
||||||
|
//=========== Multitexture ==============
|
||||||
|
//=======================================
|
||||||
|
|
||||||
|
//Requires GL version 1.3
|
||||||
|
PFNGLACTIVETEXTUREPROC glActiveTexture = NULL;
|
||||||
|
|
||||||
|
//=======================================
|
||||||
|
//========== Vertex buffer ==============
|
||||||
|
//=======================================
|
||||||
|
|
||||||
|
//Requires GL_VERSION_1_5
|
||||||
|
PFNGLGENBUFFERSPROC glGenBuffers = NULL;
|
||||||
|
PFNGLDELETEBUFFERSPROC glDeleteBuffers = NULL;
|
||||||
|
PFNGLBINDBUFFERPROC glBindBuffer = NULL;
|
||||||
|
PFNGLBUFFERDATAPROC glBufferData = NULL;
|
||||||
|
PFNGLBUFFERSUBDATAPROC glBufferSubData = NULL;
|
||||||
|
PFNGLMAPBUFFERPROC glMapBuffer = NULL;
|
||||||
|
PFNGLUNMAPBUFFERPROC glUnmapBuffer = NULL;
|
||||||
|
|
||||||
|
//=========================================
|
||||||
|
//============ Frame buffer ===============
|
||||||
|
//=========================================
|
||||||
|
|
||||||
|
//Requires GL_ARB_framebuffer_object
|
||||||
|
PFNGLISRENDERBUFFERPROC glIsRenderbuffer = NULL;
|
||||||
|
PFNGLBINDRENDERBUFFERPROC glBindRenderbuffer = NULL;
|
||||||
|
PFNGLDELETERENDERBUFFERSPROC glDeleteRenderbuffers = NULL;
|
||||||
|
PFNGLGENRENDERBUFFERSPROC glGenRenderbuffers = NULL;
|
||||||
|
PFNGLRENDERBUFFERSTORAGEPROC glRenderbufferStorage = NULL;
|
||||||
|
PFNGLGETRENDERBUFFERPARAMETERIVPROC glGetRenderbufferParameteriv = NULL;
|
||||||
|
PFNGLISFRAMEBUFFERPROC glIsFramebuffer = NULL;
|
||||||
|
PFNGLBINDFRAMEBUFFERPROC glBindFramebuffer = NULL;
|
||||||
|
PFNGLDELETEFRAMEBUFFERSPROC glDeleteFramebuffers = NULL;
|
||||||
|
PFNGLGENFRAMEBUFFERSPROC glGenFramebuffers = NULL;
|
||||||
|
PFNGLCHECKFRAMEBUFFERSTATUSPROC glCheckFramebufferStatus = NULL;
|
||||||
|
PFNGLFRAMEBUFFERTEXTURE1DPROC glFramebufferTexture1D = NULL;
|
||||||
|
PFNGLFRAMEBUFFERTEXTURE2DPROC glFramebufferTexture2D = NULL;
|
||||||
|
PFNGLFRAMEBUFFERTEXTURE3DPROC glFramebufferTexture3D = NULL;
|
||||||
|
PFNGLFRAMEBUFFERRENDERBUFFERPROC glFramebufferRenderbuffer = NULL;
|
||||||
|
PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC glGetFramebufferAttachmentParameteriv = NULL;
|
||||||
|
PFNGLBLITFRAMEBUFFERPROC glBlitFramebuffer = NULL;
|
||||||
|
PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC glRenderbufferStorageMultisample = NULL;
|
||||||
|
PFNGLGENERATEMIPMAPPROC glGenerateMipmap = NULL;
|
||||||
|
PFNGLFRAMEBUFFERTEXTURELAYERPROC glFramebufferTextureLayer = NULL;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//===========================================
|
||||||
|
//============ Uniform buffer ===============
|
||||||
|
//===========================================
|
||||||
|
|
||||||
|
//Requires GL_ARB_uniform_buffer_object
|
||||||
|
PFNGLGETUNIFORMINDICESPROC glGetUniformIndices = NULL;
|
||||||
|
PFNGLGETACTIVEUNIFORMSIVPROC glGetActiveUniformsiv = NULL;
|
||||||
|
PFNGLGETACTIVEUNIFORMNAMEPROC glGetActiveUniformName = NULL;
|
||||||
|
PFNGLGETUNIFORMBLOCKINDEXPROC glGetUniformBlockIndex = NULL;
|
||||||
|
PFNGLGETACTIVEUNIFORMBLOCKIVPROC glGetActiveUniformBlockiv = NULL;
|
||||||
|
PFNGLGETACTIVEUNIFORMBLOCKNAMEPROC glGetActiveUniformBlockName = NULL;
|
||||||
|
PFNGLUNIFORMBLOCKBINDINGPROC glUniformBlockBinding = NULL;
|
||||||
|
PFNGLBINDBUFFERBASEPROC glBindBufferBase = NULL;
|
||||||
|
|
||||||
|
|
||||||
|
PFNGLGENVERTEXARRAYSPROC glGenVertexArrays = NULL;
|
||||||
|
PFNGLBINDVERTEXARRAYPROC glBindVertexArray = NULL;
|
||||||
|
PFNGLDELETEVERTEXARRAYSPROC glDeleteVertexArray = NULL;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace ZL {
|
||||||
|
|
||||||
|
bool BindOpenGlFunctions()
|
||||||
|
{
|
||||||
|
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__)
|
||||||
|
//char* extensionList = (char*)glGetString(GL_EXTENSIONS);
|
||||||
|
char* glVersion = (char*)glGetString(GL_VERSION);
|
||||||
|
bool ok = true;
|
||||||
|
|
||||||
|
//Requires OpenGL 2.0 or above
|
||||||
|
if (glVersion[0] >= '2')
|
||||||
|
{
|
||||||
|
|
||||||
|
glActiveTexture = (PFNGLACTIVETEXTUREPROC)wglGetProcAddress("glActiveTexture");
|
||||||
|
|
||||||
|
glGenBuffers = (PFNGLGENBUFFERSPROC)wglGetProcAddress("glGenBuffers");
|
||||||
|
glDeleteBuffers = (PFNGLDELETEBUFFERSPROC)wglGetProcAddress("glDeleteBuffers");
|
||||||
|
glBindBuffer = (PFNGLBINDBUFFERPROC)wglGetProcAddress("glBindBuffer");
|
||||||
|
glBufferData = (PFNGLBUFFERDATAPROC)wglGetProcAddress("glBufferData");
|
||||||
|
glBufferSubData = (PFNGLBUFFERSUBDATAPROC)wglGetProcAddress("glBufferSubData");
|
||||||
|
glMapBuffer = (PFNGLMAPBUFFERPROC)wglGetProcAddress("glMapBuffer");
|
||||||
|
glUnmapBuffer = (PFNGLUNMAPBUFFERPROC)wglGetProcAddress("glUnmapBuffer");
|
||||||
|
|
||||||
|
glCreateProgram = (PFNGLCREATEPROGRAMPROC)wglGetProcAddress("glCreateProgram");
|
||||||
|
glDeleteProgram = (PFNGLDELETEPROGRAMPROC)wglGetProcAddress("glDeleteProgram");
|
||||||
|
glLinkProgram = (PFNGLLINKPROGRAMPROC)wglGetProcAddress("glLinkProgram");
|
||||||
|
glValidateProgram = (PFNGLVALIDATEPROGRAMPROC)wglGetProcAddress("glValidateProgram");
|
||||||
|
glUseProgram = (PFNGLUSEPROGRAMPROC)wglGetProcAddress("glUseProgram");
|
||||||
|
glGetProgramiv = (PFNGLGETPROGRAMIVPROC)wglGetProcAddress("glGetProgramiv");
|
||||||
|
glGetProgramInfoLog = (PFNGLGETPROGRAMINFOLOGPROC)wglGetProcAddress("glGetProgramInfoLog");
|
||||||
|
glCreateShader = (PFNGLCREATESHADERPROC)wglGetProcAddress("glCreateShader");
|
||||||
|
|
||||||
|
|
||||||
|
glDeleteShader = (PFNGLDELETESHADERPROC)wglGetProcAddress("glDeleteShader");
|
||||||
|
glShaderSource = (PFNGLSHADERSOURCEPROC)wglGetProcAddress("glShaderSource");
|
||||||
|
glCompileShader = (PFNGLCOMPILESHADERPROC)wglGetProcAddress("glCompileShader");
|
||||||
|
glAttachShader = (PFNGLATTACHSHADERPROC)wglGetProcAddress("glAttachShader");
|
||||||
|
glDetachShader = (PFNGLDETACHSHADERPROC)wglGetProcAddress("glDetachShader");
|
||||||
|
glGetShaderiv = (PFNGLGETSHADERIVPROC)wglGetProcAddress("glGetShaderiv");
|
||||||
|
glGetShaderInfoLog = (PFNGLGETSHADERINFOLOGPROC)wglGetProcAddress("glGetShaderInfoLog");
|
||||||
|
glGetAttribLocation = (PFNGLGETATTRIBLOCATIONPROC)wglGetProcAddress("glGetAttribLocation");
|
||||||
|
glVertexAttribPointer = (PFNGLVERTEXATTRIBPOINTERPROC)wglGetProcAddress("glVertexAttribPointer");
|
||||||
|
glEnableVertexAttribArray = (PFNGLENABLEVERTEXATTRIBARRAYPROC)wglGetProcAddress("glEnableVertexAttribArray");
|
||||||
|
|
||||||
|
glDisableVertexAttribArray = (PFNGLDISABLEVERTEXATTRIBARRAYPROC)wglGetProcAddress("glDisableVertexAttribArray");
|
||||||
|
glGetUniformLocation = (PFNGLGETUNIFORMLOCATIONPROC)wglGetProcAddress("glGetUniformLocation");
|
||||||
|
glUniformMatrix3fv = (PFNGLUNIFORMMATRIX3FVPROC)wglGetProcAddress("glUniformMatrix3fv");
|
||||||
|
glUniformMatrix4fv = (PFNGLUNIFORMMATRIX4FVPROC)wglGetProcAddress("glUniformMatrix4fv");
|
||||||
|
glUniform1i = (PFNGLUNIFORM1IPROC)wglGetProcAddress("glUniform1i");
|
||||||
|
glUniform1fv = (PFNGLUNIFORM1FVPROC)wglGetProcAddress("glUniform1fv");
|
||||||
|
glUniform2fv = (PFNGLUNIFORM2FVPROC)wglGetProcAddress("glUniform2fv");
|
||||||
|
glUniform3fv = (PFNGLUNIFORM3FVPROC)wglGetProcAddress("glUniform3fv");
|
||||||
|
glUniform4fv = (PFNGLUNIFORM4FVPROC)wglGetProcAddress("glUniform4fv");
|
||||||
|
|
||||||
|
glVertexAttrib1f = (PFNGLVERTEXATTRIB1FPROC)wglGetProcAddress("glVertexAttrib1f");
|
||||||
|
glVertexAttrib2f = (PFNGLVERTEXATTRIB2FPROC)wglGetProcAddress("glVertexAttrib2f");
|
||||||
|
glVertexAttrib3f = (PFNGLVERTEXATTRIB3FPROC)wglGetProcAddress("glVertexAttrib3f");
|
||||||
|
glVertexAttrib4f = (PFNGLVERTEXATTRIB4FPROC)wglGetProcAddress("glVertexAttrib4f");
|
||||||
|
glVertexAttrib2fv = (PFNGLVERTEXATTRIB2FVPROC)wglGetProcAddress("glVertexAttrib2fv");
|
||||||
|
glVertexAttrib3fv = (PFNGLVERTEXATTRIB3FVPROC)wglGetProcAddress("glVertexAttrib3fv");
|
||||||
|
glVertexAttrib4fv = (PFNGLVERTEXATTRIB4FVPROC)wglGetProcAddress("glVertexAttrib4fv");
|
||||||
|
glGetActiveAttrib = (PFNGLGETACTIVEATTRIBPROC)wglGetProcAddress("glGetActiveAttrib");
|
||||||
|
glGetActiveUniform = (PFNGLGETACTIVEUNIFORMPROC)wglGetProcAddress("glGetActiveUniform");
|
||||||
|
|
||||||
|
|
||||||
|
if (glActiveTexture == NULL ||
|
||||||
|
glGenBuffers == NULL ||
|
||||||
|
glDeleteBuffers == NULL ||
|
||||||
|
glBindBuffer == NULL ||
|
||||||
|
glBufferData == NULL ||
|
||||||
|
glBufferSubData == NULL ||
|
||||||
|
glMapBuffer == NULL ||
|
||||||
|
glCreateProgram == NULL ||
|
||||||
|
glDeleteProgram == NULL ||
|
||||||
|
glLinkProgram == NULL ||
|
||||||
|
glValidateProgram == NULL ||
|
||||||
|
glUseProgram == NULL ||
|
||||||
|
glGetProgramiv == NULL ||
|
||||||
|
glGetProgramInfoLog == NULL ||
|
||||||
|
glCreateShader == NULL ||
|
||||||
|
glDeleteShader == NULL ||
|
||||||
|
glShaderSource == NULL ||
|
||||||
|
glCompileShader == NULL ||
|
||||||
|
glAttachShader == NULL ||
|
||||||
|
glDetachShader == NULL ||
|
||||||
|
glGetShaderiv == NULL ||
|
||||||
|
glGetShaderInfoLog == NULL ||
|
||||||
|
glGetAttribLocation == NULL ||
|
||||||
|
glVertexAttribPointer == NULL ||
|
||||||
|
glEnableVertexAttribArray == NULL ||
|
||||||
|
glDisableVertexAttribArray == NULL ||
|
||||||
|
glGetUniformLocation == NULL ||
|
||||||
|
glUniformMatrix3fv == NULL ||
|
||||||
|
glUniformMatrix4fv == NULL ||
|
||||||
|
glUniform1i == NULL ||
|
||||||
|
glUniform1fv == NULL ||
|
||||||
|
glUniform2fv == NULL ||
|
||||||
|
glUniform3fv == NULL ||
|
||||||
|
glUniform4fv == NULL ||
|
||||||
|
glEnableVertexAttribArray == NULL ||
|
||||||
|
glVertexAttrib1f == NULL ||
|
||||||
|
glVertexAttrib2f == NULL ||
|
||||||
|
glVertexAttrib3f == NULL ||
|
||||||
|
glVertexAttrib4f == NULL ||
|
||||||
|
glVertexAttrib2fv == NULL ||
|
||||||
|
glVertexAttrib3fv == NULL ||
|
||||||
|
glVertexAttrib4fv == NULL ||
|
||||||
|
glGetActiveAttrib == NULL ||
|
||||||
|
glGetActiveUniform == NULL)
|
||||||
|
{
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
glIsRenderbuffer = (PFNGLISRENDERBUFFERPROC)wglGetProcAddress("glIsRenderbuffer");
|
||||||
|
glBindRenderbuffer = (PFNGLBINDRENDERBUFFERPROC)wglGetProcAddress("glBindRenderbuffer");
|
||||||
|
glDeleteRenderbuffers = (PFNGLDELETERENDERBUFFERSPROC)wglGetProcAddress("glDeleteRenderbuffers");
|
||||||
|
glGenRenderbuffers = (PFNGLGENRENDERBUFFERSPROC)wglGetProcAddress("glGenRenderbuffers");
|
||||||
|
glRenderbufferStorage = (PFNGLRENDERBUFFERSTORAGEPROC)wglGetProcAddress("glRenderbufferStorage");
|
||||||
|
glGetRenderbufferParameteriv = (PFNGLGETRENDERBUFFERPARAMETERIVPROC)wglGetProcAddress("glGetRenderbufferParameteriv");
|
||||||
|
glIsFramebuffer = (PFNGLISFRAMEBUFFERPROC)wglGetProcAddress("glIsFramebuffer");
|
||||||
|
glBindFramebuffer = (PFNGLBINDFRAMEBUFFERPROC)wglGetProcAddress("glBindFramebuffer");
|
||||||
|
glDeleteFramebuffers = (PFNGLDELETEFRAMEBUFFERSPROC)wglGetProcAddress("glDeleteFramebuffers");
|
||||||
|
glGenFramebuffers = (PFNGLGENFRAMEBUFFERSPROC)wglGetProcAddress("glGenFramebuffers");
|
||||||
|
glCheckFramebufferStatus = (PFNGLCHECKFRAMEBUFFERSTATUSPROC)wglGetProcAddress("glCheckFramebufferStatus");
|
||||||
|
glFramebufferTexture1D = (PFNGLFRAMEBUFFERTEXTURE1DPROC)wglGetProcAddress("glFramebufferTexture1D");
|
||||||
|
glFramebufferTexture2D = (PFNGLFRAMEBUFFERTEXTURE2DPROC)wglGetProcAddress("glFramebufferTexture2D");
|
||||||
|
glFramebufferTexture3D = (PFNGLFRAMEBUFFERTEXTURE3DPROC)wglGetProcAddress("glFramebufferTexture3D");
|
||||||
|
glFramebufferRenderbuffer = (PFNGLFRAMEBUFFERRENDERBUFFERPROC)wglGetProcAddress("glFramebufferRenderbuffer");
|
||||||
|
glGetFramebufferAttachmentParameteriv = (PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC)wglGetProcAddress("glGetFramebufferAttachmentParameteriv");
|
||||||
|
glBlitFramebuffer = (PFNGLBLITFRAMEBUFFERPROC)wglGetProcAddress("glBlitFramebuffer");
|
||||||
|
glRenderbufferStorageMultisample = (PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC)wglGetProcAddress("glRenderbufferStorageMultisample");
|
||||||
|
glGenerateMipmap = (PFNGLGENERATEMIPMAPPROC)wglGetProcAddress("glGenerateMipmap");
|
||||||
|
glFramebufferTextureLayer = (PFNGLFRAMEBUFFERTEXTURELAYERPROC)wglGetProcAddress("glFramebufferTextureLayer");
|
||||||
|
|
||||||
|
if (glIsRenderbuffer == NULL ||
|
||||||
|
glBindRenderbuffer == NULL ||
|
||||||
|
glDeleteRenderbuffers == NULL ||
|
||||||
|
glGenRenderbuffers == NULL ||
|
||||||
|
glRenderbufferStorage == NULL ||
|
||||||
|
glGetRenderbufferParameteriv == NULL ||
|
||||||
|
glIsFramebuffer == NULL ||
|
||||||
|
glBindFramebuffer == NULL ||
|
||||||
|
glDeleteFramebuffers == NULL ||
|
||||||
|
glGenFramebuffers == NULL ||
|
||||||
|
glCheckFramebufferStatus == NULL ||
|
||||||
|
glFramebufferTexture1D == NULL ||
|
||||||
|
glFramebufferTexture2D == NULL ||
|
||||||
|
glFramebufferTexture3D == NULL ||
|
||||||
|
glFramebufferRenderbuffer == NULL ||
|
||||||
|
glGetFramebufferAttachmentParameteriv == NULL ||
|
||||||
|
glBlitFramebuffer == NULL ||
|
||||||
|
glRenderbufferStorageMultisample == NULL ||
|
||||||
|
glGenerateMipmap == NULL ||
|
||||||
|
glFramebufferTextureLayer == NULL)
|
||||||
|
{
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
glGetUniformIndices = (PFNGLGETUNIFORMINDICESPROC)wglGetProcAddress("glGetUniformIndices");
|
||||||
|
glGetActiveUniformsiv = (PFNGLGETACTIVEUNIFORMSIVPROC)wglGetProcAddress("glGetActiveUniformsiv");
|
||||||
|
glGetActiveUniformName = (PFNGLGETACTIVEUNIFORMNAMEPROC)wglGetProcAddress("glGetActiveUniformName");
|
||||||
|
glGetUniformBlockIndex = (PFNGLGETUNIFORMBLOCKINDEXPROC)wglGetProcAddress("glGetUniformBlockIndex");
|
||||||
|
glGetActiveUniformBlockiv = (PFNGLGETACTIVEUNIFORMBLOCKIVPROC)wglGetProcAddress("glGetActiveUniformBlockiv");
|
||||||
|
glGetActiveUniformBlockName = (PFNGLGETACTIVEUNIFORMBLOCKNAMEPROC)wglGetProcAddress("glGetActiveUniformBlockName");
|
||||||
|
glUniformBlockBinding = (PFNGLUNIFORMBLOCKBINDINGPROC)wglGetProcAddress("glUniformBlockBinding");
|
||||||
|
glBindBufferBase = (PFNGLBINDBUFFERBASEPROC)wglGetProcAddress("glBindBufferBase");
|
||||||
|
|
||||||
|
if (glGetUniformIndices == NULL ||
|
||||||
|
glGetActiveUniformsiv == NULL ||
|
||||||
|
glGetActiveUniformName == NULL ||
|
||||||
|
glGetUniformBlockIndex == NULL ||
|
||||||
|
glGetActiveUniformBlockiv == NULL ||
|
||||||
|
glGetActiveUniformBlockName == NULL ||
|
||||||
|
glUniformBlockBinding == NULL ||
|
||||||
|
glBindBufferBase == NULL)
|
||||||
|
{
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
glGenVertexArrays = (PFNGLGENVERTEXARRAYSPROC)wglGetProcAddress("glGenVertexArrays");
|
||||||
|
glBindVertexArray = (PFNGLBINDVERTEXARRAYPROC)wglGetProcAddress("glBindVertexArray");
|
||||||
|
glDeleteVertexArray = (PFNGLDELETEVERTEXARRAYSPROC)wglGetProcAddress("glBindVertexArray");
|
||||||
|
|
||||||
|
if (glGenVertexArrays == NULL ||
|
||||||
|
glBindVertexArray == NULL ||
|
||||||
|
glDeleteVertexArray == NULL)
|
||||||
|
{
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return ok;
|
||||||
|
#else
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void CheckGlError()
|
||||||
|
{
|
||||||
|
size_t error = glGetError();
|
||||||
|
if (error != GL_NO_ERROR)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Gl error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
160
OpenGlExtensions.h
Executable file
160
OpenGlExtensions.h
Executable file
@ -0,0 +1,160 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
|
||||||
|
#include "SDL.h"
|
||||||
|
#ifdef EMSCRIPTEN
|
||||||
|
//#define GL_GLEXT_PROTOTYPES 1
|
||||||
|
//#define EGL_EGLEXT_PROTOTYPES 1
|
||||||
|
//#include <SDL2/SDL_opengl.h>
|
||||||
|
#include <GLES3/gl3.h>
|
||||||
|
#include "emscripten.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __linux__
|
||||||
|
#include <GL/gl.h>
|
||||||
|
#include <GL/glu.h>
|
||||||
|
#include <GLES3/gl3.h>
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <exception>
|
||||||
|
#include <stdexcept>
|
||||||
|
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__)
|
||||||
|
|
||||||
|
#include "windows.h"
|
||||||
|
|
||||||
|
#define GET_X_LPARAM(lp) ((int)(short)LOWORD(lp))
|
||||||
|
#define GET_Y_LPARAM(lp) ((int)(short)HIWORD(lp))
|
||||||
|
|
||||||
|
//#define GL_GLEXT_PROTOTYPES
|
||||||
|
|
||||||
|
#include "gl/gl.h"
|
||||||
|
#include "gl/glu.h"
|
||||||
|
#include "gl/glext.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
#include <array>
|
||||||
|
#include <stack>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <map>
|
||||||
|
#define _USE_MATH_DEFINES
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
//Requires GL_VERSION_2_0
|
||||||
|
extern PFNGLCREATEPROGRAMPROC glCreateProgram;
|
||||||
|
extern PFNGLDELETEPROGRAMPROC glDeleteProgram;
|
||||||
|
extern PFNGLLINKPROGRAMPROC glLinkProgram;
|
||||||
|
extern PFNGLVALIDATEPROGRAMPROC glValidateProgram;
|
||||||
|
extern PFNGLUSEPROGRAMPROC glUseProgram;
|
||||||
|
extern PFNGLGETPROGRAMIVPROC glGetProgramiv;
|
||||||
|
extern PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog;
|
||||||
|
extern PFNGLCREATESHADERPROC glCreateShader;
|
||||||
|
extern PFNGLDELETESHADERPROC glDeleteShader;
|
||||||
|
extern PFNGLSHADERSOURCEPROC glShaderSource;
|
||||||
|
extern PFNGLCOMPILESHADERPROC glCompileShader;
|
||||||
|
extern PFNGLATTACHSHADERPROC glAttachShader;
|
||||||
|
extern PFNGLDETACHSHADERPROC glDetachShader;
|
||||||
|
extern PFNGLGETSHADERIVPROC glGetShaderiv;
|
||||||
|
extern PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog;
|
||||||
|
extern PFNGLGETATTRIBLOCATIONPROC glGetAttribLocation;
|
||||||
|
extern PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer;
|
||||||
|
extern PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray;
|
||||||
|
extern PFNGLDISABLEVERTEXATTRIBARRAYPROC glDisableVertexAttribArray;
|
||||||
|
extern PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation;
|
||||||
|
extern PFNGLUNIFORMMATRIX3FVPROC glUniformMatrix3fv;
|
||||||
|
extern PFNGLUNIFORMMATRIX4FVPROC glUniformMatrix4fv;
|
||||||
|
extern PFNGLUNIFORM1IPROC glUniform1i;
|
||||||
|
extern PFNGLUNIFORM1FVPROC glUniform1fv;
|
||||||
|
extern PFNGLUNIFORM3FVPROC glUniform2fv;
|
||||||
|
extern PFNGLUNIFORM3FVPROC glUniform3fv;
|
||||||
|
extern PFNGLUNIFORM4FVPROC glUniform4fv;
|
||||||
|
extern PFNGLVERTEXATTRIB1FPROC glVertexAttrib1f;
|
||||||
|
extern PFNGLVERTEXATTRIB2FPROC glVertexAttrib2f;
|
||||||
|
extern PFNGLVERTEXATTRIB3FPROC glVertexAttrib3f;
|
||||||
|
extern PFNGLVERTEXATTRIB4FPROC glVertexAttrib4f;
|
||||||
|
extern PFNGLVERTEXATTRIB2FVPROC glVertexAttrib2fv;
|
||||||
|
extern PFNGLVERTEXATTRIB3FVPROC glVertexAttrib3fv;
|
||||||
|
extern PFNGLVERTEXATTRIB4FVPROC glVertexAttrib4fv;
|
||||||
|
extern PFNGLGETACTIVEATTRIBPROC glGetActiveAttrib;
|
||||||
|
extern PFNGLGETACTIVEUNIFORMPROC glGetActiveUniform;
|
||||||
|
|
||||||
|
|
||||||
|
//=======================================
|
||||||
|
//=========== Multitexture ==============
|
||||||
|
//=======================================
|
||||||
|
|
||||||
|
//Requires GL version 1.3
|
||||||
|
extern PFNGLACTIVETEXTUREPROC glActiveTexture;
|
||||||
|
|
||||||
|
//=======================================
|
||||||
|
//========== Vertex buffer ==============
|
||||||
|
//=======================================
|
||||||
|
|
||||||
|
//Requires GL_VERSION_1_5
|
||||||
|
extern PFNGLGENBUFFERSPROC glGenBuffers;
|
||||||
|
extern PFNGLDELETEBUFFERSPROC glDeleteBuffers;
|
||||||
|
extern PFNGLBINDBUFFERPROC glBindBuffer;
|
||||||
|
extern PFNGLBUFFERDATAPROC glBufferData;
|
||||||
|
extern PFNGLBUFFERSUBDATAPROC glBufferSubData;
|
||||||
|
extern PFNGLMAPBUFFERPROC glMapBuffer;
|
||||||
|
extern PFNGLUNMAPBUFFERPROC glUnmapBuffer;
|
||||||
|
|
||||||
|
//=========================================
|
||||||
|
//============ Frame buffer ===============
|
||||||
|
//=========================================
|
||||||
|
|
||||||
|
//Requires GL_ARB_framebuffer_object
|
||||||
|
extern PFNGLISRENDERBUFFERPROC glIsRenderbuffer;
|
||||||
|
extern PFNGLBINDRENDERBUFFERPROC glBindRenderbuffer;
|
||||||
|
extern PFNGLDELETERENDERBUFFERSPROC glDeleteRenderbuffers;
|
||||||
|
extern PFNGLGENRENDERBUFFERSPROC glGenRenderbuffers;
|
||||||
|
extern PFNGLRENDERBUFFERSTORAGEPROC glRenderbufferStorage;
|
||||||
|
extern PFNGLGETRENDERBUFFERPARAMETERIVPROC glGetRenderbufferParameteriv;
|
||||||
|
extern PFNGLISFRAMEBUFFERPROC glIsFramebuffer;
|
||||||
|
extern PFNGLBINDFRAMEBUFFERPROC glBindFramebuffer;
|
||||||
|
extern PFNGLDELETEFRAMEBUFFERSPROC glDeleteFramebuffers;
|
||||||
|
extern PFNGLGENFRAMEBUFFERSPROC glGenFramebuffers;
|
||||||
|
extern PFNGLCHECKFRAMEBUFFERSTATUSPROC glCheckFramebufferStatus;
|
||||||
|
extern PFNGLFRAMEBUFFERTEXTURE1DPROC glFramebufferTexture1D;
|
||||||
|
extern PFNGLFRAMEBUFFERTEXTURE2DPROC glFramebufferTexture2D;
|
||||||
|
extern PFNGLFRAMEBUFFERTEXTURE3DPROC glFramebufferTexture3D;
|
||||||
|
extern PFNGLFRAMEBUFFERRENDERBUFFERPROC glFramebufferRenderbuffer;
|
||||||
|
extern PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC glGetFramebufferAttachmentParameteriv;
|
||||||
|
extern PFNGLBLITFRAMEBUFFERPROC glBlitFramebuffer;
|
||||||
|
extern PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC glRenderbufferStorageMultisample;
|
||||||
|
extern PFNGLGENERATEMIPMAPPROC glGenerateMipmap;
|
||||||
|
extern PFNGLFRAMEBUFFERTEXTURELAYERPROC glFramebufferTextureLayer;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//===========================================
|
||||||
|
//============ Uniform buffer ===============
|
||||||
|
//===========================================
|
||||||
|
|
||||||
|
//Requires GL_ARB_uniform_buffer_object
|
||||||
|
extern PFNGLGETUNIFORMINDICESPROC glGetUniformIndices;
|
||||||
|
extern PFNGLGETACTIVEUNIFORMSIVPROC glGetActiveUniformsiv;
|
||||||
|
extern PFNGLGETACTIVEUNIFORMNAMEPROC glGetActiveUniformName;
|
||||||
|
extern PFNGLGETUNIFORMBLOCKINDEXPROC glGetUniformBlockIndex;
|
||||||
|
extern PFNGLGETACTIVEUNIFORMBLOCKIVPROC glGetActiveUniformBlockiv;
|
||||||
|
extern PFNGLGETACTIVEUNIFORMBLOCKNAMEPROC glGetActiveUniformBlockName;
|
||||||
|
extern PFNGLUNIFORMBLOCKBINDINGPROC glUniformBlockBinding;
|
||||||
|
extern PFNGLBINDBUFFERBASEPROC glBindBufferBase;
|
||||||
|
|
||||||
|
|
||||||
|
extern PFNGLGENVERTEXARRAYSPROC glGenVertexArrays;
|
||||||
|
extern PFNGLBINDVERTEXARRAYPROC glBindVertexArray;
|
||||||
|
extern PFNGLDELETEVERTEXARRAYSPROC glDeleteVertexArray;
|
||||||
|
#else
|
||||||
|
|
||||||
|
#endif
|
||||||
|
namespace ZL {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool BindOpenGlFunctions();
|
||||||
|
|
||||||
|
void CheckGlError();
|
||||||
|
}
|
||||||
73
Perlin.cpp
Normal file
73
Perlin.cpp
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
#include "Perlin.h"
|
||||||
|
#include <cmath>
|
||||||
|
#include <numeric>
|
||||||
|
#include <random>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace ZL {
|
||||||
|
|
||||||
|
PerlinNoise::PerlinNoise() {
|
||||||
|
p.resize(256);
|
||||||
|
std::iota(p.begin(), p.end(), 0);
|
||||||
|
// Ïåðåìåøèâàåì äëÿ ñëó÷àéíîñòè (ìîæíî çàäàòü seed)
|
||||||
|
std::default_random_engine engine(77777);
|
||||||
|
std::shuffle(p.begin(), p.end(), engine);
|
||||||
|
p.insert(p.end(), p.begin(), p.end()); // Äóáëèðóåì äëÿ ïåðåïîëíåíèÿ
|
||||||
|
}
|
||||||
|
|
||||||
|
PerlinNoise::PerlinNoise(uint64_t seed) {
|
||||||
|
p.resize(256);
|
||||||
|
std::iota(p.begin(), p.end(), 0);
|
||||||
|
// Ïåðåìåøèâàåì äëÿ ñëó÷àéíîñòè (èñïîëüçóåì ïåðåäàííûé seed)
|
||||||
|
std::default_random_engine engine(static_cast<unsigned int>(seed));
|
||||||
|
std::shuffle(p.begin(), p.end(), engine);
|
||||||
|
p.insert(p.end(), p.begin(), p.end()); // Äóáëèðóåì äëÿ ïåðåïîëíåíèÿ
|
||||||
|
}
|
||||||
|
|
||||||
|
float PerlinNoise::fade(float t) { return t * t * t * (t * (t * 6 - 15) + 10); }
|
||||||
|
|
||||||
|
float PerlinNoise::lerp(float t, float a, float b) { return a + t * (b - a); }
|
||||||
|
|
||||||
|
float PerlinNoise::grad(int hash, float x, float y, float z) {
|
||||||
|
int h = hash & 15;
|
||||||
|
float u = h < 8 ? x : y;
|
||||||
|
float v = h < 4 ? y : (h == 12 || h == 14 ? x : z);
|
||||||
|
return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);
|
||||||
|
}
|
||||||
|
|
||||||
|
float PerlinNoise::noise(float x, float y, float z) {
|
||||||
|
int X = (int)floor(x) & 255;
|
||||||
|
int Y = (int)floor(y) & 255;
|
||||||
|
int Z = (int)floor(z) & 255;
|
||||||
|
|
||||||
|
x -= floor(x);
|
||||||
|
y -= floor(y);
|
||||||
|
z -= floor(z);
|
||||||
|
|
||||||
|
float u = fade(x);
|
||||||
|
float v = fade(y);
|
||||||
|
float w = fade(z);
|
||||||
|
|
||||||
|
int A = p[X] + Y, AA = p[A] + Z, AB = p[A + 1] + Z;
|
||||||
|
int B = p[X + 1] + Y, BA = p[B] + Z, BB = p[B + 1] + Z;
|
||||||
|
|
||||||
|
return lerp(w, lerp(v, lerp(u, grad(p[AA], x, y, z), grad(p[BA], x - 1, y, z)),
|
||||||
|
lerp(u, grad(p[AB], x, y - 1, z), grad(p[BB], x - 1, y - 1, z))),
|
||||||
|
lerp(v, lerp(u, grad(p[AA + 1], x, y, z - 1), grad(p[BA + 1], x - 1, y, z - 1)),
|
||||||
|
lerp(u, grad(p[AB + 1], x, y - 1, z - 1), grad(p[BB + 1], x - 1, y - 1, z - 1))));
|
||||||
|
}
|
||||||
|
|
||||||
|
float PerlinNoise::getSurfaceHeight(Vector3f pos, float noiseCoeff) {
|
||||||
|
// ×àñòîòà øóìà (÷åì áîëüøå, òåì áîëüøå "õîëìîâ")
|
||||||
|
float frequency = 7.0f;
|
||||||
|
|
||||||
|
// Ïîëó÷àåì çíà÷åíèå øóìà (îáû÷íî îò -1 äî 1)
|
||||||
|
float noiseValue = noise(pos.v[0] * frequency, pos.v[1] * frequency, pos.v[2] * frequency);
|
||||||
|
|
||||||
|
// Ìàñøòàáèðóåì: õîòèì îòêëîíåíèå îò 1.0 äî 1.1 (ïðèìåðíî)
|
||||||
|
float height = 1.0f + (noiseValue * noiseCoeff);
|
||||||
|
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ZL
|
||||||
24
Perlin.h
Normal file
24
Perlin.h
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <cstdint>
|
||||||
|
#include "ZLMath.h"
|
||||||
|
|
||||||
|
namespace ZL {
|
||||||
|
|
||||||
|
class PerlinNoise {
|
||||||
|
std::vector<int> p;
|
||||||
|
public:
|
||||||
|
PerlinNoise();
|
||||||
|
PerlinNoise(uint64_t seed);
|
||||||
|
|
||||||
|
float fade(float t);
|
||||||
|
float lerp(float t, float a, float b);
|
||||||
|
float grad(int hash, float x, float y, float z);
|
||||||
|
|
||||||
|
float noise(float x, float y, float z);
|
||||||
|
|
||||||
|
float getSurfaceHeight(Vector3f pos, float noiseCoeff);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ZL
|
||||||
493
PlanetData.cpp
Normal file
493
PlanetData.cpp
Normal file
@ -0,0 +1,493 @@
|
|||||||
|
#include "PlanetData.h"
|
||||||
|
#include <iostream>
|
||||||
|
#include <numeric>
|
||||||
|
#include <cmath>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace ZL {
|
||||||
|
|
||||||
|
const float PlanetData::PLANET_RADIUS = 20000.f;
|
||||||
|
const Vector3f PlanetData::PLANET_CENTER_OFFSET = Vector3f{ 0.f, 0.f, 0.0f };
|
||||||
|
|
||||||
|
// --- Êîíñòàíòû äèàïàçîíîâ (ïåðåíåñåíû èç PlanetObject.cpp) ---
|
||||||
|
|
||||||
|
VertexID generateEdgeID(const VertexID& id1, const VertexID& id2) {
|
||||||
|
return id1 < id2 ? id1 + "_" + id2 : id2 + "_" + id1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Âñïîìîãàòåëüíàÿ ôóíêöèÿ äëÿ ïðîåêöèè (ëîêàëüíàÿ)
|
||||||
|
static Vector3f projectPointOnPlane(const Vector3f& P, const Vector3f& A, const Vector3f& B, const Vector3f& C) {
|
||||||
|
Vector3f AB = B + A * (-1.0f);
|
||||||
|
Vector3f AC = C + A * (-1.0f);
|
||||||
|
Vector3f N = AB.cross(AC).normalized();
|
||||||
|
Vector3f AP = P + A * (-1.0f);
|
||||||
|
float distance = N.dot(AP);
|
||||||
|
return P + N * (-distance);
|
||||||
|
}
|
||||||
|
|
||||||
|
PlanetData::PlanetData()
|
||||||
|
: perlin(77777)
|
||||||
|
, colorPerlin(123123)
|
||||||
|
, currentLod(0)
|
||||||
|
{
|
||||||
|
currentLod = planetMeshLods.size() - 1; // Start with max LOD
|
||||||
|
|
||||||
|
initialVertexMap = {
|
||||||
|
{{ 0.0f, 1.0f, 0.0f}, "A"},
|
||||||
|
{{ 0.0f, -1.0f, 0.0f}, "B"},
|
||||||
|
{{ 1.0f, 0.0f, 0.0f}, "C"},
|
||||||
|
{{-1.0f, 0.0f, 0.0f}, "D"},
|
||||||
|
{{ 0.0f, 0.0f, 1.0f}, "E"},
|
||||||
|
{{ 0.0f, 0.0f, -1.0f}, "F"}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlanetData::init() {
|
||||||
|
for (int i = 0; i < planetMeshLods.size(); i++) {
|
||||||
|
planetMeshLods[i] = generateSphere(i, 0.025f);
|
||||||
|
planetMeshLods[i].Scale(PLANET_RADIUS);
|
||||||
|
planetMeshLods[i].Move(PLANET_CENTER_OFFSET);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < planetMeshLodsNoDist.size(); i++) {
|
||||||
|
planetMeshLodsNoDist[i] = generateSphere(i, 0);
|
||||||
|
planetMeshLodsNoDist[i].Scale(PLANET_RADIUS);
|
||||||
|
planetMeshLodsNoDist[i].Move(PLANET_CENTER_OFFSET);
|
||||||
|
}
|
||||||
|
|
||||||
|
planetAtmosphereLod = generateSphere(5, 0);
|
||||||
|
planetAtmosphereLod.Scale(PLANET_RADIUS * 1.03);
|
||||||
|
planetAtmosphereLod.Move(PLANET_CENTER_OFFSET);
|
||||||
|
}
|
||||||
|
|
||||||
|
const LodLevel& PlanetData::getLodLevel(int level) const {
|
||||||
|
return planetMeshLods.at(level);
|
||||||
|
}
|
||||||
|
|
||||||
|
const LodLevel& PlanetData::getLodLevelNoDist(int level) const {
|
||||||
|
return planetMeshLodsNoDist.at(level);
|
||||||
|
}
|
||||||
|
|
||||||
|
const LodLevel& PlanetData::getAtmosphereLod() const {
|
||||||
|
return planetAtmosphereLod;
|
||||||
|
}
|
||||||
|
|
||||||
|
int PlanetData::getCurrentLodIndex() const {
|
||||||
|
return currentLod;
|
||||||
|
}
|
||||||
|
|
||||||
|
int PlanetData::getMaxLodIndex() const {
|
||||||
|
return static_cast<int>(planetMeshLods.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<float, float> PlanetData::calculateZRange(float dToPlanetSurface) {
|
||||||
|
|
||||||
|
|
||||||
|
float currentZNear;
|
||||||
|
float currentZFar;
|
||||||
|
float alpha;
|
||||||
|
|
||||||
|
if (dToPlanetSurface > 2000)
|
||||||
|
{
|
||||||
|
currentZNear = 1000;
|
||||||
|
currentZFar = currentZNear * 100;
|
||||||
|
}
|
||||||
|
else if (dToPlanetSurface > 1200)
|
||||||
|
{
|
||||||
|
currentZNear = 500;
|
||||||
|
currentZFar = currentZNear * 100;
|
||||||
|
}
|
||||||
|
else if (dToPlanetSurface > 650)
|
||||||
|
{
|
||||||
|
currentZNear = 250;
|
||||||
|
currentZFar = currentZNear * 100;
|
||||||
|
}
|
||||||
|
else if (dToPlanetSurface > 160)
|
||||||
|
{
|
||||||
|
currentZNear = 125;
|
||||||
|
currentZFar = currentZNear * 150;
|
||||||
|
}
|
||||||
|
else if (dToPlanetSurface > 100)
|
||||||
|
{
|
||||||
|
currentZNear = 65;
|
||||||
|
currentZFar = currentZNear * 170;
|
||||||
|
}
|
||||||
|
else if (dToPlanetSurface > 40)
|
||||||
|
{
|
||||||
|
currentZNear = 32;
|
||||||
|
currentZFar = 10000.f;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
currentZNear = 16;
|
||||||
|
currentZFar = 5000.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { currentZNear, currentZFar };
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
float PlanetData::distanceToPlanetSurface(const Vector3f& viewerPosition) {
|
||||||
|
Vector3f shipLocalPosition = viewerPosition - PLANET_CENTER_OFFSET;
|
||||||
|
|
||||||
|
// Èñïîëüçóåì getTrianglesUnderCamera äëÿ ïîèñêà
|
||||||
|
// ÂÍÈÌÀÍÈÅ: Çäåñü ðåêóðñèÿ ëîãèêè. Ìåòîä getTrianglesUnderCamera èñïîëüçóåò âíóòðè viewerPosition.
|
||||||
|
// ×òîáû íå äóáëèðîâàòü êîä, ïåðåïèøåì ëîãèêó ïîèñêà çäåñü èëè áóäåì èñïîëüçîâàòü óæå íàéäåííûé ðàíåå.
|
||||||
|
// Äëÿ ïðîñòîòû îñòàâèì ëîãèêó êàê â îðèãèíàëå, íî àäàïòèðîâàííóþ.
|
||||||
|
|
||||||
|
// ÂÀÆÍÎ: getTrianglesUnderCamera - ýòî "òÿæåëàÿ" ôóíêöèÿ. Â îðèãèíàëå îíà âûçûâàëàñü âíóòðè distanceToPlanetSurface.
|
||||||
|
// Íî îíà òðåáóåò LOD. Èñïîëüçóåì MAX LOD äëÿ òî÷íîñòè.
|
||||||
|
|
||||||
|
// Ðåêóðñèâíûé ïîèñê òðåóãîëüíèêîâ ïîä êàìåðîé.
|
||||||
|
// Çäåñü íàì íóæíî ðåàëèçîâàòü àíàëîã triangleUnderCamera, íî íå çàâèñÿùèé îò ãëîáàëüíîãî ñîñòîÿíèÿ.
|
||||||
|
// Â îðèãèíàëå ôóíêöèÿ triangleUnderCamera áûëà ðåêóðñèâíîé.
|
||||||
|
// Íèæå ÿ ðåàëèçóþ èòåðàòèâíûé èëè ðåêóðñèâíûé ïîäõîä âíóòðè getTrianglesUnderCamera.
|
||||||
|
|
||||||
|
// Âðåìåííîå ðåøåíèå: ïîëíàÿ êîïèÿ ëîãèêè ïîèñêà òðåóãîëüíèêà
|
||||||
|
// Íî íàì íóæåí äîñòóï ê ìåòîäó.
|
||||||
|
std::vector<int> targetTriangles = getTrianglesUnderCamera(viewerPosition);
|
||||||
|
|
||||||
|
if (targetTriangles.empty()) {
|
||||||
|
return (shipLocalPosition.length() - PLANET_RADIUS);
|
||||||
|
}
|
||||||
|
|
||||||
|
float lowestDistance;
|
||||||
|
|
||||||
|
int tri_index = targetTriangles[0];
|
||||||
|
const auto& posData = planetMeshLods[currentLod].vertexData.PositionData;
|
||||||
|
|
||||||
|
size_t data_index = tri_index * 3;
|
||||||
|
if (data_index + 2 >= posData.size()) {
|
||||||
|
return (shipLocalPosition.length() - PLANET_RADIUS);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Vector3f& A = posData[data_index];
|
||||||
|
const Vector3f& B = posData[data_index + 1];
|
||||||
|
const Vector3f& C = posData[data_index + 2];
|
||||||
|
|
||||||
|
Vector3f P_proj = projectPointOnPlane(shipLocalPosition, A, B, C);
|
||||||
|
Vector3f P_closest = P_proj;
|
||||||
|
|
||||||
|
lowestDistance = (shipLocalPosition - P_closest).length();
|
||||||
|
|
||||||
|
if (targetTriangles.size() <= 1)
|
||||||
|
{
|
||||||
|
return lowestDistance;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int i = 0; i < targetTriangles.size(); i++)
|
||||||
|
{
|
||||||
|
int tri_index = targetTriangles[i];
|
||||||
|
const auto& posData = planetMeshLods[currentLod].vertexData.PositionData;
|
||||||
|
|
||||||
|
size_t data_index = tri_index * 3;
|
||||||
|
if (data_index + 2 >= posData.size()) {
|
||||||
|
return (shipLocalPosition.length() - PLANET_RADIUS);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Vector3f& A = posData[data_index];
|
||||||
|
const Vector3f& B = posData[data_index + 1];
|
||||||
|
const Vector3f& C = posData[data_index + 2];
|
||||||
|
|
||||||
|
Vector3f P_proj = projectPointOnPlane(shipLocalPosition, A, B, C);
|
||||||
|
Vector3f P_closest = P_proj;
|
||||||
|
|
||||||
|
if (lowestDistance < (shipLocalPosition - P_closest).length())
|
||||||
|
{
|
||||||
|
lowestDistance = (shipLocalPosition - P_closest).length();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return lowestDistance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Ðåàëèçàöèÿ getTrianglesUnderCamera (áûâøàÿ triangleUnderCamera) ---
|
||||||
|
// Âñïîìîãàòåëüíàÿ ðåêóðñèâíàÿ ôóíêöèÿ, ñêðûòàÿ îò ïóáëè÷íîãî API
|
||||||
|
static std::vector<int> recursiveTriangleSearch(int lod, const Vector3f& pos, const std::array<LodLevel, MAX_LOD_LEVELS>& meshes) {
|
||||||
|
std::vector<int> r;
|
||||||
|
// Ëîãèêà óðîâíÿ 0 (áàçîâûé îêòàýäð)
|
||||||
|
if (lod == 0) {
|
||||||
|
if (pos.v[1] >= 0) {
|
||||||
|
if (pos.v[0] >= 0 && pos.v[2] >= 0) r.push_back(0);
|
||||||
|
if (pos.v[0] >= 0 && pos.v[2] <= 0) r.push_back(1);
|
||||||
|
if (pos.v[0] <= 0 && pos.v[2] <= 0) r.push_back(2);
|
||||||
|
if (pos.v[0] <= 0 && pos.v[2] >= 0) r.push_back(3);
|
||||||
|
}
|
||||||
|
if (pos.v[1] <= 0) {
|
||||||
|
if (pos.v[0] >= 0 && pos.v[2] >= 0) r.push_back(4);
|
||||||
|
if (pos.v[0] <= 0 && pos.v[2] >= 0) r.push_back(5);
|
||||||
|
if (pos.v[0] <= 0 && pos.v[2] <= 0) r.push_back(6);
|
||||||
|
if (pos.v[0] >= 0 && pos.v[2] <= 0) r.push_back(7);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Ðåêóðñèâíûé øàã
|
||||||
|
std::vector<int> r0 = recursiveTriangleSearch(lod - 1, pos, meshes);
|
||||||
|
for (int tri0 : r0) {
|
||||||
|
Vector3f a = meshes[lod - 1].vertexData.PositionData[tri0 * 3];
|
||||||
|
Vector3f b = meshes[lod - 1].vertexData.PositionData[tri0 * 3 + 1];
|
||||||
|
Vector3f c = meshes[lod - 1].vertexData.PositionData[tri0 * 3 + 2];
|
||||||
|
|
||||||
|
std::vector<int> result = PlanetData::find_sub_triangle_spherical(a, b, c, pos);
|
||||||
|
for (int trix : result) {
|
||||||
|
r.push_back(tri0 * 4 + trix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<int> PlanetData::getTrianglesUnderCamera(const Vector3f& viewerPosition) {
|
||||||
|
// Âûçûâàåì ðåêóðñèþ ñ òåêóùèì LOD
|
||||||
|
return recursiveTriangleSearch(currentLod, viewerPosition, planetMeshLods);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Îñòàëüíûå ìåòîäû (check_spherical_side, generateSphere è ò.ä.) ---
|
||||||
|
// (Êîïèðóéòå ðåàëèçàöèþ èç ñòàðîãî PlanetObject.cpp,
|
||||||
|
// çàìåíÿÿ îáðàùåíèÿ ê Environment::shipPosition íà àðãóìåíòû, åñëè íóæíî,
|
||||||
|
// è èñïîëüçóÿ this->planetMeshLods)
|
||||||
|
|
||||||
|
float PlanetData::check_spherical_side(const Vector3f& V1, const Vector3f& V2, const Vector3f& P, const Vector3f& V3, float epsilon) {
|
||||||
|
Vector3f N_plane = V1.cross(V2);
|
||||||
|
float sign_P = P.dot(N_plane);
|
||||||
|
float sign_V3 = V3.dot(N_plane);
|
||||||
|
return sign_P * sign_V3;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PlanetData::is_inside_spherical_triangle(const Vector3f& P, const Vector3f& V1, const Vector3f& V2, const Vector3f& V3, float epsilon) {
|
||||||
|
if (check_spherical_side(V1, V2, P, V3, epsilon) < -epsilon) return false;
|
||||||
|
if (check_spherical_side(V2, V3, P, V1, epsilon) < -epsilon) return false;
|
||||||
|
if (check_spherical_side(V3, V1, P, V2, epsilon) < -epsilon) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<int> PlanetData::find_sub_triangle_spherical(const Vector3f& a_orig, const Vector3f& b_orig, const Vector3f& c_orig, const Vector3f& px_orig) {
|
||||||
|
const float EPSILON = 1e-6f;
|
||||||
|
std::vector<int> result;
|
||||||
|
const Vector3f a = a_orig.normalized();
|
||||||
|
const Vector3f b = b_orig.normalized();
|
||||||
|
const Vector3f c = c_orig.normalized();
|
||||||
|
const Vector3f pxx_normalized = px_orig.normalized();
|
||||||
|
const Vector3f m_ab = ((a + b) * 0.5f).normalized();
|
||||||
|
const Vector3f m_bc = ((b + c) * 0.5f).normalized();
|
||||||
|
const Vector3f m_ac = ((a + c) * 0.5f).normalized();
|
||||||
|
|
||||||
|
if (is_inside_spherical_triangle(pxx_normalized, a, m_ab, m_ac, EPSILON)) result.push_back(0);
|
||||||
|
if (is_inside_spherical_triangle(pxx_normalized, m_ab, b, m_bc, EPSILON)) result.push_back(1);
|
||||||
|
if (is_inside_spherical_triangle(pxx_normalized, m_ac, m_bc, c, EPSILON)) result.push_back(2);
|
||||||
|
if (is_inside_spherical_triangle(pxx_normalized, m_ab, m_bc, m_ac, EPSILON)) result.push_back(3);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Triangle> PlanetData::subdivideTriangles(const std::vector<Triangle>& input, float noiseCoeff) {
|
||||||
|
std::vector<Triangle> output;
|
||||||
|
|
||||||
|
for (const auto& t : input) {
|
||||||
|
// Âåðøèíû è èõ ID
|
||||||
|
const Vector3f& a = t.data[0];
|
||||||
|
const Vector3f& b = t.data[1];
|
||||||
|
const Vector3f& c = t.data[2];
|
||||||
|
const VertexID& id_a = t.ids[0];
|
||||||
|
const VertexID& id_b = t.ids[1];
|
||||||
|
const VertexID& id_c = t.ids[2];
|
||||||
|
|
||||||
|
// 1. Âû÷èñëÿåì ñåðåäèíû (êîîðäèíàòû)
|
||||||
|
Vector3f m_ab = ((a + b) * 0.5f).normalized();
|
||||||
|
Vector3f m_bc = ((b + c) * 0.5f).normalized();
|
||||||
|
Vector3f m_ac = ((a + c) * 0.5f).normalized();
|
||||||
|
|
||||||
|
Vector3f pm_ab = m_ab * perlin.getSurfaceHeight(m_ab, noiseCoeff);
|
||||||
|
Vector3f pm_bc = m_bc * perlin.getSurfaceHeight(m_bc, noiseCoeff);
|
||||||
|
Vector3f pm_ac = m_ac * perlin.getSurfaceHeight(m_ac, noiseCoeff);
|
||||||
|
|
||||||
|
// 2. Âû÷èñëÿåì ID íîâûõ âåðøèí
|
||||||
|
VertexID id_mab = generateEdgeID(id_a, id_b);
|
||||||
|
VertexID id_mbc = generateEdgeID(id_b, id_c);
|
||||||
|
VertexID id_mac = generateEdgeID(id_a, id_c);
|
||||||
|
|
||||||
|
// 3. Ôîðìèðóåì 4 íîâûõ òðåóãîëüíèêà
|
||||||
|
output.emplace_back(Triangle{ {a, pm_ab, pm_ac}, {id_a, id_mab, id_mac} }); // 0
|
||||||
|
output.emplace_back(Triangle{ {pm_ab, b, pm_bc}, {id_mab, id_b, id_mbc} }); // 1
|
||||||
|
output.emplace_back(Triangle{ {pm_ac, pm_bc, c}, {id_mac, id_mbc, id_c} }); // 2
|
||||||
|
output.emplace_back(Triangle{ {pm_ab, pm_bc, pm_ac}, {id_mab, id_mbc, id_mac} }); // 3
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
LodLevel PlanetData::trianglesToVertices(const std::vector<Triangle>& geometry) {
|
||||||
|
LodLevel result;
|
||||||
|
result.triangles = geometry;
|
||||||
|
|
||||||
|
size_t vertexCount = geometry.size() * 3;
|
||||||
|
result.vertexData.PositionData.reserve(vertexCount);
|
||||||
|
result.vertexData.NormalData.reserve(vertexCount);
|
||||||
|
result.vertexData.TexCoordData.reserve(vertexCount);
|
||||||
|
result.vertexData.TangentData.reserve(vertexCount); // Äîáàâëÿåì ðåçåðâ
|
||||||
|
result.vertexData.BinormalData.reserve(vertexCount);
|
||||||
|
result.VertexIDs.reserve(vertexCount);
|
||||||
|
|
||||||
|
const std::array<Vector2f, 3> triangleUVs = {
|
||||||
|
Vector2f(0.5f, 1.0f),
|
||||||
|
Vector2f(0.0f, 0.0f),
|
||||||
|
Vector2f(1.0f, 0.0f)
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& t : geometry) {
|
||||||
|
// --- Âû÷èñëÿåì ëîêàëüíûé áàçèñ òðåóãîëüíèêà (êàê â GetRotationForTriangle) ---
|
||||||
|
Vector3f vA = t.data[0];
|
||||||
|
Vector3f vB = t.data[1];
|
||||||
|
Vector3f vC = t.data[2];
|
||||||
|
|
||||||
|
Vector3f x_axis = (vC - vB).normalized(); // Íàïðàâëåíèå U
|
||||||
|
|
||||||
|
Vector3f edge1 = vB - vA;
|
||||||
|
Vector3f edge2 = vC - vA;
|
||||||
|
Vector3f z_axis = edge1.cross(edge2).normalized(); // Íîðìàëü ïëîñêîñòè
|
||||||
|
|
||||||
|
// Ïðîâåðêà íàïðàâëåíèÿ íîðìàëè íàðóæó (îò öåíòðà ïëàíåòû)
|
||||||
|
Vector3f centerToTri = (vA + vB + vC).normalized();
|
||||||
|
if (z_axis.dot(centerToTri) < 0) {
|
||||||
|
z_axis = z_axis * -1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3f y_axis = z_axis.cross(x_axis).normalized(); // Íàïðàâëåíèå V
|
||||||
|
|
||||||
|
for (int i = 0; i < 3; ++i) {
|
||||||
|
result.vertexData.PositionData.push_back(t.data[i]);
|
||||||
|
result.vertexData.NormalData.push_back(z_axis); // Ïëîñêàÿ íîðìàëü ãðàíè äëÿ Parallax
|
||||||
|
result.vertexData.TexCoordData.push_back(triangleUVs[i]);
|
||||||
|
|
||||||
|
// Çàïèñûâàåì âû÷èñëåííûé áàçèñ â êàæäóþ âåðøèíó òðåóãîëüíèêà
|
||||||
|
result.vertexData.TangentData.push_back(x_axis);
|
||||||
|
result.vertexData.BinormalData.push_back(y_axis);
|
||||||
|
|
||||||
|
result.VertexIDs.push_back(t.ids[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
LodLevel PlanetData::generateSphere(int subdivisions, float noiseCoeff) {
|
||||||
|
// 1. Èñõîäíûé îêòàýäð è ïðèñâîåíèå ID
|
||||||
|
std::vector<Triangle> geometry = {
|
||||||
|
// Âåðõíÿÿ ïîëóñôåðà (Y > 0)
|
||||||
|
{{ 0.0f, 1.0f, 0.0f}, { 1.0f, 0.0f, 0.0f}, { 0.0f, 0.0f, 1.0f}}, // 0
|
||||||
|
{{ 0.0f, 1.0f, 0.0f}, { 0.0f, 0.0f, -1.0f}, { 1.0f, 0.0f, 0.0f}}, // 1
|
||||||
|
{{ 0.0f, 1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}, { 0.0f, 0.0f, -1.0f}}, // 2
|
||||||
|
{{ 0.0f, 1.0f, 0.0f}, { 0.0f, 0.0f, 1.0f}, {-1.0f, 0.0f, 0.0f}}, // 3
|
||||||
|
// Íèæíÿÿ ïîëóñôåðà (Y < 0)
|
||||||
|
{{ 0.0f, -1.0f, 0.0f}, { 0.0f, 0.0f, 1.0f}, { 1.0f, 0.0f, 0.0f}}, // 4
|
||||||
|
{{ 0.0f, -1.0f, 0.0f}, { 1.0f, 0.0f, 0.0f}, { 0.0f, 0.0f, -1.0f}}, // 5
|
||||||
|
{{ 0.0f, -1.0f, 0.0f}, { 0.0f, 0.0f, -1.0f}, {-1.0f, 0.0f, 0.0f}}, // 6
|
||||||
|
{{ 0.0f, -1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}, { 0.0f, 0.0f, 1.0f}} // 7
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ïðèñâîåíèå ID èñõîäíûì âåðøèíàì
|
||||||
|
for (auto& t : geometry) {
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
// Èñïîëüçóåì map äëÿ ïîëó÷åíèÿ ID ïî ÷èñòûì êîîðäèíàòàì (íîðì. == ÷èñòûå)
|
||||||
|
t.ids[i] = initialVertexMap[t.data[i].normalized()];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 3. Ðàçáèâàåì N ðàç (â subdivideTriangles ãåíåðèðóþòñÿ ID íîâûõ âåðøèí)
|
||||||
|
for (int i = 0; i < subdivisions; i++) {
|
||||||
|
geometry = subdivideTriangles(geometry, noiseCoeff);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Ãåíåðèðóåì PositionData, NormalData è VertexIDs
|
||||||
|
LodLevel lodLevel = trianglesToVertices(geometry);
|
||||||
|
|
||||||
|
// 5. Ñîçäàíèå V2T-Map íà îñíîâå VertexIDs (ÒÎÏÎËÎÃÈ×ÅÑÊÈÉ ÊËÞ×)
|
||||||
|
V2TMap v2tMap;
|
||||||
|
const auto& finalVertexIDs = lodLevel.VertexIDs;
|
||||||
|
size_t num_triangles = geometry.size();
|
||||||
|
|
||||||
|
for (size_t i = 0; i < num_triangles; ++i) {
|
||||||
|
for (int j = 0; j < 3; ++j) {
|
||||||
|
VertexID v_id = finalVertexIDs[i * 3 + j];
|
||||||
|
|
||||||
|
// Åñëè êëþ÷ óæå åñòü, ïðîñòî äîáàâëÿåì èíäåêñ
|
||||||
|
v2tMap[v_id].push_back((int)i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lodLevel.v2tMap = v2tMap;
|
||||||
|
|
||||||
|
// 6. Ïðèìåíåíèå ôèíàëüíîãî øóìà (åñëè âû õîòåëè, ÷òîáû øóì áûë òîëüêî çäåñü)
|
||||||
|
// Çäåñü ìû äîëæíû áûëè áû ïðèìåíèòü øóì ê buffer.PositionData,
|
||||||
|
// íî â âàøåì èñõîäíîì êîäå øóì ïðèìåíÿëñÿ ðàíåå.
|
||||||
|
// Ïðåäïîëàãàåì, ÷òî øóì áóäåò ïðèìåíåí çäåñü (èíà÷å v2tMap íå áóäåò ñîîòâåòñòâîâàòü).
|
||||||
|
// ÂÎÑÑÒÀÍÎÂÈÌ ØÀÃ 2, íî äëÿ ôèíàëüíîé ãåîìåòðèè:
|
||||||
|
for (size_t i = 0; i < lodLevel.vertexData.PositionData.size(); i++) {
|
||||||
|
Vector3f dir = lodLevel.vertexData.PositionData[i].normalized();
|
||||||
|
lodLevel.vertexData.PositionData[i] = dir * perlin.getSurfaceHeight(dir, noiseCoeff);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 7. Ãåíåðàöèÿ ColorData
|
||||||
|
lodLevel.vertexData.ColorData.reserve(geometry.size() * 3);
|
||||||
|
const Vector3f baseColor = { 0.498f, 0.416f, 0.0f };
|
||||||
|
const float colorFrequency = 5.0f;
|
||||||
|
const float colorAmplitude = 0.2f;
|
||||||
|
const Vector3f offsetR = { 0.1f, 0.2f, 0.3f };
|
||||||
|
const Vector3f offsetG = { 0.5f, 0.4f, 0.6f };
|
||||||
|
const Vector3f offsetB = { 0.9f, 0.8f, 0.7f };
|
||||||
|
|
||||||
|
|
||||||
|
for (size_t i = 0; i < geometry.size(); i++) {
|
||||||
|
for (int j = 0; j < 3; j++) {
|
||||||
|
// Èñïîëüçóåì íîðìàëèçîâàííûé âåêòîð èç PositionData (êîòîðûé ðàâåí NormalData)
|
||||||
|
Vector3f dir = lodLevel.vertexData.NormalData[i * 3 + j];
|
||||||
|
|
||||||
|
// Âû÷èñëåíèå öâåòîâîãî øóìà
|
||||||
|
float noiseR = colorPerlin.noise(
|
||||||
|
(dir.v[0] + offsetR.v[0]) * colorFrequency,
|
||||||
|
(dir.v[1] + offsetR.v[1]) * colorFrequency,
|
||||||
|
(dir.v[2] + offsetR.v[2]) * colorFrequency
|
||||||
|
);
|
||||||
|
// ... (àíàëîãè÷íî äëÿ noiseG è noiseB)
|
||||||
|
|
||||||
|
// Çäåñü ìû èñïîëüçóåì çàãëóøêè, òàê êàê íåò ïîëíîãî îïðåäåëåíèÿ PerlinNoise
|
||||||
|
float noiseG = 0.0f;
|
||||||
|
float noiseB = 0.0f;
|
||||||
|
|
||||||
|
Vector3f colorOffset = {
|
||||||
|
noiseR * colorAmplitude,
|
||||||
|
noiseG * colorAmplitude,
|
||||||
|
noiseB * colorAmplitude
|
||||||
|
};
|
||||||
|
|
||||||
|
Vector3f finalColor = baseColor + colorOffset;
|
||||||
|
|
||||||
|
lodLevel.vertexData.ColorData.push_back(finalColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return lodLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Ïðèìåð findNeighbors:
|
||||||
|
std::vector<int> PlanetData::findNeighbors(int index, int lod) {
|
||||||
|
const V2TMap& v2tMap = planetMeshLods[lod].v2tMap;
|
||||||
|
const auto& vertexIDs = planetMeshLods[lod].VertexIDs;
|
||||||
|
if ((index * 3 + 2) >= vertexIDs.size()) return {};
|
||||||
|
std::set<int> neighbors;
|
||||||
|
for (int i = 0; i < 3; ++i) {
|
||||||
|
VertexID v_id = vertexIDs[index * 3 + i];
|
||||||
|
auto it = v2tMap.find(v_id);
|
||||||
|
if (it != v2tMap.end()) {
|
||||||
|
for (int tri_index : it->second) {
|
||||||
|
if (tri_index != index) {
|
||||||
|
neighbors.insert(tri_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::vector<int>(neighbors.begin(), neighbors.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
117
PlanetData.h
Normal file
117
PlanetData.h
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ZLMath.h"
|
||||||
|
#include "Perlin.h"
|
||||||
|
#include "Renderer.h" // Äëÿ VertexDataStruct
|
||||||
|
#include <vector>
|
||||||
|
#include <array>
|
||||||
|
#include <string>
|
||||||
|
#include <map>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
namespace ZL {
|
||||||
|
|
||||||
|
using VertexID = std::string;
|
||||||
|
using V2TMap = std::map<VertexID, std::vector<int>>;
|
||||||
|
|
||||||
|
VertexID generateEdgeID(const VertexID& id1, const VertexID& id2);
|
||||||
|
|
||||||
|
constexpr static int MAX_LOD_LEVELS = 6;
|
||||||
|
//constexpr static int MAX_LOD_LEVELS = 3;
|
||||||
|
|
||||||
|
struct Triangle
|
||||||
|
{
|
||||||
|
std::array<Vector3f, 3> data;
|
||||||
|
std::array<VertexID, 3> ids;
|
||||||
|
|
||||||
|
Triangle(Vector3f p1, Vector3f p2, Vector3f p3)
|
||||||
|
: data{ p1, p2, p3 }
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Triangle(std::array<Vector3f, 3> idata, std::array<VertexID, 3> iids)
|
||||||
|
: data{ idata }
|
||||||
|
, ids{ iids }
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LodLevel
|
||||||
|
{
|
||||||
|
std::vector<Triangle> triangles;
|
||||||
|
VertexDataStruct vertexData;
|
||||||
|
std::vector<VertexID> VertexIDs;
|
||||||
|
V2TMap v2tMap;
|
||||||
|
|
||||||
|
void Scale(float s)
|
||||||
|
{
|
||||||
|
vertexData.Scale(s);
|
||||||
|
for (auto& t : triangles) {
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
t.data[i] = t.data[i] * s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void Move(Vector3f pos)
|
||||||
|
{
|
||||||
|
vertexData.Move(pos);
|
||||||
|
for (auto& t : triangles) {
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
t.data[i] = t.data[i] + pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class PlanetData {
|
||||||
|
public:
|
||||||
|
static const float PLANET_RADIUS;
|
||||||
|
static const Vector3f PLANET_CENTER_OFFSET;
|
||||||
|
|
||||||
|
private:
|
||||||
|
PerlinNoise perlin;
|
||||||
|
PerlinNoise colorPerlin;
|
||||||
|
|
||||||
|
std::array<LodLevel, MAX_LOD_LEVELS> planetMeshLods;
|
||||||
|
std::array<LodLevel, MAX_LOD_LEVELS> planetMeshLodsNoDist;
|
||||||
|
LodLevel planetAtmosphereLod;
|
||||||
|
|
||||||
|
int currentLod; // Ëîãè÷åñêèé òåêóùèé óðîâåíü äåòàëèçàöèè
|
||||||
|
|
||||||
|
std::map<Vector3f, VertexID> initialVertexMap;
|
||||||
|
|
||||||
|
// Âíóòðåííèå ìåòîäû ãåíåðàöèè
|
||||||
|
std::vector<Triangle> subdivideTriangles(const std::vector<Triangle>& inputTriangles, float noiseCoeff);
|
||||||
|
LodLevel trianglesToVertices(const std::vector<Triangle>& triangles);
|
||||||
|
LodLevel generateSphere(int subdivisions, float noiseCoeff);
|
||||||
|
|
||||||
|
// Âñïîìîãàòåëüíûå ìåòîäû ìàòåìàòèêè
|
||||||
|
static float check_spherical_side(const Vector3f& V1, const Vector3f& V2, const Vector3f& P, const Vector3f& V3, float epsilon = 1e-6f);
|
||||||
|
static bool is_inside_spherical_triangle(const Vector3f& P, const Vector3f& V1, const Vector3f& V2, const Vector3f& V3, float epsilon);
|
||||||
|
|
||||||
|
public:
|
||||||
|
PlanetData();
|
||||||
|
|
||||||
|
void init();
|
||||||
|
|
||||||
|
// Ìåòîäû äîñòóïà ê äàííûì (äëÿ ðåíäåðåðà)
|
||||||
|
const LodLevel& getLodLevel(int level) const;
|
||||||
|
const LodLevel& getLodLevelNoDist(int level) const;
|
||||||
|
const LodLevel& getAtmosphereLod() const;
|
||||||
|
int getCurrentLodIndex() const;
|
||||||
|
int getMaxLodIndex() const;
|
||||||
|
|
||||||
|
// Ëîãèêà
|
||||||
|
std::pair<float, float> calculateZRange(float distanceToSurface);
|
||||||
|
float distanceToPlanetSurface(const Vector3f& viewerPosition);
|
||||||
|
|
||||||
|
// Âîçâðàùàåò èíäåêñû òðåóãîëüíèêîâ, âèäèìûõ êàìåðîé
|
||||||
|
std::vector<int> getTrianglesUnderCamera(const Vector3f& viewerPosition);
|
||||||
|
|
||||||
|
std::vector<int> findNeighbors(int index, int lod);
|
||||||
|
|
||||||
|
static std::vector<int> find_sub_triangle_spherical(const Vector3f& a_orig, const Vector3f& b_orig, const Vector3f& c_orig, const Vector3f& px_orig);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ZL
|
||||||
570
PlanetObject.cpp
Normal file
570
PlanetObject.cpp
Normal file
@ -0,0 +1,570 @@
|
|||||||
|
#include "PlanetObject.h"
|
||||||
|
#include <random>
|
||||||
|
#include <cmath>
|
||||||
|
#include "OpenGlExtensions.h"
|
||||||
|
#include "Environment.h"
|
||||||
|
#include "StoneObject.h"
|
||||||
|
|
||||||
|
namespace ZL {
|
||||||
|
|
||||||
|
|
||||||
|
Matrix3f GetRotationForTriangle(const Triangle& tri) {
|
||||||
|
// Для треугольника №0:
|
||||||
|
// tri.data[0] = {0, 20000, 0} (A)
|
||||||
|
// tri.data[1] = {0, 0, 20000} (B)
|
||||||
|
// tri.data[2] = {20000, 0, 0} (C)
|
||||||
|
|
||||||
|
Vector3f vA = tri.data[0];
|
||||||
|
Vector3f vB = tri.data[1];
|
||||||
|
Vector3f vC = tri.data[2];
|
||||||
|
|
||||||
|
// 1. Вычисляем ось X (горизонталь).
|
||||||
|
// Нам нужна грань BC: от (0, 0, 20000) до (20000, 0, 0)
|
||||||
|
Vector3f x_axis = (vC - vB).normalized();
|
||||||
|
|
||||||
|
|
||||||
|
// 2. Вычисляем нормаль (ось Z).
|
||||||
|
// Порядок cross product (AB x AC) определит "лицевую" сторону.
|
||||||
|
Vector3f edge1 = vB - vA;
|
||||||
|
Vector3f edge2 = vC - vA;
|
||||||
|
Vector3f z_axis = edge1.cross(edge2).normalized();
|
||||||
|
|
||||||
|
// 3. Вычисляем ось Y (вертикаль).
|
||||||
|
// В ортонормированном базисе Y всегда перпендикулярна Z и X.
|
||||||
|
Vector3f y_axis = z_axis.cross(x_axis).normalized();
|
||||||
|
|
||||||
|
// 4. Формируем прямую матрицу поворота (Rotation/World Matrix).
|
||||||
|
Matrix3f rot;
|
||||||
|
|
||||||
|
// Столбец 0: Ось X
|
||||||
|
rot.m[0] = x_axis.v[0];
|
||||||
|
rot.m[3] = x_axis.v[1];
|
||||||
|
rot.m[6] = x_axis.v[2];
|
||||||
|
|
||||||
|
// Столбец 1: Ось Y
|
||||||
|
rot.m[1] = y_axis.v[0];
|
||||||
|
rot.m[4] = y_axis.v[1];
|
||||||
|
rot.m[7] = y_axis.v[2];
|
||||||
|
|
||||||
|
// Столбец 2: Ось Z
|
||||||
|
rot.m[2] = z_axis.v[0];
|
||||||
|
rot.m[5] = z_axis.v[1];
|
||||||
|
rot.m[8] = z_axis.v[2];
|
||||||
|
|
||||||
|
return rot;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlanetObject::PlanetObject()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlanetObject::init() {
|
||||||
|
// 1. Инициализируем данные (генерация мешей)
|
||||||
|
planetData.init();
|
||||||
|
|
||||||
|
// 2. Забираем данные для VBO
|
||||||
|
// Берем максимальный LOD для начальной отрисовки
|
||||||
|
int lodIndex = planetData.getMaxLodIndex();
|
||||||
|
planetRenderStruct.data = planetData.getLodLevel(lodIndex).vertexData;
|
||||||
|
planetRenderStruct.RefreshVBO();
|
||||||
|
|
||||||
|
planetRenderStructCut.data = planetData.getLodLevelNoDist(lodIndex).vertexData;
|
||||||
|
|
||||||
|
planetRenderStructCut.data.PositionData.resize(3);
|
||||||
|
|
||||||
|
planetRenderStructCut.RefreshVBO();
|
||||||
|
//sandTexture = std::make_unique<Texture>(CreateTextureDataFromPng("./resources/sand2.png", ""));
|
||||||
|
sandTexture = std::make_unique<Texture>(CreateTextureDataFromPng("./resources/sand2.png", ""));
|
||||||
|
stoneTexture = std::make_unique<Texture>(CreateTextureDataFromPng("./resources/rock.png", ""));
|
||||||
|
|
||||||
|
// Атмосфера
|
||||||
|
planetAtmosphereRenderStruct.data = planetData.getAtmosphereLod().vertexData;
|
||||||
|
planetAtmosphereRenderStruct.RefreshVBO();
|
||||||
|
|
||||||
|
|
||||||
|
planetStones = CreateStoneGroupData(778, planetData.getLodLevel(lodIndex));
|
||||||
|
planetStones.inflate({ 0/*,1,2,3,4,5,6,7*/ });
|
||||||
|
planetStonesToBakeRenderStruct.AssignFrom(planetStones.mesh);
|
||||||
|
planetStonesToBakeRenderStruct.RefreshVBO();
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlanetObject::prepareDrawData() {
|
||||||
|
if (!drawDataDirty) return;
|
||||||
|
drawDataDirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void PlanetObject::update(float deltaTimeMs) {
|
||||||
|
// 1. Получаем базовые треугольники под камерой
|
||||||
|
auto lr = planetData.getTrianglesUnderCamera(Environment::shipPosition);
|
||||||
|
int currentLod = planetData.getCurrentLodIndex();
|
||||||
|
|
||||||
|
// Временный вектор для сбора новых индексов
|
||||||
|
std::vector<int> newIndices;
|
||||||
|
std::set<int> used;
|
||||||
|
|
||||||
|
// Рекурсивно (или итеративно, как у тебя) собираем индексы видимых зон
|
||||||
|
for (int i : lr) {
|
||||||
|
if (used.insert(i).second) newIndices.push_back(i);
|
||||||
|
|
||||||
|
auto neighbors = planetData.findNeighbors(i, currentLod);
|
||||||
|
for (int n : neighbors) {
|
||||||
|
if (used.insert(n).second) newIndices.push_back(n);
|
||||||
|
|
||||||
|
auto neighbors2 = planetData.findNeighbors(n, currentLod);
|
||||||
|
for (int n2 : neighbors2) {
|
||||||
|
if (used.insert(n2).second) newIndices.push_back(n2);
|
||||||
|
|
||||||
|
auto neighbors3 = planetData.findNeighbors(n, currentLod);
|
||||||
|
|
||||||
|
for (int n3 : neighbors3) {
|
||||||
|
if (used.insert(n3).second) newIndices.push_back(n3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Сортируем новый список, чтобы порядок не влиял на сравнение
|
||||||
|
std::sort(newIndices.begin(), newIndices.end());
|
||||||
|
|
||||||
|
// 3. Сравниваем с тем, что было нарисовано в прошлый раз
|
||||||
|
if (newIndices != triangleIndicesToDraw) {
|
||||||
|
// Обновляем список индексов (используем move для эффективности)
|
||||||
|
triangleIndicesToDraw = std::move(newIndices);
|
||||||
|
|
||||||
|
// --- ОБНОВЛЯЕМ ЖЕЛТУЮ ЗОНУ (только когда изменился состав треугольников) ---
|
||||||
|
const auto& fullMesh = planetData.getLodLevel(currentLod).vertexData;
|
||||||
|
planetRenderYellowStruct.data.PositionData.clear();
|
||||||
|
planetRenderYellowStruct.data.TexCoordData.clear();
|
||||||
|
|
||||||
|
for (int i : triangleIndicesToDraw) {
|
||||||
|
// Копируем геометрию для подсветки
|
||||||
|
for (int j = 0; j < 3; ++j) {
|
||||||
|
planetRenderYellowStruct.data.PositionData.push_back(fullMesh.PositionData[i * 3 + j]);
|
||||||
|
planetRenderYellowStruct.data.TexCoordData.push_back(fullMesh.TexCoordData[i * 3 + j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (planetRenderYellowStruct.data.PositionData.size() > 0)
|
||||||
|
{
|
||||||
|
planetRenderYellowStruct.RefreshVBO();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- ОБНОВЛЯЕМ КАМНИ (через новую структуру StoneGroup) ---
|
||||||
|
if (triangleIndicesToDraw.size() > 0)
|
||||||
|
{
|
||||||
|
planetStones.inflate(triangleIndicesToDraw);
|
||||||
|
// Используем AssignFrom, он внутри сам вызывает RefreshVBO
|
||||||
|
planetStonesRenderStruct.AssignFrom(planetStones.mesh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void PlanetObject::bakeStoneTexture(Renderer& renderer) {
|
||||||
|
glViewport(0, 0, 512, 512);
|
||||||
|
glClearColor(224 / 255.f, 201 / 255.f, 167 / 255.f, 1.0f);
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||||
|
|
||||||
|
|
||||||
|
static const std::string defaultShaderName2 = "planetBake";
|
||||||
|
static const std::string vPositionName = "vPosition";
|
||||||
|
static const std::string vTexCoordName = "vTexCoord";
|
||||||
|
static const std::string textureUniformName = "Texture";
|
||||||
|
|
||||||
|
|
||||||
|
renderer.shaderManager.PushShader(defaultShaderName2);
|
||||||
|
renderer.RenderUniform1i(textureUniformName, 0);
|
||||||
|
renderer.EnableVertexAttribArray(vPositionName);
|
||||||
|
renderer.EnableVertexAttribArray(vTexCoordName);
|
||||||
|
|
||||||
|
float dist = planetData.distanceToPlanetSurface(Environment::shipPosition);
|
||||||
|
auto zRange = planetData.calculateZRange(dist);
|
||||||
|
const float currentZNear = zRange.first;
|
||||||
|
const float currentZFar = zRange.second;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Triangle tr = planetData.getLodLevelNoDist(planetData.getCurrentLodIndex()).triangles[0];
|
||||||
|
|
||||||
|
// 1. Получаем матрицу вращения (оси в столбцах)
|
||||||
|
Matrix3f mr = GetRotationForTriangle(tr);
|
||||||
|
|
||||||
|
// 2. Трансформируем вершины в локальное пространство экрана, чтобы найти габариты
|
||||||
|
// Используем MultMatrixVector(Matrix, Vector).
|
||||||
|
// Если ваша функция считает V * M, то передайте Inverse(mr).
|
||||||
|
Vector3f rA = MultMatrixVector(mr, tr.data[0]);
|
||||||
|
Vector3f rB = MultMatrixVector(mr, tr.data[1]);
|
||||||
|
Vector3f rC = MultMatrixVector(mr, tr.data[2]);
|
||||||
|
|
||||||
|
// 3. Вычисляем реальные границы треугольника после поворота
|
||||||
|
float minX = min(rA.v[0], min(rB.v[0], rC.v[0]));
|
||||||
|
float maxX = max(rA.v[0], max(rB.v[0], rC.v[0]));
|
||||||
|
float minY = min(rA.v[1], min(rB.v[1], rC.v[1]));
|
||||||
|
float maxY = max(rA.v[1], max(rB.v[1], rC.v[1]));
|
||||||
|
|
||||||
|
|
||||||
|
// Находим центр и размеры
|
||||||
|
float width = maxX - minX;
|
||||||
|
float height = maxY - minY;
|
||||||
|
float centerX = (minX + maxX) * 0.5f;
|
||||||
|
float centerY = (minY + maxY) * 0.5f;
|
||||||
|
|
||||||
|
width = width * 0.995;
|
||||||
|
height = height * 0.995;
|
||||||
|
|
||||||
|
renderer.PushProjectionMatrix(
|
||||||
|
centerX - width*0.5, centerX + width * 0.5,
|
||||||
|
centerY - height * 0.5, centerY + height * 0.5,
|
||||||
|
150, 200000);
|
||||||
|
|
||||||
|
renderer.PushMatrix();
|
||||||
|
renderer.LoadIdentity();
|
||||||
|
// Сдвигаем камеру по Z
|
||||||
|
renderer.TranslateMatrix(Vector3f{ 0, 0, -45000 });
|
||||||
|
|
||||||
|
// Применяем вращение
|
||||||
|
renderer.RotateMatrix(mr);
|
||||||
|
|
||||||
|
|
||||||
|
// Извлекаем нормаль треугольника (это 3-й столбец нашей матрицы вращения)
|
||||||
|
Vector3f planeNormal = { mr.m[2], mr.m[5], mr.m[8] };
|
||||||
|
|
||||||
|
renderer.RenderUniform3fv("uPlanePoint", &tr.data[0].v[0]);
|
||||||
|
renderer.RenderUniform3fv("uPlaneNormal", &planeNormal.v[0]);
|
||||||
|
renderer.RenderUniform1f("uMaxHeight", StoneParams::BASE_SCALE * 1.1f); // Соответствует BASE_SCALE + perturbation
|
||||||
|
|
||||||
|
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, sandTexture->getTexID());
|
||||||
|
renderer.PushMatrix();
|
||||||
|
|
||||||
|
renderer.DrawVertexRenderStruct(planetRenderStructCut);
|
||||||
|
renderer.PopMatrix();
|
||||||
|
|
||||||
|
glEnable(GL_CULL_FACE);
|
||||||
|
glCullFace(GL_BACK); // Отсекаем задние грани
|
||||||
|
if (planetStonesToBakeRenderStruct.data.PositionData.size() > 0)
|
||||||
|
{
|
||||||
|
|
||||||
|
glBindTexture(GL_TEXTURE_2D, stoneTexture->getTexID());
|
||||||
|
renderer.DrawVertexRenderStruct(planetStonesToBakeRenderStruct);
|
||||||
|
CheckGlError();
|
||||||
|
}
|
||||||
|
glDisable(GL_CULL_FACE); // Не забываем выключить, чтобы не сломать остальной рендер
|
||||||
|
renderer.PopMatrix();
|
||||||
|
renderer.PopProjectionMatrix();
|
||||||
|
renderer.DisableVertexAttribArray(vTexCoordName);
|
||||||
|
renderer.DisableVertexAttribArray(vPositionName);
|
||||||
|
renderer.shaderManager.PopShader();
|
||||||
|
CheckGlError();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void PlanetObject::draw(Renderer& renderer) {
|
||||||
|
|
||||||
|
prepareDrawData();
|
||||||
|
|
||||||
|
{
|
||||||
|
if (stoneMapFB == nullptr)
|
||||||
|
{
|
||||||
|
stoneMapFB = std::make_unique<FrameBuffer>(512, 512);
|
||||||
|
}
|
||||||
|
stoneMapFB->Bind();
|
||||||
|
|
||||||
|
bakeStoneTexture(renderer);
|
||||||
|
|
||||||
|
stoneMapFB->Unbind();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//bakeStoneTexture(renderer);
|
||||||
|
|
||||||
|
glViewport(0, 0, Environment::width, Environment::height);
|
||||||
|
//--------------------------
|
||||||
|
|
||||||
|
|
||||||
|
drawPlanet(renderer);
|
||||||
|
//drawYellowZone(renderer);
|
||||||
|
drawStones(renderer);
|
||||||
|
|
||||||
|
drawAtmosphere(renderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlanetObject::drawPlanet(Renderer& renderer)
|
||||||
|
{
|
||||||
|
static const std::string defaultShaderName = "planetLand";
|
||||||
|
|
||||||
|
static const std::string vPositionName = "vPosition";
|
||||||
|
static const std::string vColorName = "vColor";
|
||||||
|
static const std::string vNormalName = "vNormal";
|
||||||
|
static const std::string vTexCoordName = "vTexCoord";
|
||||||
|
static const std::string textureUniformName = "Texture";
|
||||||
|
|
||||||
|
renderer.shaderManager.PushShader(defaultShaderName);
|
||||||
|
|
||||||
|
renderer.EnableVertexAttribArray(vPositionName);
|
||||||
|
renderer.EnableVertexAttribArray(vColorName);
|
||||||
|
renderer.EnableVertexAttribArray(vNormalName);
|
||||||
|
renderer.EnableVertexAttribArray("vTangent");
|
||||||
|
renderer.EnableVertexAttribArray("vBinormal");
|
||||||
|
renderer.EnableVertexAttribArray(vTexCoordName);
|
||||||
|
|
||||||
|
float dist = planetData.distanceToPlanetSurface(Environment::shipPosition);
|
||||||
|
auto zRange = planetData.calculateZRange(dist);
|
||||||
|
const float currentZNear = zRange.first;
|
||||||
|
const float currentZFar = zRange.second;
|
||||||
|
|
||||||
|
// 2. Применяем динамическую матрицу проекции
|
||||||
|
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
|
||||||
|
static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
|
||||||
|
currentZNear, currentZFar);
|
||||||
|
|
||||||
|
renderer.PushMatrix();
|
||||||
|
renderer.LoadIdentity();
|
||||||
|
renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
|
||||||
|
renderer.RotateMatrix(Environment::inverseShipMatrix);
|
||||||
|
|
||||||
|
renderer.TranslateMatrix(-Environment::shipPosition);
|
||||||
|
|
||||||
|
const Matrix4f viewMatrix = renderer.GetCurrentModelViewMatrix();
|
||||||
|
|
||||||
|
renderer.RenderUniform1i("Texture", 0);
|
||||||
|
renderer.RenderUniform1i("BakedTexture", 1);
|
||||||
|
|
||||||
|
|
||||||
|
Triangle tr = planetData.getLodLevel(planetData.getCurrentLodIndex()).triangles[0]; // Берем базовый треугольник
|
||||||
|
Matrix3f mr = GetRotationForTriangle(tr); // Та же матрица, что и при запекании
|
||||||
|
|
||||||
|
// Позиция камеры (корабля) в мире
|
||||||
|
renderer.RenderUniform3fv("uViewPos", &Environment::shipPosition.v[0]);
|
||||||
|
|
||||||
|
//renderer.RenderUniform1f("uHeightScale", 0.03f);
|
||||||
|
renderer.RenderUniform1f("uHeightScale", 0.0f);
|
||||||
|
|
||||||
|
renderer.RenderUniform1f("uDistanceToPlanetSurface", dist);
|
||||||
|
renderer.RenderUniform1f("uCurrentZFar", currentZFar);
|
||||||
|
|
||||||
|
|
||||||
|
glActiveTexture(GL_TEXTURE1);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, stoneMapFB->getTextureID());
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, sandTexture->getTexID());
|
||||||
|
|
||||||
|
renderer.DrawVertexRenderStruct(planetRenderStruct);
|
||||||
|
CheckGlError();
|
||||||
|
|
||||||
|
renderer.PopMatrix();
|
||||||
|
renderer.PopProjectionMatrix();
|
||||||
|
renderer.DisableVertexAttribArray(vTexCoordName);
|
||||||
|
renderer.DisableVertexAttribArray(vNormalName);
|
||||||
|
renderer.DisableVertexAttribArray("vTangent");
|
||||||
|
renderer.DisableVertexAttribArray("vBinormal");
|
||||||
|
renderer.DisableVertexAttribArray(vColorName);
|
||||||
|
renderer.DisableVertexAttribArray(vPositionName);
|
||||||
|
renderer.shaderManager.PopShader();
|
||||||
|
CheckGlError();
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlanetObject::drawStones(Renderer& renderer)
|
||||||
|
{
|
||||||
|
static const std::string defaultShaderName2 = "planetStone";
|
||||||
|
static const std::string vPositionName = "vPosition";
|
||||||
|
static const std::string vColorName = "vColor";
|
||||||
|
static const std::string vNormalName = "vNormal";
|
||||||
|
static const std::string vTexCoordName = "vTexCoord";
|
||||||
|
static const std::string textureUniformName = "Texture";
|
||||||
|
|
||||||
|
renderer.shaderManager.PushShader(defaultShaderName2);
|
||||||
|
renderer.RenderUniform1i(textureUniformName, 0);
|
||||||
|
renderer.EnableVertexAttribArray(vPositionName);
|
||||||
|
renderer.EnableVertexAttribArray(vColorName);
|
||||||
|
renderer.EnableVertexAttribArray(vNormalName);
|
||||||
|
renderer.EnableVertexAttribArray(vTexCoordName);
|
||||||
|
|
||||||
|
float dist = planetData.distanceToPlanetSurface(Environment::shipPosition);
|
||||||
|
auto zRange = planetData.calculateZRange(dist);
|
||||||
|
const float currentZNear = zRange.first;
|
||||||
|
const float currentZFar = zRange.second;
|
||||||
|
|
||||||
|
// 2. Применяем динамическую матрицу проекции
|
||||||
|
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
|
||||||
|
static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
|
||||||
|
currentZNear, currentZFar);
|
||||||
|
|
||||||
|
renderer.PushMatrix();
|
||||||
|
renderer.LoadIdentity();
|
||||||
|
renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
|
||||||
|
renderer.RotateMatrix(Environment::inverseShipMatrix);
|
||||||
|
renderer.TranslateMatrix(-Environment::shipPosition);
|
||||||
|
|
||||||
|
|
||||||
|
renderer.RenderUniform1f("uDistanceToPlanetSurface", dist);
|
||||||
|
renderer.RenderUniform1f("uCurrentZFar", currentZFar);
|
||||||
|
renderer.RenderUniform3fv("uViewPos", &Environment::shipPosition.v[0]);
|
||||||
|
|
||||||
|
glEnable(GL_CULL_FACE);
|
||||||
|
glCullFace(GL_BACK);
|
||||||
|
glEnable(GL_BLEND);
|
||||||
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
|
|
||||||
|
if (planetStonesRenderStruct.data.PositionData.size() > 0)
|
||||||
|
{
|
||||||
|
glBindTexture(GL_TEXTURE_2D, stoneTexture->getTexID());
|
||||||
|
renderer.DrawVertexRenderStruct(planetStonesRenderStruct);
|
||||||
|
CheckGlError();
|
||||||
|
}
|
||||||
|
glDisable(GL_BLEND);
|
||||||
|
glDisable(GL_CULL_FACE);
|
||||||
|
|
||||||
|
renderer.PopMatrix();
|
||||||
|
renderer.PopProjectionMatrix();
|
||||||
|
renderer.DisableVertexAttribArray(vTexCoordName);
|
||||||
|
renderer.DisableVertexAttribArray(vNormalName);
|
||||||
|
renderer.DisableVertexAttribArray(vColorName);
|
||||||
|
renderer.DisableVertexAttribArray(vPositionName);
|
||||||
|
renderer.shaderManager.PopShader();
|
||||||
|
CheckGlError();
|
||||||
|
|
||||||
|
glClear(GL_DEPTH_BUFFER_BIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlanetObject::drawYellowZone(Renderer& renderer)
|
||||||
|
{
|
||||||
|
|
||||||
|
static const std::string defaultShaderName = "planetLand";
|
||||||
|
static const std::string vPositionName = "vPosition";
|
||||||
|
static const std::string vColorName = "vColor";
|
||||||
|
static const std::string vNormalName = "vNormal";
|
||||||
|
static const std::string vTexCoordName = "vTexCoord";
|
||||||
|
static const std::string textureUniformName = "Texture";
|
||||||
|
|
||||||
|
float dist = planetData.distanceToPlanetSurface(Environment::shipPosition);
|
||||||
|
auto zRange = planetData.calculateZRange(dist);
|
||||||
|
const float currentZNear = zRange.first;
|
||||||
|
const float currentZFar = zRange.second;
|
||||||
|
|
||||||
|
|
||||||
|
glClear(GL_DEPTH_BUFFER_BIT);
|
||||||
|
if (planetRenderYellowStruct.data.PositionData.size() > 0)
|
||||||
|
{
|
||||||
|
|
||||||
|
renderer.shaderManager.PushShader(defaultShaderName);
|
||||||
|
renderer.RenderUniform1i(textureUniformName, 0);
|
||||||
|
renderer.EnableVertexAttribArray(vPositionName);
|
||||||
|
renderer.EnableVertexAttribArray(vColorName);
|
||||||
|
renderer.EnableVertexAttribArray(vNormalName);
|
||||||
|
renderer.EnableVertexAttribArray("vTangent");
|
||||||
|
renderer.EnableVertexAttribArray("vBinormal");
|
||||||
|
renderer.EnableVertexAttribArray(vTexCoordName);
|
||||||
|
|
||||||
|
// 2. Применяем динамическую матрицу проекции
|
||||||
|
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
|
||||||
|
static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
|
||||||
|
currentZNear, currentZFar);
|
||||||
|
|
||||||
|
renderer.PushMatrix();
|
||||||
|
renderer.LoadIdentity();
|
||||||
|
renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
|
||||||
|
renderer.RotateMatrix(Environment::inverseShipMatrix);
|
||||||
|
|
||||||
|
renderer.TranslateMatrix(-Environment::shipPosition);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
renderer.RenderUniform1f("uDistanceToPlanetSurface", dist);
|
||||||
|
renderer.RenderUniform1f("uCurrentZFar", currentZFar);
|
||||||
|
|
||||||
|
renderer.RenderUniform3fv("uViewPos", &Environment::shipPosition.v[0]);
|
||||||
|
|
||||||
|
Vector3f color2 = { 1.0, 1.0, 0.0 };
|
||||||
|
|
||||||
|
glBindTexture(GL_TEXTURE_2D, sandTexture->getTexID());
|
||||||
|
|
||||||
|
renderer.RenderUniform3fv("uColor", &color2.v[0]);
|
||||||
|
renderer.DrawVertexRenderStruct(planetRenderYellowStruct);
|
||||||
|
//glDisable(GL_BLEND);
|
||||||
|
CheckGlError();
|
||||||
|
|
||||||
|
|
||||||
|
renderer.PopMatrix();
|
||||||
|
renderer.PopProjectionMatrix();
|
||||||
|
renderer.DisableVertexAttribArray(vTexCoordName);
|
||||||
|
renderer.DisableVertexAttribArray(vNormalName);
|
||||||
|
renderer.DisableVertexAttribArray("vTangent");
|
||||||
|
renderer.DisableVertexAttribArray("vBinormal");
|
||||||
|
renderer.DisableVertexAttribArray(vColorName);
|
||||||
|
renderer.DisableVertexAttribArray(vPositionName);
|
||||||
|
renderer.shaderManager.PopShader();
|
||||||
|
CheckGlError();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlanetObject::drawAtmosphere(Renderer& renderer) {
|
||||||
|
static const std::string defaultShaderName = "defaultAtmosphere";
|
||||||
|
static const std::string vPositionName = "vPosition";
|
||||||
|
static const std::string vNormalName = "vNormal";
|
||||||
|
//glClear(GL_DEPTH_BUFFER_BIT);
|
||||||
|
glDepthMask(GL_FALSE);
|
||||||
|
|
||||||
|
renderer.shaderManager.PushShader(defaultShaderName);
|
||||||
|
renderer.EnableVertexAttribArray(vPositionName);
|
||||||
|
renderer.EnableVertexAttribArray(vNormalName);
|
||||||
|
|
||||||
|
|
||||||
|
float dist = planetData.distanceToPlanetSurface(Environment::shipPosition);
|
||||||
|
auto zRange = planetData.calculateZRange(dist);
|
||||||
|
const float currentZNear = zRange.first;
|
||||||
|
const float currentZFar = zRange.second;
|
||||||
|
|
||||||
|
// 2. Применяем динамическую матрицу проекции
|
||||||
|
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
|
||||||
|
static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
|
||||||
|
currentZNear, currentZFar);
|
||||||
|
|
||||||
|
renderer.PushMatrix();
|
||||||
|
renderer.LoadIdentity();
|
||||||
|
renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
|
||||||
|
renderer.RotateMatrix(Environment::inverseShipMatrix);
|
||||||
|
renderer.TranslateMatrix(-Environment::shipPosition);
|
||||||
|
|
||||||
|
const Matrix4f viewMatrix = renderer.GetCurrentModelViewMatrix();
|
||||||
|
|
||||||
|
Vector3f color = { 0.0, 0.5, 1.0 };
|
||||||
|
|
||||||
|
renderer.RenderUniform3fv("uColor", &color.v[0]);
|
||||||
|
|
||||||
|
renderer.RenderUniformMatrix4fv("ModelViewMatrix", false, &viewMatrix.m[0]);
|
||||||
|
|
||||||
|
renderer.RenderUniform1f("uDistanceToPlanetSurface", dist);
|
||||||
|
|
||||||
|
glEnable(GL_BLEND);
|
||||||
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE);// Аддитивное смешивание для эффекта свечения
|
||||||
|
|
||||||
|
renderer.DrawVertexRenderStruct(planetAtmosphereRenderStruct);
|
||||||
|
glDisable(GL_BLEND);
|
||||||
|
glDepthMask(GL_TRUE);
|
||||||
|
renderer.PopMatrix();
|
||||||
|
renderer.PopProjectionMatrix();
|
||||||
|
|
||||||
|
renderer.DisableVertexAttribArray(vNormalName);
|
||||||
|
renderer.DisableVertexAttribArray(vPositionName);
|
||||||
|
renderer.shaderManager.PopShader();
|
||||||
|
CheckGlError();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
float PlanetObject::distanceToPlanetSurface(const Vector3f& viewerPosition)
|
||||||
|
{
|
||||||
|
return planetData.distanceToPlanetSurface(viewerPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace ZL
|
||||||
63
PlanetObject.h
Normal file
63
PlanetObject.h
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ZLMath.h"
|
||||||
|
#include "Renderer.h"
|
||||||
|
#include "TextureManager.h"
|
||||||
|
#include <vector>
|
||||||
|
#include <chrono>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <array>
|
||||||
|
#include <numeric>
|
||||||
|
#include <random>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <map>
|
||||||
|
#include <set>
|
||||||
|
#include "Perlin.h"
|
||||||
|
#include "PlanetData.h"
|
||||||
|
#include "StoneObject.h"
|
||||||
|
#include "FrameBuffer.h"
|
||||||
|
|
||||||
|
namespace ZL {
|
||||||
|
|
||||||
|
class PlanetObject {
|
||||||
|
public:
|
||||||
|
// Агрегация: логика и данные теперь здесь
|
||||||
|
PlanetData planetData;
|
||||||
|
|
||||||
|
// Данные только для рендеринга (OpenGL specific)
|
||||||
|
VertexRenderStruct planetRenderStruct;
|
||||||
|
VertexRenderStruct planetRenderStructCut;
|
||||||
|
VertexRenderStruct planetRenderYellowStruct;
|
||||||
|
VertexRenderStruct planetAtmosphereRenderStruct;
|
||||||
|
VertexRenderStruct planetStonesRenderStruct;
|
||||||
|
VertexRenderStruct planetStonesToBakeRenderStruct;
|
||||||
|
StoneGroup planetStones;
|
||||||
|
|
||||||
|
std::vector<int> triangleIndicesToDraw;
|
||||||
|
|
||||||
|
std::shared_ptr<Texture> sandTexture;
|
||||||
|
std::shared_ptr<Texture> stoneTexture;
|
||||||
|
|
||||||
|
bool drawDataDirty = true;
|
||||||
|
void prepareDrawData();
|
||||||
|
|
||||||
|
std::unique_ptr<FrameBuffer> stoneMapFB;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PlanetObject();
|
||||||
|
|
||||||
|
void init();
|
||||||
|
void update(float deltaTimeMs);
|
||||||
|
void bakeStoneTexture(Renderer& renderer);
|
||||||
|
void draw(Renderer& renderer);
|
||||||
|
void drawStones(Renderer& renderer);
|
||||||
|
void drawPlanet(Renderer& renderer);
|
||||||
|
void drawYellowZone(Renderer& renderer);
|
||||||
|
void drawAtmosphere(Renderer& renderer);
|
||||||
|
|
||||||
|
|
||||||
|
float distanceToPlanetSurface(const Vector3f& viewerPosition);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ZL
|
||||||
@ -154,12 +154,6 @@ emcc main.cpp Game.cpp Environment.cpp BoneAnimatedModel.cpp ZLMath.cpp Renderer
|
|||||||
emrun --no_browser --port 8080 .
|
emrun --no_browser --port 8080 .
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
# Emscripten new
|
|
||||||
```
|
|
||||||
emcc src/main.cpp src/Game.cpp src/Environment.cpp src/BoneAnimatedModel.cpp src/TextModel.cpp src/Projectile.cpp src/SparkEmitter.cpp src/UiManager.cpp src/render/Renderer.cpp src/render/ShaderManager.cpp src/render/TextureManager.cpp src/render/FrameBuffer.cpp src/render/OpenGlExtensions.cpp src/utils/Utils.cpp src/utils/TaskManager.cpp src/utils/Perlin.cpp src/planet/PlanetData.cpp src/planet/PlanetObject.cpp src/planet/StoneObject.cpp -O2 -std=c++17 -pthread -sUSE_PTHREADS=1 -sPTHREAD_POOL_SIZE=4 -sTOTAL_MEMORY=4294967296 -sINITIAL_MEMORY=3221225472 -sMAXIMUM_MEMORY=4294967296 -sALLOW_MEMORY_GROWTH=1 -fexceptions -I./thirdparty1/eigen-5.0.0 -I./src -I./thirdparty/libzip-1.11.3/build-emcmake/install/include -IC:/Boost/include/boost-1_84 -L./thirdparty/libzip-1.11.3/build-emcmake/install/lib -lzip -lz -sUSE_SDL_IMAGE=2 -sUSE_SDL=2 -sUSE_LIBPNG=1 --preload-file space-game001.zip -o space-game001.html
|
|
||||||
```
|
|
||||||
|
|
||||||
# License
|
# License
|
||||||
Code: MIT
|
Code: MIT
|
||||||
|
|
||||||
|
|||||||
833
Renderer.cpp
Executable file
833
Renderer.cpp
Executable file
@ -0,0 +1,833 @@
|
|||||||
|
#include "Renderer.h"
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
namespace ZL {
|
||||||
|
|
||||||
|
VBOHolder::VBOHolder()
|
||||||
|
{
|
||||||
|
glGenBuffers(1, &Buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
VBOHolder::~VBOHolder()
|
||||||
|
{
|
||||||
|
glDeleteBuffers(1, &Buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
GLuint VBOHolder::getBuffer()
|
||||||
|
{
|
||||||
|
return Buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
VAOHolder::VAOHolder()
|
||||||
|
{
|
||||||
|
#ifndef EMSCRIPTEN
|
||||||
|
glGenVertexArrays(1, &vao);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
VAOHolder::~VAOHolder()
|
||||||
|
{
|
||||||
|
#ifndef EMSCRIPTEN
|
||||||
|
|
||||||
|
#ifdef __linux__
|
||||||
|
glDeleteVertexArrays(1, &vao);
|
||||||
|
#else
|
||||||
|
//Windows
|
||||||
|
glDeleteVertexArray(1, &vao);
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
GLuint VAOHolder::getBuffer()
|
||||||
|
{
|
||||||
|
return vao;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
VertexDataStruct CreateRect2D(Vector2f center, Vector2f halfWidthHeight, float zLevel)
|
||||||
|
{
|
||||||
|
Vector2f posFrom = center - halfWidthHeight;
|
||||||
|
|
||||||
|
Vector2f posTo = center + halfWidthHeight;
|
||||||
|
|
||||||
|
Vector3f pos1 = { posFrom.v[0], posFrom.v[1], zLevel };
|
||||||
|
Vector3f pos2 = { posFrom.v[0], posTo.v[1], zLevel };
|
||||||
|
Vector3f pos3 = { posTo.v[0], posTo.v[1], zLevel };
|
||||||
|
Vector3f pos4 = { posTo.v[0], posFrom.v[1], zLevel };
|
||||||
|
|
||||||
|
|
||||||
|
Vector2f texCoordPos1 = { 0.0f, 0.0f };
|
||||||
|
Vector2f texCoordPos2 = { 0.0f, 1.0f };
|
||||||
|
Vector2f texCoordPos3 = { 1.0f, 1.0f };
|
||||||
|
Vector2f texCoordPos4 = { 1.0f, 0.0f };
|
||||||
|
|
||||||
|
VertexDataStruct result;
|
||||||
|
|
||||||
|
result.PositionData.push_back(pos1);
|
||||||
|
result.PositionData.push_back(pos2);
|
||||||
|
result.PositionData.push_back(pos3);
|
||||||
|
result.PositionData.push_back(pos3);
|
||||||
|
result.PositionData.push_back(pos4);
|
||||||
|
result.PositionData.push_back(pos1);
|
||||||
|
|
||||||
|
result.TexCoordData.push_back(texCoordPos1);
|
||||||
|
result.TexCoordData.push_back(texCoordPos2);
|
||||||
|
result.TexCoordData.push_back(texCoordPos3);
|
||||||
|
result.TexCoordData.push_back(texCoordPos3);
|
||||||
|
result.TexCoordData.push_back(texCoordPos4);
|
||||||
|
result.TexCoordData.push_back(texCoordPos1);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
VertexDataStruct CreateRectHorizontalSections2D(Vector2f center, Vector2f halfWidthHeight, float zLevel, size_t sectionCount)
|
||||||
|
{
|
||||||
|
Vector2f posFrom = center - halfWidthHeight;
|
||||||
|
|
||||||
|
Vector2f posTo = center + halfWidthHeight;
|
||||||
|
|
||||||
|
float sectionWidth = halfWidthHeight.v[0] * 2.f;
|
||||||
|
|
||||||
|
VertexDataStruct result;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < sectionCount; i++)
|
||||||
|
{
|
||||||
|
Vector3f pos1 = { posFrom.v[0]+sectionWidth*i, posFrom.v[1], zLevel };
|
||||||
|
Vector3f pos2 = { posFrom.v[0] + sectionWidth * i, posTo.v[1], zLevel };
|
||||||
|
Vector3f pos3 = { posTo.v[0] + sectionWidth * i, posTo.v[1], zLevel };
|
||||||
|
Vector3f pos4 = { posTo.v[0] + sectionWidth * i, posFrom.v[1], zLevel };
|
||||||
|
|
||||||
|
result.PositionData.push_back(pos1);
|
||||||
|
result.PositionData.push_back(pos2);
|
||||||
|
result.PositionData.push_back(pos3);
|
||||||
|
result.PositionData.push_back(pos3);
|
||||||
|
result.PositionData.push_back(pos4);
|
||||||
|
result.PositionData.push_back(pos1);
|
||||||
|
|
||||||
|
Vector2f texCoordPos1 = { 0.0f, 0.0f };
|
||||||
|
Vector2f texCoordPos2 = { 0.0f, 1.0f };
|
||||||
|
Vector2f texCoordPos3 = { 1.0f, 1.0f };
|
||||||
|
Vector2f texCoordPos4 = { 1.0f, 0.0f };
|
||||||
|
|
||||||
|
result.TexCoordData.push_back(texCoordPos1);
|
||||||
|
result.TexCoordData.push_back(texCoordPos2);
|
||||||
|
result.TexCoordData.push_back(texCoordPos3);
|
||||||
|
result.TexCoordData.push_back(texCoordPos3);
|
||||||
|
result.TexCoordData.push_back(texCoordPos4);
|
||||||
|
result.TexCoordData.push_back(texCoordPos1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
VertexDataStruct CreateCube3D(float scale)
|
||||||
|
{
|
||||||
|
|
||||||
|
std::array<std::array<Vector3f, 4>, 6> cubeSides;
|
||||||
|
|
||||||
|
std::array<Vector3f, 6> cubeColors;
|
||||||
|
|
||||||
|
|
||||||
|
cubeSides[0][0] = { -1, -1, -1 };
|
||||||
|
cubeSides[0][1] = { -1, 1, -1 };
|
||||||
|
cubeSides[0][2] = { 1, 1, -1 };
|
||||||
|
cubeSides[0][3] = { 1, -1, -1 };
|
||||||
|
|
||||||
|
cubeSides[1][0] = { -1, -1, 1 };
|
||||||
|
cubeSides[1][1] = { -1, 1, 1 };
|
||||||
|
cubeSides[1][2] = { 1, 1, 1 };
|
||||||
|
cubeSides[1][3] = { 1, -1, 1 };
|
||||||
|
|
||||||
|
//------------
|
||||||
|
|
||||||
|
cubeSides[2][0] = { -1, -1, -1 };
|
||||||
|
cubeSides[2][1] = { -1, -1, 1 };
|
||||||
|
cubeSides[2][2] = { 1, -1, 1 };
|
||||||
|
cubeSides[2][3] = { 1, -1, -1 };
|
||||||
|
|
||||||
|
cubeSides[3][0] = { -1, 1, -1 };
|
||||||
|
cubeSides[3][1] = { -1, 1, 1 };
|
||||||
|
cubeSides[3][2] = { 1, 1, 1 };
|
||||||
|
cubeSides[3][3] = { 1, 1, -1 };
|
||||||
|
|
||||||
|
//------------
|
||||||
|
cubeSides[4][0] = { -1, -1, -1 };
|
||||||
|
cubeSides[4][1] = { -1, -1, 1 };
|
||||||
|
cubeSides[4][2] = { -1, 1, 1 };
|
||||||
|
cubeSides[4][3] = { -1, 1, -1 };
|
||||||
|
|
||||||
|
cubeSides[5][0] = { 1, -1, -1 };
|
||||||
|
cubeSides[5][1] = { 1, -1, 1 };
|
||||||
|
cubeSides[5][2] = { 1, 1, 1 };
|
||||||
|
cubeSides[5][3] = { 1, 1, -1 };
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
cubeColors[0] = Vector3f{ 1, 0, 0 };
|
||||||
|
cubeColors[1] = Vector3f{ 0, 1, 0 };
|
||||||
|
cubeColors[2] = Vector3f{ 0, 0, 1 };
|
||||||
|
cubeColors[3] = Vector3f{ 1, 1, 0 };
|
||||||
|
cubeColors[4] = Vector3f{ 0, 1, 1 };
|
||||||
|
cubeColors[5] = Vector3f{ 1, 0, 1 };
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
VertexDataStruct result;
|
||||||
|
|
||||||
|
for (int i = 0; i < 6; i++)
|
||||||
|
{
|
||||||
|
result.PositionData.push_back(cubeSides[i][0] * scale);
|
||||||
|
result.PositionData.push_back(cubeSides[i][1] * scale);
|
||||||
|
result.PositionData.push_back(cubeSides[i][2] * scale);
|
||||||
|
result.PositionData.push_back(cubeSides[i][2] * scale);
|
||||||
|
result.PositionData.push_back(cubeSides[i][3] * scale);
|
||||||
|
result.PositionData.push_back(cubeSides[i][0] * scale);
|
||||||
|
|
||||||
|
result.ColorData.push_back(cubeColors[i]);
|
||||||
|
result.ColorData.push_back(cubeColors[i]);
|
||||||
|
result.ColorData.push_back(cubeColors[i]);
|
||||||
|
result.ColorData.push_back(cubeColors[i]);
|
||||||
|
result.ColorData.push_back(cubeColors[i]);
|
||||||
|
result.ColorData.push_back(cubeColors[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
VertexDataStruct CreateCubemap(float scale)
|
||||||
|
{
|
||||||
|
VertexDataStruct cubemapVertexDataStruct;
|
||||||
|
|
||||||
|
// +x
|
||||||
|
cubemapVertexDataStruct.PositionData.push_back({ scale, -scale, -scale });
|
||||||
|
cubemapVertexDataStruct.PositionData.push_back({ scale, scale, -scale });
|
||||||
|
cubemapVertexDataStruct.PositionData.push_back({ scale, scale, scale });
|
||||||
|
|
||||||
|
cubemapVertexDataStruct.PositionData.push_back({ scale, -scale, -scale });
|
||||||
|
cubemapVertexDataStruct.PositionData.push_back({ scale, scale, scale });
|
||||||
|
cubemapVertexDataStruct.PositionData.push_back({ scale, -scale, scale });
|
||||||
|
|
||||||
|
// -x
|
||||||
|
cubemapVertexDataStruct.PositionData.push_back({ -scale, -scale, -scale });
|
||||||
|
cubemapVertexDataStruct.PositionData.push_back({ -scale, scale, -scale });
|
||||||
|
cubemapVertexDataStruct.PositionData.push_back({ -scale, scale, scale });
|
||||||
|
|
||||||
|
cubemapVertexDataStruct.PositionData.push_back({ -scale, -scale, -scale });
|
||||||
|
cubemapVertexDataStruct.PositionData.push_back({ -scale, scale, scale });
|
||||||
|
cubemapVertexDataStruct.PositionData.push_back({ -scale, -scale, scale });
|
||||||
|
|
||||||
|
|
||||||
|
// +y
|
||||||
|
cubemapVertexDataStruct.PositionData.push_back({ -scale, scale, -scale });
|
||||||
|
cubemapVertexDataStruct.PositionData.push_back({ scale, scale, -scale });
|
||||||
|
cubemapVertexDataStruct.PositionData.push_back({ scale, scale, scale });
|
||||||
|
|
||||||
|
cubemapVertexDataStruct.PositionData.push_back({ -scale, scale, -scale });
|
||||||
|
cubemapVertexDataStruct.PositionData.push_back({ scale, scale, scale });
|
||||||
|
cubemapVertexDataStruct.PositionData.push_back({ -scale, scale, scale });
|
||||||
|
|
||||||
|
|
||||||
|
// -y
|
||||||
|
cubemapVertexDataStruct.PositionData.push_back({ -scale, -scale, -scale });
|
||||||
|
cubemapVertexDataStruct.PositionData.push_back({ scale, -scale, -scale });
|
||||||
|
cubemapVertexDataStruct.PositionData.push_back({ scale, -scale, scale });
|
||||||
|
|
||||||
|
cubemapVertexDataStruct.PositionData.push_back({ -scale, -scale, -scale });
|
||||||
|
cubemapVertexDataStruct.PositionData.push_back({ scale, -scale, scale });
|
||||||
|
cubemapVertexDataStruct.PositionData.push_back({ -scale, -scale, scale });
|
||||||
|
|
||||||
|
|
||||||
|
// +z
|
||||||
|
cubemapVertexDataStruct.PositionData.push_back({ -scale, -scale, scale });
|
||||||
|
cubemapVertexDataStruct.PositionData.push_back({ scale, -scale, scale });
|
||||||
|
cubemapVertexDataStruct.PositionData.push_back({ scale, scale, scale });
|
||||||
|
|
||||||
|
cubemapVertexDataStruct.PositionData.push_back({ -scale, -scale, scale });
|
||||||
|
cubemapVertexDataStruct.PositionData.push_back({ scale, scale, scale });
|
||||||
|
cubemapVertexDataStruct.PositionData.push_back({ -scale, scale, scale });
|
||||||
|
|
||||||
|
// -z
|
||||||
|
cubemapVertexDataStruct.PositionData.push_back({ -scale, -scale, -scale });
|
||||||
|
cubemapVertexDataStruct.PositionData.push_back({ scale, -scale, -scale });
|
||||||
|
cubemapVertexDataStruct.PositionData.push_back({ scale, scale, -scale });
|
||||||
|
|
||||||
|
cubemapVertexDataStruct.PositionData.push_back({ -scale, -scale, -scale });
|
||||||
|
cubemapVertexDataStruct.PositionData.push_back({ scale, scale, -scale });
|
||||||
|
cubemapVertexDataStruct.PositionData.push_back({ -scale, scale, -scale });
|
||||||
|
|
||||||
|
return cubemapVertexDataStruct;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void VertexRenderStruct::RefreshVBO()
|
||||||
|
{
|
||||||
|
//Check if main thread, check if data is not empty...
|
||||||
|
|
||||||
|
#ifndef EMSCRIPTEN
|
||||||
|
if (!vao)
|
||||||
|
{
|
||||||
|
vao = std::make_shared<VAOHolder>();
|
||||||
|
}
|
||||||
|
|
||||||
|
glBindVertexArray(vao->getBuffer());
|
||||||
|
#endif
|
||||||
|
if (!positionVBO)
|
||||||
|
{
|
||||||
|
positionVBO = std::make_shared<VBOHolder>();
|
||||||
|
}
|
||||||
|
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, positionVBO->getBuffer());
|
||||||
|
|
||||||
|
glBufferData(GL_ARRAY_BUFFER, data.PositionData.size() * 12, &data.PositionData[0], GL_STATIC_DRAW);
|
||||||
|
|
||||||
|
if (data.TexCoordData.size() > 0)
|
||||||
|
{
|
||||||
|
if (!texCoordVBO)
|
||||||
|
{
|
||||||
|
texCoordVBO = std::make_shared<VBOHolder>();
|
||||||
|
}
|
||||||
|
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, texCoordVBO->getBuffer());
|
||||||
|
|
||||||
|
glBufferData(GL_ARRAY_BUFFER, data.TexCoordData.size() * 8, &data.TexCoordData[0], GL_STATIC_DRAW);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.NormalData.size() > 0)
|
||||||
|
{
|
||||||
|
if (!normalVBO)
|
||||||
|
{
|
||||||
|
normalVBO = std::make_shared<VBOHolder>();
|
||||||
|
}
|
||||||
|
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, normalVBO->getBuffer());
|
||||||
|
|
||||||
|
glBufferData(GL_ARRAY_BUFFER, data.NormalData.size() * 12, &data.NormalData[0], GL_STATIC_DRAW);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.TangentData.size() > 0)
|
||||||
|
{
|
||||||
|
if (!tangentVBO)
|
||||||
|
{
|
||||||
|
tangentVBO = std::make_shared<VBOHolder>();
|
||||||
|
}
|
||||||
|
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, tangentVBO->getBuffer());
|
||||||
|
|
||||||
|
glBufferData(GL_ARRAY_BUFFER, data.TangentData.size() * 12, &data.TangentData[0], GL_STATIC_DRAW);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.BinormalData.size() > 0)
|
||||||
|
{
|
||||||
|
if (!binormalVBO)
|
||||||
|
{
|
||||||
|
binormalVBO = std::make_shared<VBOHolder>();
|
||||||
|
}
|
||||||
|
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, binormalVBO->getBuffer());
|
||||||
|
|
||||||
|
glBufferData(GL_ARRAY_BUFFER, data.BinormalData.size() * 12, &data.BinormalData[0], GL_STATIC_DRAW);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.ColorData.size() > 0)
|
||||||
|
{
|
||||||
|
if (!colorVBO)
|
||||||
|
{
|
||||||
|
colorVBO = std::make_shared<VBOHolder>();
|
||||||
|
}
|
||||||
|
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, colorVBO->getBuffer());
|
||||||
|
|
||||||
|
glBufferData(GL_ARRAY_BUFFER, data.ColorData.size() * 12, &data.ColorData[0], GL_STATIC_DRAW);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VertexDataStruct::Scale(float scale)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < PositionData.size(); i++)
|
||||||
|
{
|
||||||
|
PositionData[i] = PositionData[i] * scale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void VertexDataStruct::Move(Vector3f diff)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < PositionData.size(); i++)
|
||||||
|
{
|
||||||
|
PositionData[i] = PositionData[i] + diff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VertexDataStruct::SwapZandY()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < PositionData.size(); i++)
|
||||||
|
{
|
||||||
|
auto value = PositionData[i].v[1];
|
||||||
|
PositionData[i].v[1] = PositionData[i].v[2];
|
||||||
|
PositionData[i].v[2] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void VertexDataStruct::RotateByMatrix(Matrix3f m)
|
||||||
|
{
|
||||||
|
|
||||||
|
for (int i = 0; i < PositionData.size(); i++)
|
||||||
|
{
|
||||||
|
PositionData[i] = MultVectorMatrix(PositionData[i], m);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < NormalData.size(); i++)
|
||||||
|
{
|
||||||
|
NormalData[i] = MultVectorMatrix(NormalData[i], m);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < TangentData.size(); i++)
|
||||||
|
{
|
||||||
|
TangentData[i] = MultVectorMatrix(TangentData[i], m);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < BinormalData.size(); i++)
|
||||||
|
{
|
||||||
|
BinormalData[i] = MultVectorMatrix(BinormalData[i], m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VertexRenderStruct::AssignFrom(const VertexDataStruct& v)
|
||||||
|
{
|
||||||
|
data = v;
|
||||||
|
RefreshVBO();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::InitOpenGL()
|
||||||
|
{
|
||||||
|
ModelviewMatrixStack.push(Matrix4f::Identity());
|
||||||
|
ProjectionMatrixStack.push(Matrix4f::Identity());
|
||||||
|
|
||||||
|
glEnable(GL_DEPTH_TEST);
|
||||||
|
glEnable(GL_BLEND);
|
||||||
|
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
|
||||||
|
#ifndef EMSCRIPTEN
|
||||||
|
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
|
||||||
|
#endif
|
||||||
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
|
glDepthFunc(GL_LEQUAL);
|
||||||
|
|
||||||
|
CheckGlError();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::PushProjectionMatrix(float width, float height, float zNear, float zFar)
|
||||||
|
{
|
||||||
|
Matrix4f m = MakeOrthoMatrix(width, height, zNear, zFar);
|
||||||
|
ProjectionMatrixStack.push(m);
|
||||||
|
SetMatrix();
|
||||||
|
|
||||||
|
if (ProjectionMatrixStack.size() > CONST_MATRIX_STACK_SIZE)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Projection matrix stack overflow!!!!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::PushProjectionMatrix(float xmin, float xmax, float ymin, float ymax, float zNear, float zFar)
|
||||||
|
{
|
||||||
|
Matrix4f m = MakeOrthoMatrix(xmin, xmax, ymin, ymax, zNear, zFar);
|
||||||
|
ProjectionMatrixStack.push(m);
|
||||||
|
SetMatrix();
|
||||||
|
|
||||||
|
if (ProjectionMatrixStack.size() > CONST_MATRIX_STACK_SIZE)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Projection matrix stack overflow!!!!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::PushPerspectiveProjectionMatrix(float fovY, float aspectRatio, float zNear, float zFar)
|
||||||
|
{
|
||||||
|
Matrix4f m = MakePerspectiveMatrix(fovY, aspectRatio, zNear, zFar);
|
||||||
|
ProjectionMatrixStack.push(m);
|
||||||
|
SetMatrix();
|
||||||
|
|
||||||
|
if (ProjectionMatrixStack.size() > CONST_MATRIX_STACK_SIZE)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Projection matrix stack overflow!!!!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Renderer::PopProjectionMatrix()
|
||||||
|
{
|
||||||
|
if (ProjectionMatrixStack.size() == 0)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Projection matrix stack underflow!!!!");
|
||||||
|
}
|
||||||
|
ProjectionMatrixStack.pop();
|
||||||
|
SetMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
Matrix4f Renderer::GetProjectionModelViewMatrix()
|
||||||
|
{
|
||||||
|
return ProjectionModelViewMatrix;
|
||||||
|
}
|
||||||
|
|
||||||
|
Matrix4f Renderer::GetCurrentModelViewMatrix()
|
||||||
|
{
|
||||||
|
return ModelviewMatrixStack.top();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Renderer::SetMatrix()
|
||||||
|
{
|
||||||
|
if (ProjectionMatrixStack.size() <= 0)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Projection matrix stack out!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ModelviewMatrixStack.size() <= 0)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Modelview matrix stack out!");
|
||||||
|
}
|
||||||
|
|
||||||
|
Matrix4f& modelViewMatrix = ModelviewMatrixStack.top();
|
||||||
|
ProjectionModelViewMatrix = ProjectionMatrixStack.top() * modelViewMatrix;
|
||||||
|
|
||||||
|
static const std::string ProjectionModelViewMatrixName = "ProjectionModelViewMatrix";
|
||||||
|
|
||||||
|
RenderUniformMatrix4fv(ProjectionModelViewMatrixName, false, &ProjectionModelViewMatrix.m[0]);
|
||||||
|
|
||||||
|
static const std::string ModelViewMatrixName = "ModelViewMatrix";
|
||||||
|
|
||||||
|
RenderUniformMatrix4fv(ModelViewMatrixName, false, &modelViewMatrix.m[0]);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::PushMatrix()
|
||||||
|
{
|
||||||
|
if (ModelviewMatrixStack.size() == 0)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Modelview matrix stack underflow!!!!");
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelviewMatrixStack.push(ModelviewMatrixStack.top());
|
||||||
|
|
||||||
|
if (ModelviewMatrixStack.size() > CONST_MATRIX_STACK_SIZE)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Modelview matrix stack overflow!!!!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::LoadIdentity()
|
||||||
|
{
|
||||||
|
if (ModelviewMatrixStack.size() == 0)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Modelview matrix stack underflow!!!!");
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelviewMatrixStack.pop();
|
||||||
|
ModelviewMatrixStack.push(Matrix4f::Identity());
|
||||||
|
|
||||||
|
SetMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::TranslateMatrix(const Vector3f& p)
|
||||||
|
{
|
||||||
|
|
||||||
|
Matrix4f m = Matrix4f::Identity();
|
||||||
|
m.m[12] = p.v[0];
|
||||||
|
m.m[13] = p.v[1];
|
||||||
|
m.m[14] = p.v[2];
|
||||||
|
|
||||||
|
m = ModelviewMatrixStack.top() * m;
|
||||||
|
|
||||||
|
if (ModelviewMatrixStack.size() == 0)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Modelview matrix stack underflow!!!!");
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelviewMatrixStack.pop();
|
||||||
|
ModelviewMatrixStack.push(m);
|
||||||
|
|
||||||
|
SetMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::ScaleMatrix(float scale)
|
||||||
|
{
|
||||||
|
Matrix4f m = Matrix4f::Identity();
|
||||||
|
m.m[0] = scale;
|
||||||
|
m.m[5] = scale;
|
||||||
|
m.m[10] = scale;
|
||||||
|
|
||||||
|
m = ModelviewMatrixStack.top() * m;
|
||||||
|
|
||||||
|
if (ModelviewMatrixStack.size() == 0)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Modelview matrix stack underflow!!!!");
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelviewMatrixStack.pop();
|
||||||
|
ModelviewMatrixStack.push(m);
|
||||||
|
|
||||||
|
SetMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::ScaleMatrix(const Vector3f& scale)
|
||||||
|
{
|
||||||
|
Matrix4f m = Matrix4f::Identity();
|
||||||
|
m.m[0] = scale.v[0];
|
||||||
|
m.m[5] = scale.v[1];
|
||||||
|
m.m[10] = scale.v[2];
|
||||||
|
|
||||||
|
m = ModelviewMatrixStack.top() * m;
|
||||||
|
|
||||||
|
if (ModelviewMatrixStack.size() == 0)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Modelview matrix stack underflow!!!!");
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelviewMatrixStack.pop();
|
||||||
|
ModelviewMatrixStack.push(m);
|
||||||
|
|
||||||
|
SetMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::RotateMatrix(const Vector4f& q)
|
||||||
|
{
|
||||||
|
|
||||||
|
Matrix3f m3 = QuatToMatrix(q);
|
||||||
|
Matrix4f m = Matrix4f::Identity();
|
||||||
|
m.m[0] = m3.m[0];
|
||||||
|
m.m[1] = m3.m[1];
|
||||||
|
m.m[2] = m3.m[2];
|
||||||
|
|
||||||
|
m.m[4] = m3.m[3];
|
||||||
|
m.m[5] = m3.m[4];
|
||||||
|
m.m[6] = m3.m[5];
|
||||||
|
|
||||||
|
m.m[8] = m3.m[6];
|
||||||
|
m.m[9] = m3.m[7];
|
||||||
|
m.m[10] = m3.m[8];
|
||||||
|
|
||||||
|
m = ModelviewMatrixStack.top() * m;
|
||||||
|
|
||||||
|
if (ModelviewMatrixStack.size() == 0)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Modelview matrix stack underflow!!!!");
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelviewMatrixStack.pop();
|
||||||
|
ModelviewMatrixStack.push(m);
|
||||||
|
|
||||||
|
|
||||||
|
SetMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::RotateMatrix(const Matrix3f& m3)
|
||||||
|
{
|
||||||
|
Matrix4f m = Matrix4f::Identity();
|
||||||
|
m.m[0] = m3.m[0];
|
||||||
|
m.m[1] = m3.m[1];
|
||||||
|
m.m[2] = m3.m[2];
|
||||||
|
|
||||||
|
m.m[4] = m3.m[3];
|
||||||
|
m.m[5] = m3.m[4];
|
||||||
|
m.m[6] = m3.m[5];
|
||||||
|
|
||||||
|
m.m[8] = m3.m[6];
|
||||||
|
m.m[9] = m3.m[7];
|
||||||
|
m.m[10] = m3.m[8];
|
||||||
|
|
||||||
|
m = ModelviewMatrixStack.top() * m;
|
||||||
|
|
||||||
|
if (ModelviewMatrixStack.size() == 0)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Modelview matrix stack underflow!!!!");
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelviewMatrixStack.pop();
|
||||||
|
ModelviewMatrixStack.push(m);
|
||||||
|
|
||||||
|
|
||||||
|
SetMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::PushSpecialMatrix(const Matrix4f& m)
|
||||||
|
{
|
||||||
|
if (ModelviewMatrixStack.size() > 64)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Modelview matrix stack overflow!!!!");
|
||||||
|
}
|
||||||
|
ModelviewMatrixStack.push(m);
|
||||||
|
SetMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Renderer::PopMatrix()
|
||||||
|
{
|
||||||
|
if (ModelviewMatrixStack.size() == 0)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Modelview matrix stack underflow!!!!");
|
||||||
|
}
|
||||||
|
ModelviewMatrixStack.pop();
|
||||||
|
|
||||||
|
SetMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Renderer::EnableVertexAttribArray(const std::string& attribName)
|
||||||
|
{
|
||||||
|
|
||||||
|
auto shader = shaderManager.GetCurrentShader();
|
||||||
|
if (shader->attribList.find(attribName) != shader->attribList.end())
|
||||||
|
glEnableVertexAttribArray(shader->attribList[attribName]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::DisableVertexAttribArray(const std::string& attribName)
|
||||||
|
{
|
||||||
|
auto shader = shaderManager.GetCurrentShader();
|
||||||
|
if (shader->attribList.find(attribName) != shader->attribList.end())
|
||||||
|
glDisableVertexAttribArray(shader->attribList[attribName]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::RenderUniformMatrix3fv(const std::string& uniformName, bool transpose, const float* value)
|
||||||
|
{
|
||||||
|
auto shader = shaderManager.GetCurrentShader();
|
||||||
|
|
||||||
|
auto uniform = shader->uniformList.find(uniformName);
|
||||||
|
|
||||||
|
if (uniform != shader->uniformList.end())
|
||||||
|
{
|
||||||
|
glUniformMatrix3fv(uniform->second, 1, transpose, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Renderer::RenderUniformMatrix4fv(const std::string& uniformName, bool transpose, const float* value)
|
||||||
|
{
|
||||||
|
auto shader = shaderManager.GetCurrentShader();
|
||||||
|
|
||||||
|
auto uniform = shader->uniformList.find(uniformName);
|
||||||
|
|
||||||
|
if (uniform != shader->uniformList.end())
|
||||||
|
{
|
||||||
|
glUniformMatrix4fv(uniform->second, 1, transpose, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::RenderUniform3fv(const std::string& uniformName, const float* value)
|
||||||
|
{
|
||||||
|
auto shader = shaderManager.GetCurrentShader();
|
||||||
|
|
||||||
|
auto uniform = shader->uniformList.find(uniformName);
|
||||||
|
|
||||||
|
if (uniform != shader->uniformList.end())
|
||||||
|
{
|
||||||
|
glUniform3fv(uniform->second, 1, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::RenderUniform1i(const std::string& uniformName, const int value)
|
||||||
|
{
|
||||||
|
auto shader = shaderManager.GetCurrentShader();
|
||||||
|
|
||||||
|
auto uniform = shader->uniformList.find(uniformName);
|
||||||
|
|
||||||
|
if (uniform != shader->uniformList.end())
|
||||||
|
{
|
||||||
|
glUniform1i(uniform->second, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::RenderUniform1f(const std::string& uniformName, float value)
|
||||||
|
{
|
||||||
|
auto shader = shaderManager.GetCurrentShader();
|
||||||
|
|
||||||
|
auto uniform = shader->uniformList.find(uniformName);
|
||||||
|
|
||||||
|
if (uniform != shader->uniformList.end())
|
||||||
|
{
|
||||||
|
glUniform1fv(uniform->second, 1, &value);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Renderer::VertexAttribPointer2fv(const std::string& attribName, int stride, const char* pointer)
|
||||||
|
{
|
||||||
|
auto shader = shaderManager.GetCurrentShader();
|
||||||
|
if (shader->attribList.find(attribName) != shader->attribList.end())
|
||||||
|
glVertexAttribPointer(shader->attribList[attribName], 2, GL_FLOAT, GL_FALSE, stride, pointer);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::VertexAttribPointer3fv(const std::string& attribName, int stride, const char* pointer)
|
||||||
|
{
|
||||||
|
|
||||||
|
auto shader = shaderManager.GetCurrentShader();
|
||||||
|
if (shader->attribList.find(attribName) != shader->attribList.end())
|
||||||
|
glVertexAttribPointer(shader->attribList[attribName], 3, GL_FLOAT, GL_FALSE, stride, pointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::DrawVertexRenderStruct(const VertexRenderStruct& VertexRenderStruct)
|
||||||
|
{
|
||||||
|
static const std::string vNormal("vNormal");
|
||||||
|
static const std::string vTangent("vTangent");
|
||||||
|
static const std::string vBinormal("vBinormal");
|
||||||
|
static const std::string vColor("vColor");
|
||||||
|
static const std::string vTexCoord("vTexCoord");
|
||||||
|
static const std::string vPosition("vPosition");
|
||||||
|
|
||||||
|
//glBindVertexArray(VertexRenderStruct.vao->getBuffer());
|
||||||
|
|
||||||
|
//Check if main thread, check if data is not empty...
|
||||||
|
if (VertexRenderStruct.data.NormalData.size() > 0)
|
||||||
|
{
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.normalVBO->getBuffer());
|
||||||
|
VertexAttribPointer3fv(vNormal, 0, NULL);
|
||||||
|
}
|
||||||
|
if (VertexRenderStruct.data.TangentData.size() > 0)
|
||||||
|
{
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.tangentVBO->getBuffer());
|
||||||
|
VertexAttribPointer3fv(vTangent, 0, NULL);
|
||||||
|
}
|
||||||
|
if (VertexRenderStruct.data.BinormalData.size() > 0)
|
||||||
|
{
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.binormalVBO->getBuffer());
|
||||||
|
VertexAttribPointer3fv(vBinormal, 0, NULL);
|
||||||
|
}
|
||||||
|
if (VertexRenderStruct.data.ColorData.size() > 0)
|
||||||
|
{
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.colorVBO->getBuffer());
|
||||||
|
VertexAttribPointer3fv(vColor, 0, NULL);
|
||||||
|
}
|
||||||
|
if (VertexRenderStruct.data.TexCoordData.size() > 0)
|
||||||
|
{
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.texCoordVBO->getBuffer());
|
||||||
|
VertexAttribPointer2fv(vTexCoord, 0, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.positionVBO->getBuffer());
|
||||||
|
VertexAttribPointer3fv(vPosition, 0, NULL);
|
||||||
|
|
||||||
|
glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(VertexRenderStruct.data.PositionData.size()));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void worldToScreenCoordinates(Vector3f objectPos,
|
||||||
|
Matrix4f projectionModelView,
|
||||||
|
int screenWidth, int screenHeight,
|
||||||
|
int& screenX, int& screenY) {
|
||||||
|
|
||||||
|
Vector4f inx = { objectPos.v[0], objectPos.v[1], objectPos.v[2], 1.0f };
|
||||||
|
Vector4f clipCoords = MultMatrixVector(projectionModelView, inx);
|
||||||
|
|
||||||
|
float ndcX = clipCoords.v[0] / clipCoords.v[3];
|
||||||
|
float ndcY = clipCoords.v[1] / clipCoords.v[3];
|
||||||
|
|
||||||
|
screenX = (int)((ndcX + 1.0f) * 0.5f * screenWidth);
|
||||||
|
screenY = (int)((1.0f + ndcY) * 0.5f * screenHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
144
Renderer.h
Executable file
144
Renderer.h
Executable file
@ -0,0 +1,144 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "OpenGlExtensions.h"
|
||||||
|
#include "ZLMath.h"
|
||||||
|
#include <exception>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include "ShaderManager.h"
|
||||||
|
|
||||||
|
namespace ZL {
|
||||||
|
|
||||||
|
constexpr size_t CONST_MATRIX_STACK_SIZE = 64;
|
||||||
|
|
||||||
|
class VBOHolder {
|
||||||
|
GLuint Buffer;
|
||||||
|
|
||||||
|
public:
|
||||||
|
VBOHolder();
|
||||||
|
|
||||||
|
VBOHolder(const VBOHolder& v) = delete;
|
||||||
|
|
||||||
|
VBOHolder& operator=(const VBOHolder& v) = delete;
|
||||||
|
|
||||||
|
~VBOHolder();
|
||||||
|
|
||||||
|
GLuint getBuffer();
|
||||||
|
};
|
||||||
|
|
||||||
|
class VAOHolder {
|
||||||
|
GLuint vao;
|
||||||
|
|
||||||
|
public:
|
||||||
|
VAOHolder();
|
||||||
|
|
||||||
|
VAOHolder(const VAOHolder& v) = delete;
|
||||||
|
|
||||||
|
VAOHolder& operator=(const VAOHolder& v) = delete;
|
||||||
|
|
||||||
|
~VAOHolder();
|
||||||
|
|
||||||
|
GLuint getBuffer();
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VertexDataStruct
|
||||||
|
{
|
||||||
|
std::vector<Vector3f> PositionData;
|
||||||
|
std::vector<Vector2f> TexCoordData;
|
||||||
|
std::vector<Vector3f> NormalData;
|
||||||
|
std::vector<Vector3f> TangentData;
|
||||||
|
std::vector<Vector3f> BinormalData;
|
||||||
|
std::vector<Vector3f> ColorData;
|
||||||
|
|
||||||
|
void RotateByMatrix(Matrix3f m);
|
||||||
|
|
||||||
|
void Scale(float scale);
|
||||||
|
void Move(Vector3f diff);
|
||||||
|
void SwapZandY();
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VertexRenderStruct
|
||||||
|
{
|
||||||
|
VertexDataStruct data;
|
||||||
|
|
||||||
|
std::shared_ptr<VAOHolder> vao;
|
||||||
|
std::shared_ptr<VBOHolder> positionVBO;
|
||||||
|
std::shared_ptr<VBOHolder> texCoordVBO;
|
||||||
|
std::shared_ptr<VBOHolder> normalVBO;
|
||||||
|
std::shared_ptr<VBOHolder> tangentVBO;
|
||||||
|
std::shared_ptr<VBOHolder> binormalVBO;
|
||||||
|
std::shared_ptr<VBOHolder> colorVBO;
|
||||||
|
void RefreshVBO();
|
||||||
|
|
||||||
|
void AssignFrom(const VertexDataStruct& v);
|
||||||
|
};
|
||||||
|
|
||||||
|
VertexDataStruct CreateRect2D(Vector2f center, Vector2f halfWidthHeight, float zLevel);
|
||||||
|
VertexDataStruct CreateRectHorizontalSections2D(Vector2f center, Vector2f halfWidthHeight, float zLevel, size_t sectionCount);
|
||||||
|
VertexDataStruct CreateCube3D(float scale);
|
||||||
|
VertexDataStruct CreateCubemap(float scale = 1000.f);
|
||||||
|
|
||||||
|
|
||||||
|
class Renderer
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
std::stack<Matrix4f> ProjectionMatrixStack;
|
||||||
|
std::stack<Matrix4f> ModelviewMatrixStack;
|
||||||
|
|
||||||
|
Matrix4f ProjectionModelViewMatrix;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
ShaderManager shaderManager;
|
||||||
|
|
||||||
|
void InitOpenGL();
|
||||||
|
|
||||||
|
void PushProjectionMatrix(float width, float height, float zNear = 0.f, float zFar = 1.f);
|
||||||
|
void PushProjectionMatrix(float xmin, float xmax, float ymin, float ymax, float zNear, float zFar);
|
||||||
|
void PushPerspectiveProjectionMatrix(float fovY, float aspectRatio, float zNear, float zFar);
|
||||||
|
void PopProjectionMatrix();
|
||||||
|
|
||||||
|
void PushMatrix();
|
||||||
|
void LoadIdentity();
|
||||||
|
void TranslateMatrix(const Vector3f& p);
|
||||||
|
void ScaleMatrix(float scale);
|
||||||
|
void ScaleMatrix(const Vector3f& scale);
|
||||||
|
void RotateMatrix(const Vector4f& q);
|
||||||
|
void RotateMatrix(const Matrix3f& m3);
|
||||||
|
void PushSpecialMatrix(const Matrix4f& m);
|
||||||
|
void PopMatrix();
|
||||||
|
|
||||||
|
|
||||||
|
Matrix4f GetProjectionModelViewMatrix();
|
||||||
|
Matrix4f GetCurrentModelViewMatrix();
|
||||||
|
|
||||||
|
void SetMatrix();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void EnableVertexAttribArray(const std::string& attribName);
|
||||||
|
|
||||||
|
void DisableVertexAttribArray(const std::string& attribName);
|
||||||
|
|
||||||
|
|
||||||
|
void RenderUniformMatrix3fv(const std::string& uniformName, bool transpose, const float* value);
|
||||||
|
void RenderUniformMatrix4fv(const std::string& uniformName, bool transpose, const float* value);
|
||||||
|
void RenderUniform1i(const std::string& uniformName, const int value);
|
||||||
|
void RenderUniform3fv(const std::string& uniformName, const float* value);
|
||||||
|
void RenderUniform1f(const std::string& uniformName, float value);
|
||||||
|
|
||||||
|
void VertexAttribPointer2fv(const std::string& attribName, int stride, const char* pointer);
|
||||||
|
|
||||||
|
void VertexAttribPointer3fv(const std::string& attribName, int stride, const char* pointer);
|
||||||
|
|
||||||
|
void DrawVertexRenderStruct(const VertexRenderStruct& VertexRenderStruct);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void worldToScreenCoordinates(Vector3f objectPos,
|
||||||
|
Matrix4f projectionModelView,
|
||||||
|
int screenWidth, int screenHeight,
|
||||||
|
int& screenX, int& screenY);
|
||||||
|
|
||||||
|
};
|
||||||
215
ShaderManager.cpp
Executable file
215
ShaderManager.cpp
Executable file
@ -0,0 +1,215 @@
|
|||||||
|
#include "ShaderManager.h"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
|
||||||
|
namespace ZL {
|
||||||
|
|
||||||
|
ShaderResource::ShaderResource(const std::string& vertexCode, const std::string& fragmentCode)
|
||||||
|
{
|
||||||
|
|
||||||
|
const int CONST_INFOLOG_LENGTH = 256;
|
||||||
|
|
||||||
|
char infoLog[CONST_INFOLOG_LENGTH];
|
||||||
|
int infoLogLength;
|
||||||
|
char infoLog2[CONST_INFOLOG_LENGTH];
|
||||||
|
int infoLogLength2;
|
||||||
|
|
||||||
|
int vertexShaderCompiled;
|
||||||
|
int fragmentShaderCompiled;
|
||||||
|
int programLinked;
|
||||||
|
|
||||||
|
GLuint vertexShader;
|
||||||
|
GLuint fragmentShader;
|
||||||
|
|
||||||
|
int vertexCodeLength = static_cast<int>(strlen(vertexCode.c_str()));
|
||||||
|
int fragmentCodeLength = static_cast<int>(strlen(fragmentCode.c_str()));
|
||||||
|
|
||||||
|
const char* vc = &vertexCode[0];
|
||||||
|
const char* fc = &fragmentCode[0];
|
||||||
|
|
||||||
|
vertexShader = glCreateShader(GL_VERTEX_SHADER);
|
||||||
|
glShaderSource(vertexShader, 1, &(vc), &vertexCodeLength);
|
||||||
|
|
||||||
|
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
|
||||||
|
glShaderSource(fragmentShader, 1, &(fc), &fragmentCodeLength);
|
||||||
|
|
||||||
|
glCompileShader(vertexShader);
|
||||||
|
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &vertexShaderCompiled);
|
||||||
|
glGetShaderInfoLog(vertexShader, CONST_INFOLOG_LENGTH, &infoLogLength, infoLog);
|
||||||
|
|
||||||
|
glCompileShader(fragmentShader);
|
||||||
|
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &fragmentShaderCompiled);
|
||||||
|
glGetShaderInfoLog(fragmentShader, CONST_INFOLOG_LENGTH, &infoLogLength2, infoLog2);
|
||||||
|
|
||||||
|
|
||||||
|
if (!vertexShaderCompiled)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Failed to compile vertex shader code!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fragmentShaderCompiled)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Failed to compile fragment shader code!");
|
||||||
|
}
|
||||||
|
|
||||||
|
shaderProgram = glCreateProgram();
|
||||||
|
|
||||||
|
glAttachShader(shaderProgram, vertexShader);
|
||||||
|
glAttachShader(shaderProgram, fragmentShader);
|
||||||
|
|
||||||
|
glLinkProgram(shaderProgram);
|
||||||
|
|
||||||
|
glDeleteShader(vertexShader);
|
||||||
|
glDeleteShader(fragmentShader);
|
||||||
|
|
||||||
|
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &programLinked);
|
||||||
|
glGetProgramInfoLog(shaderProgram, CONST_INFOLOG_LENGTH, &infoLogLength, infoLog);
|
||||||
|
|
||||||
|
if (!programLinked)
|
||||||
|
{
|
||||||
|
shaderProgram = 0;
|
||||||
|
throw std::runtime_error("Failed to link shader program!");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int dummySize; //Dummy
|
||||||
|
int dummyLen; //Dummy
|
||||||
|
GLenum dummyEnum;
|
||||||
|
|
||||||
|
|
||||||
|
//================= Parsing all uniforms ================
|
||||||
|
|
||||||
|
int activeUniforms;
|
||||||
|
|
||||||
|
const int CONST_UNIFORM_NAME_LENGTH = 256;
|
||||||
|
char uniformName[CONST_UNIFORM_NAME_LENGTH];
|
||||||
|
|
||||||
|
glGetProgramiv(shaderProgram, GL_ACTIVE_UNIFORMS, &activeUniforms);
|
||||||
|
|
||||||
|
for (int i = 0; i < activeUniforms; i++)
|
||||||
|
{
|
||||||
|
glGetActiveUniform(shaderProgram, i, CONST_UNIFORM_NAME_LENGTH, &dummyLen, &dummySize, &dummyEnum, uniformName);
|
||||||
|
|
||||||
|
uniformList[uniformName] = glGetUniformLocation(shaderProgram, uniformName);
|
||||||
|
}
|
||||||
|
|
||||||
|
//================= Parsing all attributes ================
|
||||||
|
int activeAttribs;
|
||||||
|
|
||||||
|
const int CONST_ATTRIB_NAME_LENGTH = 256;
|
||||||
|
char attribName[CONST_ATTRIB_NAME_LENGTH];
|
||||||
|
|
||||||
|
glGetProgramiv(shaderProgram, GL_ACTIVE_ATTRIBUTES, &activeAttribs);
|
||||||
|
|
||||||
|
for (int i = 0; i < activeAttribs; i++)
|
||||||
|
{
|
||||||
|
glGetActiveAttrib(shaderProgram, i, CONST_ATTRIB_NAME_LENGTH, &dummyLen, &dummySize, &dummyEnum, attribName);
|
||||||
|
attribList[attribName] = glGetAttribLocation(shaderProgram, attribName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ShaderResource::~ShaderResource()
|
||||||
|
{
|
||||||
|
if (shaderProgram != 0)
|
||||||
|
{
|
||||||
|
glDeleteProgram(shaderProgram);
|
||||||
|
|
||||||
|
shaderProgram = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GLuint ShaderResource::getShaderProgram()
|
||||||
|
{
|
||||||
|
return shaderProgram;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ShaderManager::AddShaderFromFiles(const std::string& shaderName, const std::string& vertexShaderFileName, const std::string& fragmentShaderFileName, const std::string& ZIPFileName)
|
||||||
|
{
|
||||||
|
|
||||||
|
std::string vertexShader;
|
||||||
|
std::string fragmentShader;
|
||||||
|
|
||||||
|
if (!ZIPFileName.empty()){
|
||||||
|
|
||||||
|
std::vector<char> vertexShaderData;
|
||||||
|
std::vector<char> fragmentShaderData;
|
||||||
|
|
||||||
|
vertexShaderData = readFileFromZIP(vertexShaderFileName, ZIPFileName);
|
||||||
|
fragmentShaderData = readFileFromZIP(fragmentShaderFileName, ZIPFileName);
|
||||||
|
|
||||||
|
vertexShader = std::string(vertexShaderData.begin(), vertexShaderData.end());
|
||||||
|
fragmentShader = std::string(fragmentShaderData.begin(), fragmentShaderData.end());
|
||||||
|
|
||||||
|
}else{
|
||||||
|
vertexShader = readTextFile(vertexShaderFileName);
|
||||||
|
fragmentShader = readTextFile(fragmentShaderFileName);
|
||||||
|
}
|
||||||
|
///std::cout << "Shader: "<< vertexShader << std::endl;
|
||||||
|
shaderResourceMap[shaderName] = std::make_shared<ShaderResource>(vertexShader, fragmentShader);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShaderManager::PushShader(const std::string& shaderName)
|
||||||
|
{
|
||||||
|
if (shaderStack.size() >= CONST_MAX_SHADER_STACK_SIZE)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Shader stack overflow!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shaderResourceMap.find(shaderName) == shaderResourceMap.end())
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Shader does not exist!");
|
||||||
|
}
|
||||||
|
|
||||||
|
shaderStack.push(shaderName);
|
||||||
|
|
||||||
|
glUseProgram(shaderResourceMap[shaderName]->getShaderProgram());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ShaderManager::PopShader()
|
||||||
|
{
|
||||||
|
if (shaderStack.size() == 0)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Shader stack underflow!");
|
||||||
|
}
|
||||||
|
|
||||||
|
shaderStack.pop();
|
||||||
|
|
||||||
|
if (shaderStack.size() == 0)
|
||||||
|
{
|
||||||
|
glUseProgram(0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
glUseProgram(shaderResourceMap[shaderStack.top()]->getShaderProgram());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<ShaderResource> ShaderManager::GetCurrentShader()
|
||||||
|
{
|
||||||
|
if (shaderStack.size() == 0)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Shader stack underflow!");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return shaderResourceMap[shaderStack.top()];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ShaderSetter::ShaderSetter(ShaderManager& inShaderManager, const std::string& shaderName)
|
||||||
|
: shaderManager(shaderManager)
|
||||||
|
{
|
||||||
|
shaderManager.PushShader(shaderName);
|
||||||
|
}
|
||||||
|
|
||||||
|
ShaderSetter::~ShaderSetter()
|
||||||
|
{
|
||||||
|
shaderManager.PopShader();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
64
ShaderManager.h
Executable file
64
ShaderManager.h
Executable file
@ -0,0 +1,64 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "OpenGlExtensions.h"
|
||||||
|
#include "Utils.h"
|
||||||
|
|
||||||
|
namespace ZL {
|
||||||
|
|
||||||
|
|
||||||
|
constexpr size_t CONST_MAX_SHADER_STACK_SIZE = 16;
|
||||||
|
|
||||||
|
class ShaderResource
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
GLuint shaderProgram;
|
||||||
|
|
||||||
|
std::unordered_map<std::string, GLuint> uniformList;
|
||||||
|
|
||||||
|
//std::unordered_map<std::string, std::pair<bool, size_t>> UniformList;
|
||||||
|
std::map<std::string, GLuint> attribList;
|
||||||
|
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
GLuint getShaderProgram();
|
||||||
|
|
||||||
|
ShaderResource(const std::string& vertexCode, const std::string& fragmentCode);
|
||||||
|
~ShaderResource();
|
||||||
|
|
||||||
|
public:
|
||||||
|
friend class ShaderManager;
|
||||||
|
friend class Renderer;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ShaderManager {
|
||||||
|
protected:
|
||||||
|
std::unordered_map<std::string, std::shared_ptr<ShaderResource>> shaderResourceMap;
|
||||||
|
|
||||||
|
std::stack<std::string> shaderStack;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void AddShaderFromFiles(const std::string& shaderName, const std::string& vertexShaderFileName, const std::string& fragmentShaderFileName, const std::string& ZIPFileName = "");
|
||||||
|
|
||||||
|
void PushShader(const std::string& shaderName);
|
||||||
|
void PopShader();
|
||||||
|
|
||||||
|
std::shared_ptr<ShaderResource> GetCurrentShader();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class ShaderSetter
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
|
||||||
|
ShaderManager& shaderManager;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ShaderSetter(ShaderManager& inShaderManager, const std::string& shaderName);
|
||||||
|
|
||||||
|
~ShaderSetter();
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
299
SparkEmitter.cpp
Normal file
299
SparkEmitter.cpp
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
#include "SparkEmitter.h"
|
||||||
|
#include <random>
|
||||||
|
#include <cmath>
|
||||||
|
#include "OpenGlExtensions.h"
|
||||||
|
#include "Environment.h"
|
||||||
|
|
||||||
|
|
||||||
|
namespace ZL {
|
||||||
|
|
||||||
|
SparkEmitter::SparkEmitter()
|
||||||
|
: emissionRate(100.0f), isActive(true), drawDataDirty(true), maxParticles(200) {
|
||||||
|
particles.resize(maxParticles);
|
||||||
|
drawPositions.reserve(maxParticles * 6);
|
||||||
|
drawTexCoords.reserve(maxParticles * 6);
|
||||||
|
lastEmissionTime = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
sparkQuad.data = VertexDataStruct();
|
||||||
|
}
|
||||||
|
|
||||||
|
SparkEmitter::SparkEmitter(const std::vector<Vector3f>& positions, float rate)
|
||||||
|
: emissionPoints(positions), emissionRate(rate), isActive(true),
|
||||||
|
drawDataDirty(true), maxParticles(positions.size() * 100) {
|
||||||
|
particles.resize(maxParticles);
|
||||||
|
drawPositions.reserve(maxParticles * 6);
|
||||||
|
drawTexCoords.reserve(maxParticles * 6);
|
||||||
|
lastEmissionTime = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
sparkQuad.data = VertexDataStruct();
|
||||||
|
}
|
||||||
|
|
||||||
|
SparkEmitter::SparkEmitter(const std::vector<Vector3f>& positions,
|
||||||
|
std::shared_ptr<Texture> tex,
|
||||||
|
float rate)
|
||||||
|
: emissionPoints(positions), texture(tex), emissionRate(rate),
|
||||||
|
isActive(true), drawDataDirty(true), maxParticles(positions.size() * 100) {
|
||||||
|
particles.resize(maxParticles);
|
||||||
|
drawPositions.reserve(maxParticles * 6);
|
||||||
|
drawTexCoords.reserve(maxParticles * 6);
|
||||||
|
lastEmissionTime = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
sparkQuad.data = VertexDataStruct();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SparkEmitter::setTexture(std::shared_ptr<Texture> tex) {
|
||||||
|
texture = tex;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SparkEmitter::prepareDrawData() {
|
||||||
|
if (!drawDataDirty) return;
|
||||||
|
|
||||||
|
drawPositions.clear();
|
||||||
|
drawTexCoords.clear();
|
||||||
|
|
||||||
|
if (getActiveParticleCount() == 0) {
|
||||||
|
drawDataDirty = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::pair<const SparkParticle*, float>> sortedParticles;
|
||||||
|
sortedParticles.reserve(getActiveParticleCount());
|
||||||
|
|
||||||
|
for (const auto& particle : particles) {
|
||||||
|
if (particle.active) {
|
||||||
|
sortedParticles.push_back({ &particle, particle.position.v[2] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(sortedParticles.begin(), sortedParticles.end(),
|
||||||
|
[](const auto& a, const auto& b) {
|
||||||
|
return a.second > b.second;
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const auto& [particlePtr, depth] : sortedParticles) {
|
||||||
|
const auto& particle = *particlePtr;
|
||||||
|
Vector3f pos = particle.position;
|
||||||
|
float size = 0.04f * particle.scale;
|
||||||
|
|
||||||
|
drawPositions.push_back({ pos.v[0] - size, pos.v[1] - size, pos.v[2] });
|
||||||
|
drawTexCoords.push_back({ 0.0f, 0.0f });
|
||||||
|
|
||||||
|
drawPositions.push_back({ pos.v[0] - size, pos.v[1] + size, pos.v[2] });
|
||||||
|
drawTexCoords.push_back({ 0.0f, 1.0f });
|
||||||
|
|
||||||
|
drawPositions.push_back({ pos.v[0] + size, pos.v[1] + size, pos.v[2] });
|
||||||
|
drawTexCoords.push_back({ 1.0f, 1.0f });
|
||||||
|
|
||||||
|
drawPositions.push_back({ pos.v[0] - size, pos.v[1] - size, pos.v[2] });
|
||||||
|
drawTexCoords.push_back({ 0.0f, 0.0f });
|
||||||
|
|
||||||
|
drawPositions.push_back({ pos.v[0] + size, pos.v[1] + size, pos.v[2] });
|
||||||
|
drawTexCoords.push_back({ 1.0f, 1.0f });
|
||||||
|
|
||||||
|
drawPositions.push_back({ pos.v[0] + size, pos.v[1] - size, pos.v[2] });
|
||||||
|
drawTexCoords.push_back({ 1.0f, 0.0f });
|
||||||
|
}
|
||||||
|
|
||||||
|
drawDataDirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SparkEmitter::draw(Renderer& renderer, float zoom, int screenWidth, int screenHeight) {
|
||||||
|
if (getActiveParticleCount() == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!texture) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
prepareDrawData();
|
||||||
|
|
||||||
|
if (drawPositions.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sparkQuad.data.PositionData = drawPositions;
|
||||||
|
sparkQuad.data.TexCoordData = drawTexCoords;
|
||||||
|
sparkQuad.RefreshVBO();
|
||||||
|
|
||||||
|
static const std::string defaultShaderName = "default";
|
||||||
|
static const std::string vPositionName = "vPosition";
|
||||||
|
static const std::string vTexCoordName = "vTexCoord";
|
||||||
|
static const std::string textureUniformName = "Texture";
|
||||||
|
|
||||||
|
renderer.shaderManager.PushShader(defaultShaderName);
|
||||||
|
renderer.RenderUniform1i(textureUniformName, 0);
|
||||||
|
renderer.EnableVertexAttribArray(vPositionName);
|
||||||
|
renderer.EnableVertexAttribArray(vTexCoordName);
|
||||||
|
|
||||||
|
float aspectRatio = static_cast<float>(screenWidth) / static_cast<float>(screenHeight);
|
||||||
|
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5, aspectRatio, Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
|
||||||
|
|
||||||
|
glBindTexture(GL_TEXTURE_2D, texture->getTexID());
|
||||||
|
|
||||||
|
glEnable(GL_BLEND);
|
||||||
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE);// Аддитивное смешивание для эффекта свечения
|
||||||
|
|
||||||
|
renderer.PushMatrix();
|
||||||
|
renderer.LoadIdentity();
|
||||||
|
renderer.TranslateMatrix({ 0, 0, -1.0f * zoom });
|
||||||
|
|
||||||
|
renderer.DrawVertexRenderStruct(sparkQuad);
|
||||||
|
|
||||||
|
renderer.PopMatrix();
|
||||||
|
renderer.PopProjectionMatrix();
|
||||||
|
|
||||||
|
glDisable(GL_BLEND);
|
||||||
|
|
||||||
|
renderer.DisableVertexAttribArray(vPositionName);
|
||||||
|
renderer.DisableVertexAttribArray(vTexCoordName);
|
||||||
|
renderer.shaderManager.PopShader();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SparkEmitter::update(float deltaTimeMs) {
|
||||||
|
auto currentTime = std::chrono::steady_clock::now();
|
||||||
|
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
|
currentTime - lastEmissionTime).count();
|
||||||
|
|
||||||
|
if (isActive && elapsed >= emissionRate) {
|
||||||
|
emit();
|
||||||
|
lastEmissionTime = currentTime;
|
||||||
|
drawDataDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool anyChanged = false;
|
||||||
|
for (auto& particle : particles) {
|
||||||
|
if (particle.active) {
|
||||||
|
Vector3f oldPosition = particle.position;
|
||||||
|
float oldScale = particle.scale;
|
||||||
|
|
||||||
|
particle.position.v[0] += particle.velocity.v[0] * deltaTimeMs / 1000.0f;
|
||||||
|
particle.position.v[1] += particle.velocity.v[1] * deltaTimeMs / 1000.0f;
|
||||||
|
particle.position.v[2] += particle.velocity.v[2] * deltaTimeMs / 1000.0f;
|
||||||
|
|
||||||
|
particle.lifeTime += deltaTimeMs;
|
||||||
|
|
||||||
|
if (particle.lifeTime >= particle.maxLifeTime) {
|
||||||
|
particle.active = false;
|
||||||
|
anyChanged = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
float lifeRatio = particle.lifeTime / particle.maxLifeTime;
|
||||||
|
particle.scale = 1.0f - lifeRatio * 0.8f;
|
||||||
|
|
||||||
|
if (oldPosition.v[0] != particle.position.v[0] ||
|
||||||
|
oldPosition.v[1] != particle.position.v[1] ||
|
||||||
|
oldPosition.v[2] != particle.position.v[2] ||
|
||||||
|
oldScale != particle.scale) {
|
||||||
|
anyChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (anyChanged) {
|
||||||
|
drawDataDirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SparkEmitter::emit() {
|
||||||
|
if (emissionPoints.empty()) return;
|
||||||
|
bool emitted = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < emissionPoints.size(); ++i) {
|
||||||
|
bool particleFound = false;
|
||||||
|
|
||||||
|
for (auto& particle : particles) {
|
||||||
|
if (!particle.active) {
|
||||||
|
initParticle(particle, i);
|
||||||
|
particle.active = true;
|
||||||
|
particle.lifeTime = 0;
|
||||||
|
particle.position = emissionPoints[i];
|
||||||
|
particle.emitterIndex = i;
|
||||||
|
particleFound = true;
|
||||||
|
emitted = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!particleFound && !particles.empty()) {
|
||||||
|
size_t oldestIndex = 0;
|
||||||
|
float maxLifeTime = 0;
|
||||||
|
|
||||||
|
for (size_t j = 0; j < particles.size(); ++j) {
|
||||||
|
if (particles[j].lifeTime > maxLifeTime) {
|
||||||
|
maxLifeTime = particles[j].lifeTime;
|
||||||
|
oldestIndex = j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
initParticle(particles[oldestIndex], i);
|
||||||
|
particles[oldestIndex].active = true;
|
||||||
|
particles[oldestIndex].lifeTime = 0;
|
||||||
|
particles[oldestIndex].position = emissionPoints[i];
|
||||||
|
particles[oldestIndex].emitterIndex = i;
|
||||||
|
emitted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (emitted) {
|
||||||
|
drawDataDirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SparkEmitter::setEmissionPoints(const std::vector<Vector3f>& positions) {
|
||||||
|
emissionPoints = positions;
|
||||||
|
maxParticles = positions.size() * 100;
|
||||||
|
particles.resize(maxParticles);
|
||||||
|
drawDataDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SparkEmitter::initParticle(SparkParticle& particle, int emitterIndex) {
|
||||||
|
particle.velocity = getRandomVelocity(emitterIndex);
|
||||||
|
particle.scale = 1.0f;
|
||||||
|
particle.maxLifeTime = 800.0f + (rand() % 400);
|
||||||
|
particle.emitterIndex = emitterIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3f SparkEmitter::getRandomVelocity(int emitterIndex) {
|
||||||
|
static std::random_device rd;
|
||||||
|
static std::mt19937 gen(rd());
|
||||||
|
static std::uniform_real_distribution<> angleDist(0, 2 * M_PI);
|
||||||
|
static std::uniform_real_distribution<> speedDist(0.5f, 2.0f);
|
||||||
|
static std::uniform_real_distribution<> zSpeedDist(1.0f, 3.0f);
|
||||||
|
|
||||||
|
float angle = angleDist(gen);
|
||||||
|
float speed = speedDist(gen);
|
||||||
|
float zSpeed = zSpeedDist(gen);
|
||||||
|
|
||||||
|
if (emitterIndex == 0) {
|
||||||
|
return Vector3f{
|
||||||
|
cosf(angle) * speed - 0.3f,
|
||||||
|
sinf(angle) * speed,
|
||||||
|
zSpeed
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return Vector3f{
|
||||||
|
cosf(angle) * speed + 0.3f,
|
||||||
|
sinf(angle) * speed,
|
||||||
|
zSpeed
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<SparkParticle>& SparkEmitter::getParticles() const {
|
||||||
|
return particles;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t SparkEmitter::getActiveParticleCount() const {
|
||||||
|
size_t count = 0;
|
||||||
|
for (const auto& particle : particles) {
|
||||||
|
if (particle.active) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ZL
|
||||||
66
SparkEmitter.h
Normal file
66
SparkEmitter.h
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ZLMath.h"
|
||||||
|
#include "Renderer.h"
|
||||||
|
#include "TextureManager.h"
|
||||||
|
#include <vector>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
namespace ZL {
|
||||||
|
|
||||||
|
struct SparkParticle {
|
||||||
|
Vector3f position;
|
||||||
|
Vector3f velocity;
|
||||||
|
float scale;
|
||||||
|
float lifeTime;
|
||||||
|
float maxLifeTime;
|
||||||
|
bool active;
|
||||||
|
int emitterIndex;
|
||||||
|
|
||||||
|
SparkParticle() : position({ 0,0,0 }), velocity({ 0,0,0 }), scale(1.0f),
|
||||||
|
lifeTime(0), maxLifeTime(1000.0f), active(false), emitterIndex(0) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class SparkEmitter {
|
||||||
|
private:
|
||||||
|
std::vector<SparkParticle> particles;
|
||||||
|
std::vector<Vector3f> emissionPoints;
|
||||||
|
std::chrono::steady_clock::time_point lastEmissionTime;
|
||||||
|
float emissionRate;
|
||||||
|
bool isActive;
|
||||||
|
|
||||||
|
std::vector<Vector3f> drawPositions;
|
||||||
|
std::vector<Vector2f> drawTexCoords;
|
||||||
|
bool drawDataDirty;
|
||||||
|
VertexRenderStruct sparkQuad;
|
||||||
|
std::shared_ptr<Texture> texture;
|
||||||
|
|
||||||
|
int maxParticles;
|
||||||
|
|
||||||
|
void prepareDrawData();
|
||||||
|
|
||||||
|
public:
|
||||||
|
SparkEmitter();
|
||||||
|
SparkEmitter(const std::vector<Vector3f>& positions, float rate = 100.0f);
|
||||||
|
SparkEmitter(const std::vector<Vector3f>& positions,
|
||||||
|
std::shared_ptr<Texture> tex,
|
||||||
|
float rate = 100.0f);
|
||||||
|
|
||||||
|
void setEmissionPoints(const std::vector<Vector3f>& positions);
|
||||||
|
void setTexture(std::shared_ptr<Texture> tex);
|
||||||
|
|
||||||
|
void update(float deltaTimeMs);
|
||||||
|
void emit();
|
||||||
|
|
||||||
|
void draw(Renderer& renderer, float zoom, int screenWidth, int screenHeight);
|
||||||
|
|
||||||
|
const std::vector<SparkParticle>& getParticles() const;
|
||||||
|
size_t getActiveParticleCount() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void initParticle(SparkParticle& particle, int emitterIndex);
|
||||||
|
Vector3f getRandomVelocity(int emitterIndex);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ZL
|
||||||
276
StoneObject.cpp
Normal file
276
StoneObject.cpp
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
#include "StoneObject.h"
|
||||||
|
|
||||||
|
#include "Utils.h"
|
||||||
|
#include <GL/gl.h>
|
||||||
|
#include <random>
|
||||||
|
#include <cmath>
|
||||||
|
#include "Renderer.h"
|
||||||
|
#include "PlanetData.h"
|
||||||
|
|
||||||
|
namespace ZL {
|
||||||
|
|
||||||
|
|
||||||
|
// --- ÊÎÍÑÒÀÍÒÛ ÏÀÐÀÌÅÒÐÎÂ (êàê âû ïðîñèëè) ---
|
||||||
|
const float StoneParams::BASE_SCALE = 17.0f; // Îáùèé ðàçìåð êàìíÿ
|
||||||
|
//const float StoneParams::BASE_SCALE = 5000.0f; // Îáùèé ðàçìåð êàìíÿ
|
||||||
|
//const float StoneParams::MIN_AXIS_SCALE = 1.0f; // Ìèíèìàëüíîå ðàñòÿæåíèå/ñæàòèå ïî îñè
|
||||||
|
//const float StoneParams::MAX_AXIS_SCALE = 1.0f; // Ìàêñèìàëüíîå ðàñòÿæåíèå/ñæàòèå ïî îñè
|
||||||
|
//const float StoneParams::MIN_PERTURBATION = 0.0f; // Ìèíèìàëüíîå ðàäèàëüíîå âîçìóùåíèå âåðøèíû
|
||||||
|
//const float StoneParams::MAX_PERTURBATION = 0.0f; // Ìàêñèìàëüíîå ðàäèàëüíîå âîçìóùåíèå âåðøèíû
|
||||||
|
const float StoneParams::MIN_AXIS_SCALE = 0.5f; // Ìèíèìàëüíîå ðàñòÿæåíèå/ñæàòèå ïî îñè
|
||||||
|
const float StoneParams::MAX_AXIS_SCALE = 1.5f; // Ìàêñèìàëüíîå ðàñòÿæåíèå/ñæàòèå ïî îñè
|
||||||
|
const float StoneParams::MIN_PERTURBATION = 0.05f; // Ìèíèìàëüíîå ðàäèàëüíîå âîçìóùåíèå âåðøèíû
|
||||||
|
const float StoneParams::MAX_PERTURBATION = 0.25f; // Ìàêñèìàëüíîå ðàäèàëüíîå âîçìóùåíèå âåðøèíû
|
||||||
|
const int StoneParams::STONES_PER_TRIANGLE = 100;
|
||||||
|
|
||||||
|
// Âñïîìîãàòåëüíàÿ ôóíêöèÿ äëÿ ïîëó÷åíèÿ ñëó÷àéíîãî ÷èñëà â äèàïàçîíå [min, max]
|
||||||
|
float getRandomFloat(std::mt19937& gen, float min, float max) {
|
||||||
|
std::uniform_real_distribution<> distrib(min, max);
|
||||||
|
return static_cast<float>(distrib(gen));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Âñïîìîãàòåëüíàÿ ôóíêöèÿ äëÿ ãåíåðàöèè ñëó÷àéíîé òî÷êè íà òðåóãîëüíèêå
|
||||||
|
// Èñïîëüçóåò áàðèöåíòðè÷åñêèå êîîðäèíàòû
|
||||||
|
Vector3f GetRandomPointOnTriangle(const Triangle& t, std::mt19937& engine) {
|
||||||
|
std::uniform_real_distribution<> distrib(0.0f, 1.0f);
|
||||||
|
|
||||||
|
float r1 = getRandomFloat(engine, 0.0f, 1.0f);
|
||||||
|
float r2 = getRandomFloat(engine, 0.0f, 1.0f);
|
||||||
|
|
||||||
|
// Ïðåîáðàçîâàíèå r1, r2 äëÿ ïîëó÷åíèÿ ðàâíîìåðíîãî ðàñïðåäåëåíèÿ
|
||||||
|
float a = 1.0f - std::sqrt(r1);
|
||||||
|
float b = std::sqrt(r1) * r2;
|
||||||
|
float c = 1.0f - a - b; // c = sqrt(r1) * (1 - r2)
|
||||||
|
|
||||||
|
// Áàðèöåíòðè÷åñêèå êîîðäèíàòû
|
||||||
|
// P = a*p1 + b*p2 + c*p3
|
||||||
|
Vector3f p1_term = t.data[0] * a;
|
||||||
|
Vector3f p2_term = t.data[1] * b;
|
||||||
|
Vector3f p3_term = t.data[2] * c;
|
||||||
|
|
||||||
|
return p1_term + p2_term + p3_term;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Èêîñàýäð (íà îñíîâå çîëîòîãî ñå÷åíèÿ phi)
|
||||||
|
// Êîîðäèíàòû ìîãóò áûòü âû÷èñëåíû çàðàíåå äëÿ êîíñòàíòíîãî èêîñàýäðà.
|
||||||
|
// Çäåñü òîëüêî îáúÿâëåíèå, ÷òîáû ïîêàçàòü èäåþ.
|
||||||
|
|
||||||
|
VertexDataStruct CreateBaseConvexPolyhedron(uint64_t seed) {
|
||||||
|
|
||||||
|
// const size_t SUBDIVISION_LEVEL = 1; // Óðîâåíü ïîäðàçäåëåíèÿ (äëÿ áîëåå êðóãëîãî êàìíÿ, ïîêà îïóñòèì)
|
||||||
|
|
||||||
|
std::mt19937 engine(static_cast<unsigned int>(seed));
|
||||||
|
|
||||||
|
// Çîëîòîå ñå÷åíèå
|
||||||
|
const float t = (1.0f + std::sqrt(5.0f)) / 2.0f;
|
||||||
|
|
||||||
|
// 12 âåðøèí èêîñàýäðà
|
||||||
|
std::vector<Vector3f> initialVertices = {
|
||||||
|
{ -1, t, 0 }, { 1, t, 0 }, { -1, -t, 0 }, { 1, -t, 0 },
|
||||||
|
{ 0, -1, t }, { 0, 1, t }, { 0, -1, -t }, { 0, 1, -t },
|
||||||
|
{ t, 0, -1 }, { t, 0, 1 }, { -t, 0, -1 }, { -t, 0, 1 }
|
||||||
|
};
|
||||||
|
|
||||||
|
// 20 òðåóãîëüíûõ ãðàíåé (èíäåêñû âåðøèí)
|
||||||
|
std::vector<std::array<int, 3>> faces = {
|
||||||
|
// 5 òðåóãîëüíèêîâ âîêðóã âåðøèíû 0
|
||||||
|
{0, 11, 5}, {0, 5, 1}, {0, 1, 7}, {0, 7, 10}, {0, 10, 11},
|
||||||
|
// 5 ñìåæíûõ ïîëîñ
|
||||||
|
{1, 5, 9}, {5, 11, 4}, {11, 10, 2}, {10, 7, 6}, {7, 1, 8},
|
||||||
|
// 5 òðåóãîëüíèêîâ âîêðóã âåðøèíû 3
|
||||||
|
{3, 9, 4}, {3, 4, 2}, {3, 2, 6}, {3, 6, 8}, {3, 8, 9},
|
||||||
|
// 5 ñìåæíûõ ïîëîñ
|
||||||
|
{4, 9, 5}, {2, 4, 11}, {6, 2, 10}, {8, 6, 7}, {9, 8, 1}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 1. Íîðìàëèçàöèÿ è Âîçìóùåíèå (Perturbation)
|
||||||
|
for (Vector3f& v : initialVertices) {
|
||||||
|
v = v.normalized() * StoneParams::BASE_SCALE; // Íîðìàëèçàöèÿ ê ñôåðå ðàäèóñà BASE_SCALE
|
||||||
|
|
||||||
|
// Ðàäèàëüíîå âîçìóùåíèå:
|
||||||
|
float perturbation = getRandomFloat(engine, StoneParams::MIN_PERTURBATION, StoneParams::MAX_PERTURBATION);
|
||||||
|
v = v * (1.0f + perturbation);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Òðàíñôîðìàöèÿ (Ìàñøòàáèðîâàíèå è Ïîâîðîò)
|
||||||
|
|
||||||
|
// Ñëó÷àéíûå ìàñøòàáû ïî îñÿì
|
||||||
|
Vector3f scaleFactors = {
|
||||||
|
getRandomFloat(engine, StoneParams::MIN_AXIS_SCALE, StoneParams::MAX_AXIS_SCALE),
|
||||||
|
getRandomFloat(engine, StoneParams::MIN_AXIS_SCALE, StoneParams::MAX_AXIS_SCALE),
|
||||||
|
getRandomFloat(engine, StoneParams::MIN_AXIS_SCALE, StoneParams::MAX_AXIS_SCALE)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ïðèìåíÿåì ìàñøòàáèðîâàíèå
|
||||||
|
for (Vector3f& v : initialVertices) {
|
||||||
|
v.v[0] *= scaleFactors.v[0];
|
||||||
|
v.v[1] *= scaleFactors.v[1];
|
||||||
|
v.v[2] *= scaleFactors.v[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ñëó÷àéíûé ïîâîðîò (íàïðèìåð, âîêðóã òðåõ îñåé)
|
||||||
|
Vector4f qx = QuatFromRotateAroundX(getRandomFloat(engine, 0.0f, 360.0f));
|
||||||
|
Vector4f qy = QuatFromRotateAroundY(getRandomFloat(engine, 0.0f, 360.0f));
|
||||||
|
Vector4f qz = QuatFromRotateAroundZ(getRandomFloat(engine, 0.0f, 360.0f));
|
||||||
|
Vector4f qFinal = slerp(qx, qy, 0.5f); // Ïðîñòîé ïðèìåð êîìáèíèðîâàíèÿ
|
||||||
|
qFinal = slerp(qFinal, qz, 0.5f).normalized();
|
||||||
|
Matrix3f rotationMatrix = QuatToMatrix(qFinal);
|
||||||
|
|
||||||
|
for (Vector3f& v : initialVertices) {
|
||||||
|
v = MultMatrixVector(rotationMatrix, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Ñãëàæåííûå Íîðìàëè è Ôîðìèðîâàíèå Mesh
|
||||||
|
VertexDataStruct result;
|
||||||
|
// Êàðòà äëÿ íàêîïëåíèÿ íîðìàëåé ïî óíèêàëüíûì ïîçèöèÿì âåðøèí
|
||||||
|
// (Òðåáóåò îïðåäåëåííîãî îïåðàòîðà < äëÿ Vector3f â ZLMath.h, êîòîðûé ó âàñ åñòü)
|
||||||
|
std::map<Vector3f, Vector3f> smoothNormalsMap;
|
||||||
|
|
||||||
|
// Ïðåäâàðèòåëüíîå çàïîëíåíèå êàðòû íîðìàëÿìè
|
||||||
|
for (const auto& face : faces) {
|
||||||
|
Vector3f p1 = initialVertices[face[0]];
|
||||||
|
Vector3f p2 = initialVertices[face[1]];
|
||||||
|
Vector3f p3 = initialVertices[face[2]];
|
||||||
|
|
||||||
|
// Íîðìàëü ãðàíè: (p2 - p1) x (p3 - p1)
|
||||||
|
Vector3f faceNormal = (p2 - p1).cross(p3 - p1).normalized();
|
||||||
|
|
||||||
|
smoothNormalsMap[p1] = smoothNormalsMap[p1] + faceNormal;
|
||||||
|
smoothNormalsMap[p2] = smoothNormalsMap[p2] + faceNormal;
|
||||||
|
smoothNormalsMap[p3] = smoothNormalsMap[p3] + faceNormal;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Íîðìàëèçàöèÿ íàêîïëåííûõ íîðìàëåé
|
||||||
|
for (auto& pair : smoothNormalsMap) {
|
||||||
|
pair.second = pair.second.normalized();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 4. Ôèíàëüíîå çàïîëíåíèå VertexDataStruct è Òåêñòóðíûå Êîîðäèíàòû
|
||||||
|
for (const auto& face : faces) {
|
||||||
|
Vector3f p1 = initialVertices[face[0]];
|
||||||
|
Vector3f p2 = initialVertices[face[1]];
|
||||||
|
Vector3f p3 = initialVertices[face[2]];
|
||||||
|
|
||||||
|
// Ïîçèöèè
|
||||||
|
result.PositionData.push_back(p1);
|
||||||
|
result.PositionData.push_back(p2);
|
||||||
|
result.PositionData.push_back(p3);
|
||||||
|
|
||||||
|
// Ñãëàæåííûå Íîðìàëè (èç êàðòû)
|
||||||
|
result.NormalData.push_back(smoothNormalsMap[p1]);
|
||||||
|
result.NormalData.push_back(smoothNormalsMap[p2]);
|
||||||
|
result.NormalData.push_back(smoothNormalsMap[p3]);
|
||||||
|
|
||||||
|
// Òåêñòóðíûå Êîîðäèíàòû (Ïëàíàðíàÿ ïðîåêöèÿ íà ïëîñêîñòü ãðàíè)
|
||||||
|
// p1 -> (0, 0), p2 -> (L_12, 0), p3 -> (L_13 * cos(angle), L_13 * sin(angle))
|
||||||
|
// Ãäå L_xy - äëèíà âåêòîðà, angle - óãîë ìåæäó p2-p1 è p3-p1
|
||||||
|
|
||||||
|
Vector3f uAxis = (p2 - p1).normalized();
|
||||||
|
Vector3f vRaw = p3 - p1;
|
||||||
|
|
||||||
|
// Ïðîåêöèÿ vRaw íà uAxis
|
||||||
|
float uProjLen = vRaw.dot(uAxis);
|
||||||
|
|
||||||
|
// Âåêòîð V ïåðïåíäèêóëÿðíûé U: vRaw - uProj
|
||||||
|
Vector3f vAxisRaw = vRaw - (uAxis * uProjLen);
|
||||||
|
|
||||||
|
// Äëèíà îñè V
|
||||||
|
float vLen = vAxisRaw.length();
|
||||||
|
|
||||||
|
// Íîðìàëèçîâàííàÿ îñü V
|
||||||
|
Vector3f vAxis = vAxisRaw.normalized();
|
||||||
|
|
||||||
|
// Êîîðäèíàòû (u, v) äëÿ p1, p2, p3 îòíîñèòåëüíî p1
|
||||||
|
Vector2f uv1 = { 0.0f, 0.0f };
|
||||||
|
Vector2f uv2 = { (p2 - p1).length(), 0.0f }; // p2-p1 âäîëü îñè U
|
||||||
|
Vector2f uv3 = { uProjLen, vLen }; // p3-p1: u-êîìïîíåíòà = uProjLen, v-êîìïîíåíòà = vLen
|
||||||
|
|
||||||
|
// Íàõîäèì ìàêñèìàëüíûé ðàçìåð, ÷òîáû ìàñøòàáèðîâàòü â [0, 1]
|
||||||
|
float maxUV = max(uv2.v[0], max(uv3.v[0], uv3.v[1]));
|
||||||
|
|
||||||
|
if (maxUV > 0.000001f) {
|
||||||
|
// Ìàñøòàáèðóåì:
|
||||||
|
result.TexCoordData.push_back(uv1);
|
||||||
|
result.TexCoordData.push_back(uv2 * (1.f / maxUV));
|
||||||
|
result.TexCoordData.push_back(uv3 * (1.f / maxUV));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Ïðåäîòâðàùåíèå äåëåíèÿ íà íîëü äëÿ âûðîæäåííûõ ãðàíåé
|
||||||
|
result.TexCoordData.push_back({ 0.0f, 0.0f });
|
||||||
|
result.TexCoordData.push_back({ 0.0f, 0.0f });
|
||||||
|
result.TexCoordData.push_back({ 0.0f, 0.0f });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
StoneGroup CreateStoneGroupData(uint64_t globalSeed, const LodLevel& planetLodLevel) {
|
||||||
|
StoneGroup group;
|
||||||
|
|
||||||
|
// Ðåçåðâèðóåì ìåñòî ïîä âñå òðåóãîëüíèêè òåêóùåãî LOD
|
||||||
|
group.allInstances.resize(planetLodLevel.triangles.size());
|
||||||
|
|
||||||
|
for (size_t tIdx = 0; tIdx < planetLodLevel.triangles.size(); ++tIdx) {
|
||||||
|
const Triangle& tri = planetLodLevel.triangles[tIdx];
|
||||||
|
std::mt19937 engine(static_cast<unsigned int>(globalSeed));
|
||||||
|
|
||||||
|
for (int i = 0; i < StoneParams::STONES_PER_TRIANGLE; ++i) {
|
||||||
|
StoneInstance instance;
|
||||||
|
instance.seed = globalSeed;// + tIdx * 1000 + i; // Óíèêàëüíûé ñèä äëÿ êàæäîãî êàìíÿ
|
||||||
|
|
||||||
|
instance.position = GetRandomPointOnTriangle(tri, engine);
|
||||||
|
|
||||||
|
//instance.position = Vector3f(5000.0f, 5000.0f, 5000.0f);
|
||||||
|
// Ãåíåðèðóåì ñëó÷àéíûå ïàðàìåòðû îäèí ðàç
|
||||||
|
instance.scale = {
|
||||||
|
getRandomFloat(engine, 0.5f, 1.5f),
|
||||||
|
getRandomFloat(engine, 0.5f, 1.5f),
|
||||||
|
getRandomFloat(engine, 0.5f, 1.5f)
|
||||||
|
};
|
||||||
|
|
||||||
|
Vector4f qx = QuatFromRotateAroundX(getRandomFloat(engine, 0.0f, 360.0f));
|
||||||
|
Vector4f qy = QuatFromRotateAroundY(getRandomFloat(engine, 0.0f, 360.0f));
|
||||||
|
Vector4f qz = QuatFromRotateAroundZ(getRandomFloat(engine, 0.0f, 360.0f));
|
||||||
|
instance.rotation = slerp(slerp(qx, qy, 0.5f), qz, 0.5f).normalized();
|
||||||
|
|
||||||
|
group.allInstances[tIdx].push_back(instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
void StoneGroup::inflate(const std::vector<int>& triangleIndices) {
|
||||||
|
// 1. Î÷èùàåì òåêóùèé ìåø ïåðåä çàïîëíåíèåì
|
||||||
|
mesh.PositionData.clear();
|
||||||
|
mesh.NormalData.clear();
|
||||||
|
mesh.TexCoordData.clear();
|
||||||
|
|
||||||
|
static VertexDataStruct baseStone = CreateBaseConvexPolyhedron(1337);
|
||||||
|
|
||||||
|
// 2. Íàïîëíÿåì ìåø òîëüêî äëÿ âèäèìûõ òðåóãîëüíèêîâ
|
||||||
|
for (int tIdx : triangleIndices) {
|
||||||
|
if (tIdx >= allInstances.size()) continue;
|
||||||
|
|
||||||
|
for (const auto& inst : allInstances[tIdx]) {
|
||||||
|
Matrix3f rotMat = QuatToMatrix(inst.rotation);
|
||||||
|
|
||||||
|
for (size_t j = 0; j < baseStone.PositionData.size(); ++j) {
|
||||||
|
Vector3f p = baseStone.PositionData[j];
|
||||||
|
Vector3f n = baseStone.NormalData[j];
|
||||||
|
|
||||||
|
// Ìàñøòàá -> Ïîâîðîò -> Ñìåùåíèå
|
||||||
|
p.v[0] *= inst.scale.v[0];
|
||||||
|
p.v[1] *= inst.scale.v[1];
|
||||||
|
p.v[2] *= inst.scale.v[2];
|
||||||
|
|
||||||
|
mesh.PositionData.push_back(MultMatrixVector(rotMat, p) + inst.position);
|
||||||
|
mesh.NormalData.push_back(MultMatrixVector(rotMat, n));
|
||||||
|
mesh.TexCoordData.push_back(baseStone.TexCoordData[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace ZL
|
||||||
41
StoneObject.h
Normal file
41
StoneObject.h
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "ZLMath.h"
|
||||||
|
#include "Renderer.h"
|
||||||
|
#include "PlanetData.h"
|
||||||
|
|
||||||
|
namespace ZL {
|
||||||
|
|
||||||
|
struct StoneParams
|
||||||
|
{
|
||||||
|
static const float BASE_SCALE; // Îáùèé ðàçìåð êàìíÿ
|
||||||
|
static const float MIN_AXIS_SCALE; // Ìèíèìàëüíîå ðàñòÿæåíèå/ñæàòèå ïî îñè
|
||||||
|
static const float MAX_AXIS_SCALE; // Ìàêñèìàëüíîå ðàñòÿæåíèå/ñæàòèå ïî îñè
|
||||||
|
static const float MIN_PERTURBATION; // Ìèíèìàëüíîå ðàäèàëüíîå âîçìóùåíèå âåðøèíû
|
||||||
|
static const float MAX_PERTURBATION; // Ìàêñèìàëüíîå ðàäèàëüíîå âîçìóùåíèå âåðøèíû
|
||||||
|
static const int STONES_PER_TRIANGLE;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StoneInstance {
|
||||||
|
uint64_t seed;
|
||||||
|
Vector3f position;
|
||||||
|
Vector3f scale;
|
||||||
|
Vector4f rotation;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StoneGroup {
|
||||||
|
// mesh.PositionData è ïðî÷èå áóäóò çàïîëíÿòüñÿ â inflate()
|
||||||
|
VertexDataStruct mesh;
|
||||||
|
|
||||||
|
// Âíåøíèé âåêòîð — èíäåêñ òðåóãîëüíèêà ïëàíåòû,
|
||||||
|
// âíóòðåííèé — ñïèñîê êàìíåé íà ýòîì òðåóãîëüíèêå
|
||||||
|
std::vector<std::vector<StoneInstance>> allInstances;
|
||||||
|
|
||||||
|
// Î÷èùàåò ñòàðóþ ãåîìåòðèþ è ãåíåðèðóåò íîâóþ äëÿ óêàçàííûõ èíäåêñîâ
|
||||||
|
void inflate(const std::vector<int>& triangleIndices);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Òåïåðü âîçâðàùàåò çàãîòîâêó ñî âñåìè ïàðàìåòðàìè, íî áåç òÿæåëîãî ìåøà
|
||||||
|
StoneGroup CreateStoneGroupData(uint64_t globalSeed, const LodLevel& planetLodLevel);
|
||||||
|
|
||||||
|
} // namespace ZL
|
||||||
390
TextModel.cpp
Normal file
390
TextModel.cpp
Normal file
@ -0,0 +1,390 @@
|
|||||||
|
#include "TextModel.h"
|
||||||
|
#include <regex>
|
||||||
|
#include <string>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
namespace ZL
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
VertexDataStruct LoadFromTextFile(const std::string& fileName, const std::string& ZIPFileName)
|
||||||
|
{
|
||||||
|
VertexDataStruct result;
|
||||||
|
std::ifstream filestream;
|
||||||
|
std::istringstream zipStream;
|
||||||
|
|
||||||
|
if (!ZIPFileName.empty())
|
||||||
|
{
|
||||||
|
std::vector<char> fileData = readFileFromZIP(fileName, ZIPFileName);
|
||||||
|
std::string fileContents(fileData.begin(), fileData.end());
|
||||||
|
zipStream.str(fileContents);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
filestream.open(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Создаем ссылку f на нужный поток – после этого код ниже остается без изменений
|
||||||
|
std::istream& f = (!ZIPFileName.empty()) ? static_cast<std::istream&>(zipStream) : static_cast<std::istream&>(filestream);
|
||||||
|
|
||||||
|
|
||||||
|
//Skip first 5 lines
|
||||||
|
std::string tempLine;
|
||||||
|
|
||||||
|
std::getline(f, tempLine);
|
||||||
|
|
||||||
|
static const std::regex pattern_count(R"(\d+)");
|
||||||
|
static const std::regex pattern_float(R"([-]?\d+\.\d+)");
|
||||||
|
static const std::regex pattern_int(R"([-]?\d+)");
|
||||||
|
|
||||||
|
|
||||||
|
std::smatch match;
|
||||||
|
|
||||||
|
int numberVertices;
|
||||||
|
|
||||||
|
if (std::regex_search(tempLine, match, pattern_count)) {
|
||||||
|
std::string number_str = match.str();
|
||||||
|
numberVertices = std::stoi(number_str);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw std::runtime_error("No number found in the input string.");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Vector3f> vertices;
|
||||||
|
|
||||||
|
vertices.resize(numberVertices);
|
||||||
|
for (int i = 0; i < numberVertices; i++)
|
||||||
|
{
|
||||||
|
std::getline(f, tempLine);
|
||||||
|
|
||||||
|
std::vector<float> floatValues;
|
||||||
|
|
||||||
|
auto b = tempLine.cbegin();
|
||||||
|
auto e = tempLine.cend();
|
||||||
|
while (std::regex_search(b, e, match, pattern_float)) {
|
||||||
|
floatValues.push_back(std::stof(match.str()));
|
||||||
|
b = match.suffix().first;
|
||||||
|
}
|
||||||
|
|
||||||
|
vertices[i] = Vector3f{ floatValues[0], floatValues[1], floatValues[2] };
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "UV Coordinates" << std::endl;
|
||||||
|
|
||||||
|
std::getline(f, tempLine); //===UV Coordinates:
|
||||||
|
|
||||||
|
std::getline(f, tempLine); //triangle count
|
||||||
|
int numberTriangles;
|
||||||
|
|
||||||
|
if (std::regex_search(tempLine, match, pattern_count)) {
|
||||||
|
std::string number_str = match.str();
|
||||||
|
numberTriangles = std::stoi(number_str);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw std::runtime_error("No number found in the input string.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Now process UVs
|
||||||
|
std::vector<std::array<Vector2f, 3>> uvCoords;
|
||||||
|
|
||||||
|
uvCoords.resize(numberTriangles);
|
||||||
|
|
||||||
|
for (int i = 0; i < numberTriangles; i++)
|
||||||
|
{
|
||||||
|
std::getline(f, tempLine); //Face 0
|
||||||
|
|
||||||
|
int uvCount;
|
||||||
|
std::getline(f, tempLine);
|
||||||
|
if (std::regex_search(tempLine, match, pattern_count)) {
|
||||||
|
std::string number_str = match.str();
|
||||||
|
uvCount = std::stoi(number_str);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw std::runtime_error("No number found in the input string.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uvCount != 3)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("more than 3 uvs");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<float> floatValues;
|
||||||
|
|
||||||
|
for (int j = 0; j < 3; j++)
|
||||||
|
{
|
||||||
|
std::getline(f, tempLine); //UV <Vector (-0.3661, -1.1665)>
|
||||||
|
|
||||||
|
auto b = tempLine.cbegin();
|
||||||
|
auto e = tempLine.cend();
|
||||||
|
floatValues.clear();
|
||||||
|
while (std::regex_search(b, e, match, pattern_float)) {
|
||||||
|
floatValues.push_back(std::stof(match.str()));
|
||||||
|
b = match.suffix().first;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (floatValues.size() != 2)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("more than 2 uvs---");
|
||||||
|
}
|
||||||
|
|
||||||
|
uvCoords[i][j] = Vector2f{ floatValues[0],floatValues[1] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "Normals go" << std::endl;
|
||||||
|
|
||||||
|
std::getline(f, tempLine); //===Normals:
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<Vector3f> normals;
|
||||||
|
|
||||||
|
normals.resize(numberVertices);
|
||||||
|
for (int i = 0; i < numberVertices; i++)
|
||||||
|
{
|
||||||
|
std::getline(f, tempLine);
|
||||||
|
|
||||||
|
std::vector<float> floatValues;
|
||||||
|
|
||||||
|
auto b = tempLine.cbegin();
|
||||||
|
auto e = tempLine.cend();
|
||||||
|
while (std::regex_search(b, e, match, pattern_float)) {
|
||||||
|
floatValues.push_back(std::stof(match.str()));
|
||||||
|
b = match.suffix().first;
|
||||||
|
}
|
||||||
|
|
||||||
|
normals[i] = Vector3f{ floatValues[0], floatValues[1], floatValues[2] };
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "Triangles go:" << std::endl;
|
||||||
|
|
||||||
|
std::getline(f, tempLine); //===Triangles: 3974
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<std::array<int, 3>> triangles;
|
||||||
|
|
||||||
|
triangles.resize(numberTriangles);
|
||||||
|
for (int i = 0; i < numberTriangles; i++)
|
||||||
|
{
|
||||||
|
std::getline(f, tempLine);
|
||||||
|
|
||||||
|
std::vector<int> intValues;
|
||||||
|
|
||||||
|
auto b = tempLine.cbegin();
|
||||||
|
auto e = tempLine.cend();
|
||||||
|
while (std::regex_search(b, e, match, pattern_int)) {
|
||||||
|
intValues.push_back(std::stoi(match.str()));
|
||||||
|
b = match.suffix().first;
|
||||||
|
}
|
||||||
|
|
||||||
|
triangles[i] = { intValues[0], intValues[1], intValues[2] };
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::cout << "Process vertices" << std::endl;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Now let's process vertices
|
||||||
|
|
||||||
|
for (int i = 0; i < numberTriangles; i++)
|
||||||
|
{
|
||||||
|
|
||||||
|
result.PositionData.push_back(vertices[triangles[i][0]]);
|
||||||
|
result.PositionData.push_back(vertices[triangles[i][1]]);
|
||||||
|
result.PositionData.push_back(vertices[triangles[i][2]]);
|
||||||
|
|
||||||
|
/*
|
||||||
|
result.NormalData.push_back(normals[triangles[i][0]]);
|
||||||
|
result.NormalData.push_back(normals[triangles[i][1]]);
|
||||||
|
result.NormalData.push_back(normals[triangles[i][2]]);
|
||||||
|
*/
|
||||||
|
result.TexCoordData.push_back(uvCoords[i][0]);
|
||||||
|
result.TexCoordData.push_back(uvCoords[i][1]);
|
||||||
|
result.TexCoordData.push_back(uvCoords[i][2]);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//Swap from Blender format to OpenGL format
|
||||||
|
for (int i = 0; i < result.PositionData.size(); i++)
|
||||||
|
{
|
||||||
|
Vector3f tempVec = result.PositionData[i];
|
||||||
|
result.PositionData[i].v[0] = tempVec.v[1];
|
||||||
|
result.PositionData[i].v[1] = tempVec.v[2];
|
||||||
|
result.PositionData[i].v[2] = tempVec.v[0];
|
||||||
|
|
||||||
|
/*
|
||||||
|
tempVec = result.NormalData[i];
|
||||||
|
result.NormalData[i].v[0] = tempVec.v[1];
|
||||||
|
result.NormalData[i].v[1] = tempVec.v[2];
|
||||||
|
result.NormalData[i].v[2] = tempVec.v[0];*/
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
VertexDataStruct LoadFromTextFile02(const std::string& fileName, const std::string& ZIPFileName)
|
||||||
|
{
|
||||||
|
VertexDataStruct result;
|
||||||
|
std::ifstream filestream;
|
||||||
|
std::istringstream zipStream;
|
||||||
|
|
||||||
|
// --- 1. Открытие потока (без изменений) ---
|
||||||
|
if (!ZIPFileName.empty())
|
||||||
|
{
|
||||||
|
std::vector<char> fileData = readFileFromZIP(fileName, ZIPFileName);
|
||||||
|
std::string fileContents(fileData.begin(), fileData.end());
|
||||||
|
zipStream.str(fileContents);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
filestream.open(fileName);
|
||||||
|
if (!filestream.is_open()) {
|
||||||
|
throw std::runtime_error("Failed to open file: " + fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::istream& f = (!ZIPFileName.empty()) ? static_cast<std::istream&>(zipStream) : static_cast<std::istream&>(filestream);
|
||||||
|
|
||||||
|
std::string tempLine;
|
||||||
|
std::smatch match;
|
||||||
|
|
||||||
|
// Обновленные регулярки
|
||||||
|
// pattern_float стал чуть надежнее для чисел вида "0" или "-1" без точки, если вдруг Python округлит до int
|
||||||
|
static const std::regex pattern_count(R"(\d+)");
|
||||||
|
static const std::regex pattern_float(R"([-]?\d+(\.\d+)?)");
|
||||||
|
static const std::regex pattern_int(R"([-]?\d+)");
|
||||||
|
|
||||||
|
// --- 2. Парсинг Вершин (Pos + Norm + UV) ---
|
||||||
|
|
||||||
|
// Ищем заголовок ===Vertices
|
||||||
|
while (std::getline(f, tempLine)) {
|
||||||
|
if (tempLine.find("===Vertices") != std::string::npos) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
int numberVertices = 0;
|
||||||
|
if (std::regex_search(tempLine, match, pattern_count)) {
|
||||||
|
numberVertices = std::stoi(match.str());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw std::runtime_error("Vertices header not found or invalid.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Временные буферы для хранения "уникальных" вершин перед разверткой по индексам
|
||||||
|
std::vector<Vector3f> tempPositions(numberVertices);
|
||||||
|
std::vector<Vector3f> tempNormals(numberVertices);
|
||||||
|
std::vector<Vector2f> tempUVs(numberVertices);
|
||||||
|
|
||||||
|
for (int i = 0; i < numberVertices; i++)
|
||||||
|
{
|
||||||
|
std::getline(f, tempLine);
|
||||||
|
// Строка вида: V 0: Pos(x, y, z) Norm(x, y, z) UV(u, v)
|
||||||
|
|
||||||
|
std::vector<float> floatValues;
|
||||||
|
floatValues.reserve(8); // Ожидаем ровно 8 чисел (3 pos + 3 norm + 2 uv)
|
||||||
|
|
||||||
|
auto b = tempLine.cbegin();
|
||||||
|
auto e = tempLine.cend();
|
||||||
|
while (std::regex_search(b, e, match, pattern_float)) {
|
||||||
|
floatValues.push_back(std::stof(match.str()));
|
||||||
|
b = match.suffix().first;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверка целостности строки (ID вершины regex может поймать первым, но нас интересуют данные)
|
||||||
|
// Обычно ID идет первым (0), потом 3+3+2 float. Итого 9 чисел, если считать ID.
|
||||||
|
// Ваш Python пишет "V 0:", regex поймает 0. Потом 8 флоатов.
|
||||||
|
|
||||||
|
// Если regex ловит ID вершины как float (что вероятно), нам нужно смещение.
|
||||||
|
// ID - floatValues[0]
|
||||||
|
// Pos - [1], [2], [3]
|
||||||
|
// Norm - [4], [5], [6]
|
||||||
|
// UV - [7], [8]
|
||||||
|
|
||||||
|
if (floatValues.size() < 9) {
|
||||||
|
throw std::runtime_error("Malformed vertex line at index " + std::to_string(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
tempPositions[i] = Vector3f{ floatValues[1], floatValues[2], floatValues[3] };
|
||||||
|
tempNormals[i] = Vector3f{ floatValues[4], floatValues[5], floatValues[6] };
|
||||||
|
tempUVs[i] = Vector2f{ floatValues[7], floatValues[8] };
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 3. Парсинг Треугольников (Индексов) ---
|
||||||
|
|
||||||
|
// Пропускаем пустые строки до заголовка треугольников
|
||||||
|
while (std::getline(f, tempLine)) {
|
||||||
|
if (tempLine.find("===Triangles") != std::string::npos) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
int numberTriangles = 0;
|
||||||
|
if (std::regex_search(tempLine, match, pattern_count)) {
|
||||||
|
numberTriangles = std::stoi(match.str());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw std::runtime_error("Triangles header not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Резервируем память в result, чтобы избежать лишних аллокаций
|
||||||
|
result.PositionData.reserve(numberTriangles * 3);
|
||||||
|
result.NormalData.reserve(numberTriangles * 3);
|
||||||
|
result.TexCoordData.reserve(numberTriangles * 3);
|
||||||
|
|
||||||
|
for (int i = 0; i < numberTriangles; i++)
|
||||||
|
{
|
||||||
|
std::getline(f, tempLine);
|
||||||
|
// Строка вида: Tri: 0 1 2
|
||||||
|
|
||||||
|
std::vector<int> indices;
|
||||||
|
indices.reserve(3);
|
||||||
|
|
||||||
|
auto b = tempLine.cbegin();
|
||||||
|
auto e = tempLine.cend();
|
||||||
|
while (std::regex_search(b, e, match, pattern_int)) {
|
||||||
|
indices.push_back(std::stoi(match.str()));
|
||||||
|
b = match.suffix().first;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (indices.size() != 3) {
|
||||||
|
throw std::runtime_error("Malformed triangle line at index " + std::to_string(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 4. Заполнение VertexDataStruct (Flattening) ---
|
||||||
|
// Берем данные из временных буферов по индексам и кладем в итоговый массив
|
||||||
|
|
||||||
|
for (int k = 0; k < 3; k++) {
|
||||||
|
int idx = indices[k];
|
||||||
|
result.PositionData.push_back(tempPositions[idx]);
|
||||||
|
result.NormalData.push_back(tempNormals[idx]);
|
||||||
|
result.TexCoordData.push_back(tempUVs[idx]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 5. Конвертация координат (Blender -> OpenGL/Engine) ---
|
||||||
|
// Сохраняем вашу логику смены осей: X->Z, Y->X, Z->Y
|
||||||
|
|
||||||
|
for (size_t i = 0; i < result.PositionData.size(); i++)
|
||||||
|
{
|
||||||
|
Vector3f originalPos = result.PositionData[i];
|
||||||
|
result.PositionData[i].v[0] = originalPos.v[1]; // New X = Old Y
|
||||||
|
result.PositionData[i].v[1] = originalPos.v[2]; // New Y = Old Z
|
||||||
|
result.PositionData[i].v[2] = originalPos.v[0]; // New Z = Old X
|
||||||
|
|
||||||
|
Vector3f originalNorm = result.NormalData[i];
|
||||||
|
result.NormalData[i].v[0] = originalNorm.v[1];
|
||||||
|
result.NormalData[i].v[1] = originalNorm.v[2];
|
||||||
|
result.NormalData[i].v[2] = originalNorm.v[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "Model loaded: " << numberVertices << " verts, " << numberTriangles << " tris." << std::endl;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
12
TextModel.h
Normal file
12
TextModel.h
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ZLMath.h"
|
||||||
|
#include "Renderer.h"
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
|
||||||
|
namespace ZL
|
||||||
|
{
|
||||||
|
VertexDataStruct LoadFromTextFile(const std::string& fileName, const std::string& ZIPFileName = "");
|
||||||
|
VertexDataStruct LoadFromTextFile02(const std::string& fileName, const std::string& ZIPFileName = "");
|
||||||
|
}
|
||||||
430
TextureManager.cpp
Executable file
430
TextureManager.cpp
Executable file
@ -0,0 +1,430 @@
|
|||||||
|
#include "TextureManager.h"
|
||||||
|
#ifdef PNG_ENABLED
|
||||||
|
#include "png.h"
|
||||||
|
#endif
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace ZL
|
||||||
|
{
|
||||||
|
|
||||||
|
Texture::Texture(const TextureDataStruct& texData)
|
||||||
|
{
|
||||||
|
|
||||||
|
width = texData.width;
|
||||||
|
height = texData.height;
|
||||||
|
|
||||||
|
glGenTextures(1, &texID);
|
||||||
|
|
||||||
|
if (texID == 0)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("glGenTextures did not work");
|
||||||
|
}
|
||||||
|
|
||||||
|
glBindTexture(GL_TEXTURE_2D, texID);
|
||||||
|
|
||||||
|
CheckGlError();
|
||||||
|
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
|
|
||||||
|
CheckGlError();
|
||||||
|
|
||||||
|
//This should be only for Windows
|
||||||
|
//glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
|
||||||
|
|
||||||
|
CheckGlError();
|
||||||
|
|
||||||
|
if (texData.bitSize == TextureDataStruct::BS_24BIT)
|
||||||
|
{
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, static_cast<GLsizei>(texData.width), static_cast<GLsizei>(texData.height), 0, GL_RGB, GL_UNSIGNED_BYTE, &texData.data[0]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, static_cast<GLsizei>(texData.width), static_cast<GLsizei>(texData.height), 0, GL_RGBA, GL_UNSIGNED_BYTE, &texData.data[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckGlError();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Texture::Texture(const std::array<TextureDataStruct, 6>& texDataArray)
|
||||||
|
{
|
||||||
|
// Ïðîâåðêà, ÷òî âñå ãðàíè èìåþò îäèíàêîâûå ðàçìåðû
|
||||||
|
width = texDataArray[0].width;
|
||||||
|
height = texDataArray[0].height;
|
||||||
|
|
||||||
|
for (size_t i = 1; i < 6; ++i) {
|
||||||
|
if (texDataArray[i].width != width || texDataArray[i].height != height) {
|
||||||
|
throw std::runtime_error("Cubemap faces must have the same dimensions");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
glGenTextures(1, &texID);
|
||||||
|
|
||||||
|
if (texID == 0)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("glGenTextures did not work for cubemap");
|
||||||
|
}
|
||||||
|
|
||||||
|
glBindTexture(GL_TEXTURE_CUBE_MAP, texID);
|
||||||
|
|
||||||
|
CheckGlError();
|
||||||
|
|
||||||
|
// Íàñòðîéêà ïàðàìåòðîâ äëÿ Cubemap
|
||||||
|
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
|
// Èñïîëüçóåì GL_LINEAR äëÿ MIN_FILTER, òàê êàê ìèïìàïû çäåñü íå ãåíåðèðóþòñÿ
|
||||||
|
// Åñëè áû èñïîëüçîâàëèñü ìèïìàïû (e.g., GL_LINEAR_MIPMAP_LINEAR), íóæíî áûëî áû âûçâàòü glGenerateMipmap.
|
||||||
|
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
|
|
||||||
|
// Îáÿçàòåëüíûå ïàðàìåòðû îáåðòêè äëÿ Cubemap
|
||||||
|
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||||
|
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||||
|
|
||||||
|
// GL_TEXTURE_WRAP_R íå ïîääåðæèâàåòñÿ â WebGL 1.0/OpenGL ES 2.0 è âûçûâàåò îøèáêó.
|
||||||
|
// Îãðàíè÷èâàåì åãî âûçîâ òîëüêî äëÿ íàñòîëüíûõ ïëàòôîðì.
|
||||||
|
#ifndef EMSCRIPTEN
|
||||||
|
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
CheckGlError(); // Ïðîâåðêà ïîñëå óñòàíîâêè ïàðàìåòðîâ
|
||||||
|
|
||||||
|
// Çàãðóçêà äàííûõ äëÿ êàæäîé èç 6 ãðàíåé
|
||||||
|
// GL_TEXTURE_CUBE_MAP_POSITIVE_X + i äàåò ãðàíè: +X (0), -X (1), +Y (2), -Y (3), +Z (4), -Z (5)
|
||||||
|
for (int i = 0; i < 6; ++i)
|
||||||
|
{
|
||||||
|
GLint internalFormat;
|
||||||
|
GLenum format;
|
||||||
|
|
||||||
|
// Â WebGL 1.0/OpenGL ES 2.0 âíóòðåííèé ôîðìàò (internalFormat)
|
||||||
|
// äîëæåí ñòðîãî ñîîòâåòñòâîâàòü ôîðìàòó äàííûõ (format).
|
||||||
|
if (texDataArray[i].bitSize == TextureDataStruct::BS_24BIT)
|
||||||
|
{
|
||||||
|
internalFormat = GL_RGB; // internalFormat
|
||||||
|
format = GL_RGB; // format
|
||||||
|
}
|
||||||
|
else // BS_32BIT
|
||||||
|
{
|
||||||
|
internalFormat = GL_RGBA; // internalFormat
|
||||||
|
format = GL_RGBA; // format
|
||||||
|
}
|
||||||
|
|
||||||
|
glTexImage2D(
|
||||||
|
GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, // Öåëåâàÿ ãðàíü
|
||||||
|
0, // Óðîâåíü MIP-òåêñòóðû
|
||||||
|
internalFormat, // Âíóòðåííèé ôîðìàò (äîëæåí ñîâïàäàòü ñ ôîðìàòîì)
|
||||||
|
static_cast<GLsizei>(width),
|
||||||
|
static_cast<GLsizei>(height),
|
||||||
|
0, // Ãðàíèöà (âñåãäà 0)
|
||||||
|
format, // Ôîðìàò èñõîäíûõ äàííûõ
|
||||||
|
GL_UNSIGNED_BYTE, // Òèï äàííûõ
|
||||||
|
texDataArray[i].data.data() // Óêàçàòåëü íà äàííûå
|
||||||
|
);
|
||||||
|
CheckGlError();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ñíèìàåì ïðèâÿçêó äëÿ ÷èñòîòû
|
||||||
|
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Texture::~Texture()
|
||||||
|
{
|
||||||
|
glDeleteTextures(1, &texID);
|
||||||
|
texID = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
GLuint Texture::getTexID()
|
||||||
|
{
|
||||||
|
return texID;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Texture::getWidth()
|
||||||
|
{
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Texture::getHeight()
|
||||||
|
{
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
TextureDataStruct CreateTextureDataFromBmp24(const std::string& fullFileName, const std::string& ZIPFileName)
|
||||||
|
{
|
||||||
|
|
||||||
|
TextureDataStruct texData;
|
||||||
|
std::vector<char> fileArr;
|
||||||
|
|
||||||
|
fileArr = !ZIPFileName.empty() ? readFileFromZIP(fullFileName, ZIPFileName) : readFile(fullFileName);
|
||||||
|
|
||||||
|
size_t fileSize = fileArr.size();
|
||||||
|
|
||||||
|
if (fileSize < 22)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("File is too short or not correct!");
|
||||||
|
}
|
||||||
|
|
||||||
|
//This refers to BITMAPV5HEADER
|
||||||
|
texData.width = *reinterpret_cast<uint32_t*>(&fileArr[18]);
|
||||||
|
texData.height = *reinterpret_cast<uint32_t*>(&fileArr[22]);
|
||||||
|
|
||||||
|
texData.bitSize = TextureDataStruct::BS_24BIT;
|
||||||
|
|
||||||
|
size_t dataSize = texData.width * texData.height * 3;
|
||||||
|
|
||||||
|
texData.data.resize(dataSize);
|
||||||
|
|
||||||
|
size_t pos = *reinterpret_cast<uint32_t*>(&fileArr[10]);
|
||||||
|
size_t x = 0;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < texData.width; i++)
|
||||||
|
for (size_t j = 0; j < texData.height; j++)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (pos + 3 > fileSize)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("File is too short!");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
x = (i * texData.height + j) + (i * texData.height + j) + (i * texData.height + j);
|
||||||
|
|
||||||
|
texData.data[x + 2] = fileArr[pos++];
|
||||||
|
texData.data[x + 1] = fileArr[pos++];
|
||||||
|
texData.data[x + 0] = fileArr[pos++];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return texData;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextureDataStruct CreateTextureDataFromBmp32(const std::string& fullFileName, const std::string& ZIPFileName)
|
||||||
|
{
|
||||||
|
|
||||||
|
TextureDataStruct texData;
|
||||||
|
std::vector<char> fileArr;
|
||||||
|
|
||||||
|
fileArr = !ZIPFileName.empty() ? readFileFromZIP(fullFileName, ZIPFileName) : readFile(fullFileName);
|
||||||
|
|
||||||
|
size_t fileSize = fileArr.size();
|
||||||
|
|
||||||
|
if (fileSize < 22)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("File is too short or not correct!");
|
||||||
|
}
|
||||||
|
|
||||||
|
//This refers to BITMAPV5HEADER
|
||||||
|
texData.width = *reinterpret_cast<uint32_t*>(&fileArr[18]);
|
||||||
|
texData.height = *reinterpret_cast<uint32_t*>(&fileArr[22]);
|
||||||
|
|
||||||
|
texData.bitSize = TextureDataStruct::BS_32BIT;
|
||||||
|
|
||||||
|
size_t dataSize = texData.width * texData.height * 4;
|
||||||
|
|
||||||
|
texData.data.resize(dataSize);
|
||||||
|
|
||||||
|
size_t pos = *reinterpret_cast<uint32_t*>(&fileArr[10]);
|
||||||
|
size_t x = 0;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < texData.width; i++)
|
||||||
|
for (size_t j = 0; j < texData.height; j++)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (pos + 4 > fileSize)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("File is too short!");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
x = (i * texData.height + j) + (i * texData.height + j) + (i * texData.height + j) + (i * texData.height + j);
|
||||||
|
|
||||||
|
texData.data[x + 2] = fileArr[pos++];
|
||||||
|
texData.data[x + 1] = fileArr[pos++];
|
||||||
|
texData.data[x + 0] = fileArr[pos++];
|
||||||
|
texData.data[x + 3] = fileArr[pos++];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return texData;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef PNG_ENABLED
|
||||||
|
|
||||||
|
// Ñòðóêòóðà äëÿ õðàíåíèÿ äàííûõ î ôàéëå/ìàññèâå è òåêóùåé ïîçèöèè ÷òåíèÿ
|
||||||
|
struct png_data_t {
|
||||||
|
const char* data;
|
||||||
|
size_t size;
|
||||||
|
size_t offset;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ïîëüçîâàòåëüñêàÿ ôóíêöèÿ ÷òåíèÿ äëÿ libpng
|
||||||
|
// 'png_ptr' - óêàçàòåëü íà ñòðóêòóðó png
|
||||||
|
// 'out_ptr' - êóäà çàïèñûâàòü ïðî÷èòàííûå äàííûå
|
||||||
|
// 'bytes_to_read' - ñêîëüêî áàéò íóæíî ïðî÷èòàòü
|
||||||
|
void user_read_data(png_structp png_ptr, png_bytep out_ptr, png_size_t bytes_to_read) {
|
||||||
|
// Ïîëó÷àåì óêàçàòåëü íà íàøó ñòðóêòóðó png_data_t, êîòîðóþ ìû óñòàíîâèëè ñ ïîìîùüþ png_set_read_fn
|
||||||
|
png_data_t* data = (png_data_t*)png_get_io_ptr(png_ptr);
|
||||||
|
|
||||||
|
if (data->offset + bytes_to_read > data->size) {
|
||||||
|
// Ïîïûòêà ïðî÷èòàòü áîëüøå, ÷åì åñòü â ìàññèâå.
|
||||||
|
// Âìåñòî âûçîâà ñòàíäàðòíîé îøèáêè, ìû ìîæåì ïðîñòî ïðî÷èòàòü îñòàòîê èëè âûçâàòü îøèáêó.
|
||||||
|
//  ýòîì ñëó÷àå ìû âûçîâåì îøèáêó libpng.
|
||||||
|
png_error(png_ptr, "PNG Read Error: Attempted to read past end of data buffer.");
|
||||||
|
bytes_to_read = data->size - data->offset; // Óñòàíàâëèâàåì, ÷òîáû ïðî÷èòàòü îñòàâøååñÿ
|
||||||
|
}
|
||||||
|
|
||||||
|
// Êîïèðóåì äàííûå èç íàøåãî ìàññèâà â áóôåð libpng
|
||||||
|
std::memcpy(out_ptr, data->data + data->offset, bytes_to_read);
|
||||||
|
|
||||||
|
// Îáíîâëÿåì ñìåùåíèå
|
||||||
|
data->offset += bytes_to_read;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ïîëüçîâàòåëüñêàÿ ôóíêöèÿ ïðåäóïðåæäåíèé (ïî æåëàíèþ, ìîæíî èñïîëüçîâàòü nullptr)
|
||||||
|
void user_warning_fn(png_structp png_ptr, png_const_charp warning_msg) {
|
||||||
|
// Çäåñü ìîæíî ðåàëèçîâàòü ëîãèðîâàíèå ïðåäóïðåæäåíèé
|
||||||
|
//throw std::runtime_error();
|
||||||
|
std::cout << "PNG Warning: " << warning_msg << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ïîëüçîâàòåëüñêàÿ ôóíêöèÿ îøèáîê (îáÿçàòåëüíà äëÿ setjmp)
|
||||||
|
void user_error_fn(png_structp png_ptr, png_const_charp error_msg) {
|
||||||
|
// Çäåñü ìîæíî ðåàëèçîâàòü ëîãèðîâàíèå îøèáîê
|
||||||
|
std::cout << "PNG Error: " << error_msg << std::endl;
|
||||||
|
// Îáÿçàòåëüíî âûçûâàåì longjmp äëÿ âûõîäà èç ïðîöåññà ÷òåíèÿ/çàïèñè PNG
|
||||||
|
longjmp(png_jmpbuf(png_ptr), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TextureDataStruct CreateTextureDataFromPng(const std::vector<char>& fileArr)
|
||||||
|
{
|
||||||
|
TextureDataStruct texData;
|
||||||
|
|
||||||
|
// Ñòðóêòóðà äëÿ óïðàâëåíèÿ ÷òåíèåì èç ìàññèâà
|
||||||
|
png_data_t png_data = { fileArr.data(), fileArr.size(), 0 };
|
||||||
|
|
||||||
|
png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
||||||
|
if (!png) {
|
||||||
|
throw std::runtime_error("Could not create PNG read structure");
|
||||||
|
}
|
||||||
|
|
||||||
|
png_infop info = png_create_info_struct(png);
|
||||||
|
if (!info) {
|
||||||
|
png_destroy_read_struct(&png, nullptr, nullptr);
|
||||||
|
throw std::runtime_error("Could not create PNG info structure");
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Óñòàíîâêà ïîëüçîâàòåëüñêèõ ôóíêöèé ÷òåíèÿ è îáðàáîòêè îøèáîê ===
|
||||||
|
// 1. Óñòàíîâêà îáðàáîò÷èêà îøèáîê è longjmp
|
||||||
|
if (setjmp(png_jmpbuf(png))) {
|
||||||
|
png_destroy_read_struct(&png, &info, nullptr);
|
||||||
|
throw std::runtime_error("Error during PNG read (longjmp was executed)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Óñòàíîâêà ïîëüçîâàòåëüñêèõ ôóíêöèé äëÿ îáðàáîòêè îøèáîê è ïðåäóïðåæäåíèé
|
||||||
|
// Âìåñòî nullptr â error_ptr è warning_ptr ìîæíî ïåðåäàòü óêàçàòåëü íà ñâîþ ñòðóêòóðó äàííûõ, åñëè íåîáõîäèìî
|
||||||
|
png_set_error_fn(png, nullptr, user_error_fn, user_warning_fn);
|
||||||
|
|
||||||
|
// 3. Óñòàíîâêà ïîëüçîâàòåëüñêîé ôóíêöèè ÷òåíèÿ è ïåðåäà÷à åé íàøåé ñòðóêòóðû png_data
|
||||||
|
png_set_read_fn(png, &png_data, user_read_data);
|
||||||
|
// ===================================================================
|
||||||
|
|
||||||
|
png_read_info(png, info);
|
||||||
|
|
||||||
|
texData.width = png_get_image_width(png, info);
|
||||||
|
texData.height = png_get_image_height(png, info);
|
||||||
|
png_byte color_type = png_get_color_type(png, info);
|
||||||
|
png_byte bit_depth = png_get_bit_depth(png, info);
|
||||||
|
|
||||||
|
// === Áëîê ïðåîáðàçîâàíèé (îñòàâëåí áåç èçìåíåíèé) ===
|
||||||
|
if (bit_depth == 16)
|
||||||
|
png_set_strip_16(png);
|
||||||
|
|
||||||
|
if (color_type == PNG_COLOR_TYPE_PALETTE)
|
||||||
|
png_set_palette_to_rgb(png);
|
||||||
|
|
||||||
|
if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
|
||||||
|
png_set_expand_gray_1_2_4_to_8(png);
|
||||||
|
|
||||||
|
if (png_get_valid(png, info, PNG_INFO_tRNS))
|
||||||
|
png_set_tRNS_to_alpha(png);
|
||||||
|
|
||||||
|
if (color_type == PNG_COLOR_TYPE_RGB ||
|
||||||
|
color_type == PNG_COLOR_TYPE_GRAY ||
|
||||||
|
color_type == PNG_COLOR_TYPE_PALETTE)
|
||||||
|
png_set_filler(png, 0xFF, PNG_FILLER_AFTER);
|
||||||
|
|
||||||
|
if (color_type == PNG_COLOR_TYPE_GRAY ||
|
||||||
|
color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
|
||||||
|
png_set_gray_to_rgb(png);
|
||||||
|
|
||||||
|
png_read_update_info(png, info);
|
||||||
|
// ====================================================
|
||||||
|
|
||||||
|
// === ×òåíèå ïèêñåëåé (îñòàâëåí áåç èçìåíåíèé) ===
|
||||||
|
png_bytep* row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * texData.height);
|
||||||
|
for (int y = 0; y < texData.height; y++) {
|
||||||
|
row_pointers[y] = (png_byte*)malloc(png_get_rowbytes(png, info));
|
||||||
|
}
|
||||||
|
|
||||||
|
png_read_image(png, row_pointers);
|
||||||
|
|
||||||
|
bool has_alpha = (color_type & PNG_COLOR_MASK_ALPHA) || (png_get_valid(png, info, PNG_INFO_tRNS));
|
||||||
|
|
||||||
|
size_t dataSize;
|
||||||
|
|
||||||
|
if (has_alpha)
|
||||||
|
{
|
||||||
|
texData.bitSize = TextureDataStruct::BS_32BIT;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
texData.bitSize = TextureDataStruct::BS_24BIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
int channels = has_alpha ? 4 : 3;
|
||||||
|
|
||||||
|
dataSize = texData.width * texData.height * channels;
|
||||||
|
texData.data.resize(dataSize);
|
||||||
|
|
||||||
|
|
||||||
|
for (int y = texData.height - 1; y >= 0; y--) {
|
||||||
|
png_bytep row = row_pointers[texData.height - 1 - y];
|
||||||
|
for (int x = 0; x < texData.width; x++) {
|
||||||
|
png_bytep px = &(row[x * 4]);
|
||||||
|
texData.data[(y * texData.width + x) * channels + 0] = px[0]; // R
|
||||||
|
texData.data[(y * texData.width + x) * channels + 1] = px[1]; // G
|
||||||
|
texData.data[(y * texData.width + x) * channels + 2] = px[2]; // B
|
||||||
|
if (has_alpha) {
|
||||||
|
texData.data[(y * texData.width + x) * channels + 3] = px[3]; // A
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(row_pointers[texData.height - 1 - y]);
|
||||||
|
}
|
||||||
|
free(row_pointers);
|
||||||
|
// ==================================================
|
||||||
|
|
||||||
|
png_destroy_read_struct(&png, &info, nullptr);
|
||||||
|
|
||||||
|
return texData;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TextureDataStruct CreateTextureDataFromPng(const std::string& fullFileName, const std::string& ZIPFileName)
|
||||||
|
{
|
||||||
|
std::vector<char> fileArr;
|
||||||
|
|
||||||
|
fileArr = !ZIPFileName.empty() ? readFileFromZIP(fullFileName, ZIPFileName) : readFile(fullFileName);
|
||||||
|
|
||||||
|
if (fileArr.empty()) {
|
||||||
|
throw std::runtime_error("Could not read file data into memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Âûçûâàåì íîâóþ ôóíêöèþ, êîòîðàÿ ðàáîòàåò ñ ìàññèâîì áàéò
|
||||||
|
return CreateTextureDataFromPng(fileArr);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
56
TextureManager.h
Executable file
56
TextureManager.h
Executable file
@ -0,0 +1,56 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "OpenGlExtensions.h"
|
||||||
|
#include "Utils.h"
|
||||||
|
|
||||||
|
#ifdef EMSCRIPTEN
|
||||||
|
#define PNG_ENABLED
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace ZL
|
||||||
|
{
|
||||||
|
|
||||||
|
struct TextureDataStruct
|
||||||
|
{
|
||||||
|
size_t width;
|
||||||
|
size_t height;
|
||||||
|
std::vector<char> data;
|
||||||
|
enum BitSize {
|
||||||
|
BS_24BIT,
|
||||||
|
BS_32BIT
|
||||||
|
};
|
||||||
|
|
||||||
|
BitSize bitSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Texture
|
||||||
|
{
|
||||||
|
size_t width = 0;
|
||||||
|
size_t height = 0;
|
||||||
|
GLuint texID = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
Texture(const TextureDataStruct& texData);
|
||||||
|
|
||||||
|
//Cubemap texture:
|
||||||
|
Texture(const std::array<TextureDataStruct, 6>& texDataArray);
|
||||||
|
|
||||||
|
~Texture();
|
||||||
|
|
||||||
|
GLuint getTexID();
|
||||||
|
|
||||||
|
size_t getWidth();
|
||||||
|
size_t getHeight();
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
TextureDataStruct CreateTextureDataFromBmp24(const std::string& fullFileName, const std::string& ZIPFileName="");
|
||||||
|
TextureDataStruct CreateTextureDataFromBmp32(const std::string& fullFileName, const std::string& ZIPFileName="");
|
||||||
|
#ifdef PNG_ENABLED
|
||||||
|
TextureDataStruct CreateTextureDataFromPng(const std::string& fullFileName, const std::string& ZIPFileName = "");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
105
Utils.cpp
Executable file
105
Utils.cpp
Executable file
@ -0,0 +1,105 @@
|
|||||||
|
#include "Utils.h"
|
||||||
|
#include <cstring>
|
||||||
|
#include <iterator>
|
||||||
|
#include <vector>
|
||||||
|
#include <iostream>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <fstream>
|
||||||
|
#ifdef EMSCRIPTEN
|
||||||
|
#include <zip.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace ZL
|
||||||
|
{
|
||||||
|
|
||||||
|
std::string readTextFile(const std::string& filename)
|
||||||
|
{
|
||||||
|
std::ifstream f(filename);
|
||||||
|
|
||||||
|
std::string str((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<char> readFile(const std::string& filename)
|
||||||
|
{
|
||||||
|
std::ifstream file(filename, std::ios::binary);
|
||||||
|
|
||||||
|
file.unsetf(std::ios::skipws);
|
||||||
|
|
||||||
|
std::streampos fileSize;
|
||||||
|
|
||||||
|
file.seekg(0, std::ios::end);
|
||||||
|
fileSize = file.tellg();
|
||||||
|
file.seekg(0, std::ios::beg);
|
||||||
|
|
||||||
|
std::vector<char> vec;
|
||||||
|
vec.reserve(fileSize);
|
||||||
|
|
||||||
|
vec.insert(vec.begin(),
|
||||||
|
std::istream_iterator<char>(file),
|
||||||
|
std::istream_iterator<char>());
|
||||||
|
|
||||||
|
return vec;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<char> readFileFromZIP(const std::string& filename, const std::string& zipfilename) {
|
||||||
|
#ifdef EMSCRIPTEN
|
||||||
|
const std::string zipPath = zipfilename;
|
||||||
|
int zipErr;
|
||||||
|
zip_t* archive = zip_open(zipPath.c_str(), ZIP_RDONLY, &zipErr);
|
||||||
|
if (!archive) {
|
||||||
|
throw std::runtime_error("Ошибка открытия ZIP: " + zipPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string cleanFilename = filename;
|
||||||
|
if (cleanFilename.rfind("./", 0) == 0) {
|
||||||
|
cleanFilename = cleanFilename.substr(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "Ищем в ZIP: " << cleanFilename << std::endl;
|
||||||
|
|
||||||
|
zip_file_t* zipFile = zip_fopen(archive, cleanFilename.c_str(), 0);
|
||||||
|
if (!zipFile) {
|
||||||
|
zip_close(archive);
|
||||||
|
throw std::runtime_error("Файл не найден в ZIP: " + cleanFilename);
|
||||||
|
}
|
||||||
|
|
||||||
|
zip_stat_t fileStat;
|
||||||
|
if (zip_stat(archive, cleanFilename.c_str(), 0, &fileStat) != 0) {
|
||||||
|
zip_fclose(zipFile);
|
||||||
|
zip_close(archive);
|
||||||
|
throw std::runtime_error("Ошибка чтения ZIP-статистики.");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<char> fileData;
|
||||||
|
fileData.resize(fileStat.size);
|
||||||
|
|
||||||
|
zip_fread(zipFile, fileData.data(), fileData.size());
|
||||||
|
|
||||||
|
zip_fclose(zipFile);
|
||||||
|
zip_close(archive);
|
||||||
|
|
||||||
|
return fileData;
|
||||||
|
#else
|
||||||
|
return {};
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool findString(const char* in, char* list)
|
||||||
|
{
|
||||||
|
size_t thisLength = strlen(in);
|
||||||
|
while (*list != 0)
|
||||||
|
{
|
||||||
|
size_t length = strcspn(list, " ");
|
||||||
|
|
||||||
|
if (thisLength == length)
|
||||||
|
if (!strncmp(in, list, length))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
list += length;
|
||||||
|
list += 1;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
20
Utils.h
Executable file
20
Utils.h
Executable file
@ -0,0 +1,20 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <exception>
|
||||||
|
#include <map>
|
||||||
|
#include <stack>
|
||||||
|
#include <memory>
|
||||||
|
#include <unordered_map>
|
||||||
|
namespace ZL
|
||||||
|
{
|
||||||
|
std::string readTextFile(const std::string& filename);
|
||||||
|
|
||||||
|
std::vector<char> readFile(const std::string& filename);
|
||||||
|
|
||||||
|
std::vector<char> readFileFromZIP(const std::string& filename, const std::string& zipfilename);
|
||||||
|
|
||||||
|
bool findString(const char* in, char* list);
|
||||||
|
|
||||||
|
}
|
||||||
799
ZLMath.cpp
Normal file
799
ZLMath.cpp
Normal file
@ -0,0 +1,799 @@
|
|||||||
|
#include "ZLMath.h"
|
||||||
|
|
||||||
|
#include <exception>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
namespace ZL {
|
||||||
|
|
||||||
|
Vector2f operator+(const Vector2f& x, const Vector2f& y)
|
||||||
|
{
|
||||||
|
Vector2f result;
|
||||||
|
|
||||||
|
result.v[0] = x.v[0] + y.v[0];
|
||||||
|
result.v[1] = x.v[1] + y.v[1];
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector2f operator-(const Vector2f& x, const Vector2f& y)
|
||||||
|
{
|
||||||
|
Vector2f result;
|
||||||
|
|
||||||
|
result.v[0] = x.v[0] - y.v[0];
|
||||||
|
result.v[1] = x.v[1] - y.v[1];
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3f operator+(const Vector3f& x, const Vector3f& y)
|
||||||
|
{
|
||||||
|
Vector3f result;
|
||||||
|
|
||||||
|
result.v[0] = x.v[0] + y.v[0];
|
||||||
|
result.v[1] = x.v[1] + y.v[1];
|
||||||
|
result.v[2] = x.v[2] + y.v[2];
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3f operator-(const Vector3f& x, const Vector3f& y)
|
||||||
|
{
|
||||||
|
Vector3f result;
|
||||||
|
|
||||||
|
result.v[0] = x.v[0] - y.v[0];
|
||||||
|
result.v[1] = x.v[1] - y.v[1];
|
||||||
|
result.v[2] = x.v[2] - y.v[2];
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3f operator-(const Vector3f& x)
|
||||||
|
{
|
||||||
|
Vector3f result;
|
||||||
|
|
||||||
|
result.v[0] = -x.v[0];
|
||||||
|
result.v[1] = -x.v[1];
|
||||||
|
result.v[2] = -x.v[2];
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector4f operator+(const Vector4f& x, const Vector4f& y)
|
||||||
|
{
|
||||||
|
Vector4f result;
|
||||||
|
|
||||||
|
result.v[0] = x.v[0] + y.v[0];
|
||||||
|
result.v[1] = x.v[1] + y.v[1];
|
||||||
|
result.v[2] = x.v[2] + y.v[2];
|
||||||
|
result.v[3] = x.v[3] + y.v[3];
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector4f operator-(const Vector4f& x, const Vector4f& y)
|
||||||
|
{
|
||||||
|
Vector4f result;
|
||||||
|
|
||||||
|
result.v[0] = x.v[0] - y.v[0];
|
||||||
|
result.v[1] = x.v[1] - y.v[1];
|
||||||
|
result.v[2] = x.v[2] - y.v[2];
|
||||||
|
result.v[3] = x.v[3] - y.v[3];
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Matrix3f Matrix3f::Identity()
|
||||||
|
{
|
||||||
|
Matrix3f r;
|
||||||
|
|
||||||
|
r.m[0] = 1.f;
|
||||||
|
r.m[1] = 0.f;
|
||||||
|
r.m[2] = 0.f;
|
||||||
|
|
||||||
|
r.m[3] = 0.f;
|
||||||
|
r.m[4] = 1.f;
|
||||||
|
r.m[5] = 0.f;
|
||||||
|
|
||||||
|
r.m[6] = 0.f;
|
||||||
|
r.m[7] = 0.f;
|
||||||
|
r.m[8] = 1.f;
|
||||||
|
|
||||||
|
return r;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Matrix4f Matrix4f::Identity()
|
||||||
|
{
|
||||||
|
Matrix4f r;
|
||||||
|
|
||||||
|
r.m[0] = 1.f;
|
||||||
|
r.m[1] = 0.f;
|
||||||
|
r.m[2] = 0.f;
|
||||||
|
r.m[3] = 0.f;
|
||||||
|
|
||||||
|
r.m[4] = 0.f;
|
||||||
|
r.m[5] = 1.f;
|
||||||
|
r.m[6] = 0.f;
|
||||||
|
r.m[7] = 0.f;
|
||||||
|
|
||||||
|
r.m[8] = 0.f;
|
||||||
|
r.m[9] = 0.f;
|
||||||
|
r.m[10] = 1.f;
|
||||||
|
r.m[11] = 0.f;
|
||||||
|
|
||||||
|
r.m[12] = 0.f;
|
||||||
|
r.m[13] = 0.f;
|
||||||
|
r.m[14] = 0.f;
|
||||||
|
r.m[15] = 1.f;
|
||||||
|
|
||||||
|
return r;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Matrix4f operator*(const Matrix4f& m1, const Matrix4f& m2)
|
||||||
|
{
|
||||||
|
Matrix4f r;
|
||||||
|
|
||||||
|
r.m[0] = m1.m[0] * m2.m[0] + m1.m[4] * m2.m[1] + m1.m[8] * m2.m[2] + m1.m[12] * m2.m[3];
|
||||||
|
r.m[1] = m1.m[1] * m2.m[0] + m1.m[5] * m2.m[1] + m1.m[9] * m2.m[2] + m1.m[13] * m2.m[3];
|
||||||
|
r.m[2] = m1.m[2] * m2.m[0] + m1.m[6] * m2.m[1] + m1.m[10] * m2.m[2] + m1.m[14] * m2.m[3];
|
||||||
|
r.m[3] = m1.m[3] * m2.m[0] + m1.m[7] * m2.m[1] + m1.m[11] * m2.m[2] + m1.m[15] * m2.m[3];
|
||||||
|
|
||||||
|
r.m[4] = m1.m[0] * m2.m[4] + m1.m[4] * m2.m[5] + m1.m[8] * m2.m[6] + m1.m[12] * m2.m[7];
|
||||||
|
r.m[5] = m1.m[1] * m2.m[4] + m1.m[5] * m2.m[5] + m1.m[9] * m2.m[6] + m1.m[13] * m2.m[7];
|
||||||
|
r.m[6] = m1.m[2] * m2.m[4] + m1.m[6] * m2.m[5] + m1.m[10] * m2.m[6] + m1.m[14] * m2.m[7];
|
||||||
|
r.m[7] = m1.m[3] * m2.m[4] + m1.m[7] * m2.m[5] + m1.m[11] * m2.m[6] + m1.m[15] * m2.m[7];
|
||||||
|
|
||||||
|
|
||||||
|
r.m[8] = m1.m[0] * m2.m[8] + m1.m[4] * m2.m[9] + m1.m[8] * m2.m[10] + m1.m[12] * m2.m[11];
|
||||||
|
r.m[9] = m1.m[1] * m2.m[8] + m1.m[5] * m2.m[9] + m1.m[9] * m2.m[10] + m1.m[13] * m2.m[11];
|
||||||
|
r.m[10] = m1.m[2] * m2.m[8] + m1.m[6] * m2.m[9] + m1.m[10] * m2.m[10] + m1.m[14] * m2.m[11];
|
||||||
|
r.m[11] = m1.m[3] * m2.m[8] + m1.m[7] * m2.m[9] + m1.m[11] * m2.m[10] + m1.m[15] * m2.m[11];
|
||||||
|
|
||||||
|
r.m[12] = m1.m[0] * m2.m[12] + m1.m[4] * m2.m[13] + m1.m[8] * m2.m[14] + m1.m[12] * m2.m[15];
|
||||||
|
r.m[13] = m1.m[1] * m2.m[12] + m1.m[5] * m2.m[13] + m1.m[9] * m2.m[14] + m1.m[13] * m2.m[15];
|
||||||
|
r.m[14] = m1.m[2] * m2.m[12] + m1.m[6] * m2.m[13] + m1.m[10] * m2.m[14] + m1.m[14] * m2.m[15];
|
||||||
|
r.m[15] = m1.m[3] * m2.m[12] + m1.m[7] * m2.m[13] + m1.m[11] * m2.m[14] + m1.m[15] * m2.m[15];
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
Matrix4f MakeOrthoMatrix(float width, float height, float zNear, float zFar)
|
||||||
|
{
|
||||||
|
float depthRange = zFar - zNear;
|
||||||
|
|
||||||
|
if (depthRange <= 0)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("zFar must be greater than zNear");
|
||||||
|
}
|
||||||
|
|
||||||
|
Matrix4f r;
|
||||||
|
|
||||||
|
r.m[0] = 2.f / width;
|
||||||
|
r.m[1] = 0;
|
||||||
|
r.m[2] = 0;
|
||||||
|
r.m[3] = 0;
|
||||||
|
|
||||||
|
r.m[4] = 0;
|
||||||
|
r.m[5] = 2.f / height;
|
||||||
|
r.m[6] = 0;
|
||||||
|
r.m[7] = 0;
|
||||||
|
|
||||||
|
r.m[8] = 0;
|
||||||
|
r.m[9] = 0;
|
||||||
|
r.m[10] = -1 / depthRange;
|
||||||
|
r.m[11] = 0;
|
||||||
|
|
||||||
|
r.m[12] = -1;
|
||||||
|
r.m[13] = -1;
|
||||||
|
r.m[14] = zNear / depthRange;
|
||||||
|
r.m[15] = 1;
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
Matrix4f MakeOrthoMatrix(float xmin, float xmax, float ymin, float ymax, float zNear, float zFar)
|
||||||
|
{
|
||||||
|
float width = xmax - xmin;
|
||||||
|
float height = ymax - ymin;
|
||||||
|
float depthRange = zFar - zNear;
|
||||||
|
|
||||||
|
if (width <= 0 || height <= 0 || depthRange <= 0)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Invalid dimensions for orthogonal matrix");
|
||||||
|
}
|
||||||
|
|
||||||
|
Matrix4f r;
|
||||||
|
|
||||||
|
// Масштабирование
|
||||||
|
r.m[0] = 2.f / width;
|
||||||
|
r.m[5] = 2.f / height;
|
||||||
|
r.m[10] = -1.f / depthRange;
|
||||||
|
|
||||||
|
// Обнуление неиспользуемых компонентов
|
||||||
|
r.m[1] = r.m[2] = r.m[3] = 0;
|
||||||
|
r.m[4] = r.m[6] = r.m[7] = 0;
|
||||||
|
r.m[8] = r.m[9] = r.m[11] = 0;
|
||||||
|
|
||||||
|
// Трансляция (смещение)
|
||||||
|
r.m[12] = -(xmax + xmin) / width;
|
||||||
|
r.m[13] = -(ymax + ymin) / height;
|
||||||
|
r.m[14] = zNear / depthRange;
|
||||||
|
r.m[15] = 1.f;
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
Matrix4f MakePerspectiveMatrix(float fovY, float aspectRatio, float zNear, float zFar)
|
||||||
|
{
|
||||||
|
float tanHalfFovy = tan(fovY / 2.f);
|
||||||
|
Matrix4f r;
|
||||||
|
|
||||||
|
if (zNear >= zFar || aspectRatio == 0)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Invalid perspective parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
r.m[0] = 1.f / (aspectRatio * tanHalfFovy);
|
||||||
|
r.m[1] = 0;
|
||||||
|
r.m[2] = 0;
|
||||||
|
r.m[3] = 0;
|
||||||
|
|
||||||
|
r.m[4] = 0;
|
||||||
|
r.m[5] = 1.f / (tanHalfFovy);
|
||||||
|
r.m[6] = 0;
|
||||||
|
r.m[7] = 0;
|
||||||
|
|
||||||
|
r.m[8] = 0;
|
||||||
|
r.m[9] = 0;
|
||||||
|
r.m[10] = -(zFar + zNear) / (zFar - zNear);
|
||||||
|
r.m[11] = -1;
|
||||||
|
|
||||||
|
r.m[12] = 0;
|
||||||
|
r.m[13] = 0;
|
||||||
|
r.m[14] = -(2.f * zFar * zNear) / (zFar - zNear);
|
||||||
|
r.m[15] = 0;
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
Matrix3f QuatToMatrix(const Vector4f& q)
|
||||||
|
{
|
||||||
|
Matrix3f m;
|
||||||
|
|
||||||
|
float wx, wy, wz, xx, yy, yz, xy, xz, zz, s, x2, y2, z2;
|
||||||
|
|
||||||
|
s = 2.0f / (q.v[0] * q.v[0] + q.v[1] * q.v[1] + q.v[2] * q.v[2] + q.v[3] * q.v[3]);
|
||||||
|
|
||||||
|
|
||||||
|
x2 = q.v[0] * s;
|
||||||
|
y2 = q.v[1] * s;
|
||||||
|
z2 = q.v[2] * s;
|
||||||
|
|
||||||
|
wx = q.v[3] * x2; wy = q.v[3] * y2; wz = q.v[3] * z2;
|
||||||
|
xx = q.v[0] * x2; xy = q.v[1] * x2; xz = q.v[2] * x2;
|
||||||
|
yy = q.v[1] * y2; yz = q.v[2] * y2;
|
||||||
|
zz = q.v[2] * z2;
|
||||||
|
|
||||||
|
m.m[0] = 1.0f - (yy + zz);
|
||||||
|
m.m[1] = xy + wz;
|
||||||
|
m.m[2] = xz - wy;
|
||||||
|
|
||||||
|
m.m[3] = xy - wz;
|
||||||
|
m.m[4] = 1.0f - (xx + zz);
|
||||||
|
m.m[5] = yz + wx;
|
||||||
|
|
||||||
|
m.m[6] = xz + wy;
|
||||||
|
m.m[7] = yz - wx;
|
||||||
|
m.m[8] = 1.0f - (xx + yy);
|
||||||
|
|
||||||
|
return m;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector4f MatrixToQuat(const Matrix3f& m)
|
||||||
|
{
|
||||||
|
Vector4f r;
|
||||||
|
float trace = m.m[0] + m.m[4] + m.m[8];
|
||||||
|
|
||||||
|
if (trace > 0)
|
||||||
|
{
|
||||||
|
float s = 0.5f / sqrtf(trace + 1.0f);
|
||||||
|
r.v[3] = 0.25f / s;
|
||||||
|
r.v[0] = (m.m[5] - m.m[7]) * s;
|
||||||
|
r.v[1] = (m.m[6] - m.m[2]) * s;
|
||||||
|
r.v[2] = (m.m[1] - m.m[3]) * s;
|
||||||
|
}
|
||||||
|
else if (m.m[0] > m.m[4] && m.m[0] > m.m[8])
|
||||||
|
{
|
||||||
|
float s = 2.0f * sqrtf(1.0f + m.m[0] - m.m[4] - m.m[8]);
|
||||||
|
r.v[3] = (m.m[5] - m.m[7]) / s;
|
||||||
|
r.v[0] = 0.25f * s;
|
||||||
|
r.v[1] = (m.m[1] + m.m[3]) / s;
|
||||||
|
r.v[2] = (m.m[6] + m.m[2]) / s;
|
||||||
|
}
|
||||||
|
else if (m.m[4] > m.m[8])
|
||||||
|
{
|
||||||
|
float s = 2.0f * sqrtf(1.0f + m.m[4] - m.m[0] - m.m[8]);
|
||||||
|
r.v[3] = (m.m[6] - m.m[2]) / s;
|
||||||
|
r.v[0] = (m.m[1] + m.m[3]) / s;
|
||||||
|
r.v[1] = 0.25f * s;
|
||||||
|
r.v[2] = (m.m[5] + m.m[7]) / s;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
float s = 2.0f * sqrtf(1.0f + m.m[8] - m.m[0] - m.m[4]);
|
||||||
|
r.v[3] = (m.m[1] - m.m[3]) / s;
|
||||||
|
r.v[0] = (m.m[6] + m.m[2]) / s;
|
||||||
|
r.v[1] = (m.m[5] + m.m[7]) / s;
|
||||||
|
r.v[2] = 0.25f * s;
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.normalized();
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector4f QuatFromRotateAroundX(float angle)
|
||||||
|
{
|
||||||
|
Vector4f result;
|
||||||
|
|
||||||
|
result.v[0] = sinf(angle * 0.5f);
|
||||||
|
result.v[1] = 0.f;
|
||||||
|
result.v[2] = 0.f;
|
||||||
|
result.v[3] = cosf(angle * 0.5f);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector4f QuatFromRotateAroundY(float angle)
|
||||||
|
{
|
||||||
|
Vector4f result;
|
||||||
|
|
||||||
|
result.v[0] = 0.f;
|
||||||
|
result.v[1] = sinf(angle * 0.5f);
|
||||||
|
result.v[2] = 0.f;
|
||||||
|
result.v[3] = cosf(angle * 0.5f);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector4f QuatFromRotateAroundZ(float angle)
|
||||||
|
{
|
||||||
|
Vector4f result;
|
||||||
|
|
||||||
|
result.v[0] = 0.f;
|
||||||
|
result.v[1] = 0.f;
|
||||||
|
result.v[2] = sinf(angle * 0.5f);
|
||||||
|
result.v[3] = cosf(angle * 0.5f);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Matrix3f TransposeMatrix(const Matrix3f& m)
|
||||||
|
{
|
||||||
|
Matrix3f r;
|
||||||
|
r.m[0] = m.m[0];
|
||||||
|
r.m[1] = m.m[3];
|
||||||
|
r.m[2] = m.m[6];
|
||||||
|
r.m[3] = m.m[1];
|
||||||
|
r.m[4] = m.m[4];
|
||||||
|
r.m[5] = m.m[7];
|
||||||
|
r.m[6] = m.m[2];
|
||||||
|
r.m[7] = m.m[5];
|
||||||
|
r.m[8] = m.m[8];
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
Matrix3f InverseMatrix(const Matrix3f& m)
|
||||||
|
{
|
||||||
|
float d;
|
||||||
|
Matrix3f r;
|
||||||
|
|
||||||
|
d = m.m[0] * (m.m[4] * m.m[8] - m.m[5] * m.m[7]);
|
||||||
|
d -= m.m[1] * (m.m[3] * m.m[8] - m.m[6] * m.m[5]);
|
||||||
|
d += m.m[2] * (m.m[3] * m.m[7] - m.m[6] * m.m[4]);
|
||||||
|
|
||||||
|
if (fabs(d) < 0.01f)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Error: matrix cannot be inversed!");
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
r.m[0] = (m.m[4] * m.m[8] - m.m[5] * m.m[7]) / d;
|
||||||
|
r.m[1] = -(m.m[1] * m.m[8] - m.m[2] * m.m[7]) / d;
|
||||||
|
r.m[2] = (m.m[1] * m.m[5] - m.m[2] * m.m[4]) / d;
|
||||||
|
|
||||||
|
r.m[3] = -(m.m[3] * m.m[8] - m.m[5] * m.m[6]) / d;
|
||||||
|
r.m[4] = (m.m[0] * m.m[8] - m.m[2] * m.m[6]) / d;
|
||||||
|
r.m[5] = -(m.m[0] * m.m[5] - m.m[2] * m.m[3]) / d;
|
||||||
|
|
||||||
|
r.m[6] = (m.m[3] * m.m[7] - m.m[6] * m.m[4]) / d;
|
||||||
|
r.m[7] = -(m.m[0] * m.m[7] - m.m[6] * m.m[1]) / d;
|
||||||
|
r.m[8] = (m.m[0] * m.m[4] - m.m[1] * m.m[3]) / d;
|
||||||
|
|
||||||
|
};
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Matrix4f InverseMatrix(const Matrix4f& mat)
|
||||||
|
{
|
||||||
|
Matrix4f inv;
|
||||||
|
float det;
|
||||||
|
|
||||||
|
inv.m[0] = mat.m[5] * mat.m[10] * mat.m[15] -
|
||||||
|
mat.m[5] * mat.m[11] * mat.m[14] -
|
||||||
|
mat.m[9] * mat.m[6] * mat.m[15] +
|
||||||
|
mat.m[9] * mat.m[7] * mat.m[14] +
|
||||||
|
mat.m[13] * mat.m[6] * mat.m[11] -
|
||||||
|
mat.m[13] * mat.m[7] * mat.m[10];
|
||||||
|
|
||||||
|
inv.m[4] = -mat.m[4] * mat.m[10] * mat.m[15] +
|
||||||
|
mat.m[4] * mat.m[11] * mat.m[14] +
|
||||||
|
mat.m[8] * mat.m[6] * mat.m[15] -
|
||||||
|
mat.m[8] * mat.m[7] * mat.m[14] -
|
||||||
|
mat.m[12] * mat.m[6] * mat.m[11] +
|
||||||
|
mat.m[12] * mat.m[7] * mat.m[10];
|
||||||
|
|
||||||
|
inv.m[8] = mat.m[4] * mat.m[9] * mat.m[15] -
|
||||||
|
mat.m[4] * mat.m[11] * mat.m[13] -
|
||||||
|
mat.m[8] * mat.m[5] * mat.m[15] +
|
||||||
|
mat.m[8] * mat.m[7] * mat.m[13] +
|
||||||
|
mat.m[12] * mat.m[5] * mat.m[11] -
|
||||||
|
mat.m[12] * mat.m[7] * mat.m[9];
|
||||||
|
|
||||||
|
inv.m[12] = -mat.m[4] * mat.m[9] * mat.m[14] +
|
||||||
|
mat.m[4] * mat.m[10] * mat.m[13] +
|
||||||
|
mat.m[8] * mat.m[5] * mat.m[14] -
|
||||||
|
mat.m[8] * mat.m[6] * mat.m[13] -
|
||||||
|
mat.m[12] * mat.m[5] * mat.m[10] +
|
||||||
|
mat.m[12] * mat.m[6] * mat.m[9];
|
||||||
|
|
||||||
|
inv.m[1] = -mat.m[1] * mat.m[10] * mat.m[15] +
|
||||||
|
mat.m[1] * mat.m[11] * mat.m[14] +
|
||||||
|
mat.m[9] * mat.m[2] * mat.m[15] -
|
||||||
|
mat.m[9] * mat.m[3] * mat.m[14] -
|
||||||
|
mat.m[13] * mat.m[2] * mat.m[11] +
|
||||||
|
mat.m[13] * mat.m[3] * mat.m[10];
|
||||||
|
|
||||||
|
inv.m[5] = mat.m[0] * mat.m[10] * mat.m[15] -
|
||||||
|
mat.m[0] * mat.m[11] * mat.m[14] -
|
||||||
|
mat.m[8] * mat.m[2] * mat.m[15] +
|
||||||
|
mat.m[8] * mat.m[3] * mat.m[14] +
|
||||||
|
mat.m[12] * mat.m[2] * mat.m[11] -
|
||||||
|
mat.m[12] * mat.m[3] * mat.m[10];
|
||||||
|
|
||||||
|
inv.m[9] = -mat.m[0] * mat.m[9] * mat.m[15] +
|
||||||
|
mat.m[0] * mat.m[11] * mat.m[13] +
|
||||||
|
mat.m[8] * mat.m[1] * mat.m[15] -
|
||||||
|
mat.m[8] * mat.m[3] * mat.m[13] -
|
||||||
|
mat.m[12] * mat.m[1] * mat.m[11] +
|
||||||
|
mat.m[12] * mat.m[3] * mat.m[9];
|
||||||
|
|
||||||
|
inv.m[13] = mat.m[0] * mat.m[9] * mat.m[14] -
|
||||||
|
mat.m[0] * mat.m[10] * mat.m[13] -
|
||||||
|
mat.m[8] * mat.m[1] * mat.m[14] +
|
||||||
|
mat.m[8] * mat.m[2] * mat.m[13] +
|
||||||
|
mat.m[12] * mat.m[1] * mat.m[10] -
|
||||||
|
mat.m[12] * mat.m[2] * mat.m[9];
|
||||||
|
|
||||||
|
inv.m[2] = mat.m[1] * mat.m[6] * mat.m[15] -
|
||||||
|
mat.m[1] * mat.m[7] * mat.m[14] -
|
||||||
|
mat.m[5] * mat.m[2] * mat.m[15] +
|
||||||
|
mat.m[5] * mat.m[3] * mat.m[14] +
|
||||||
|
mat.m[13] * mat.m[2] * mat.m[7] -
|
||||||
|
mat.m[13] * mat.m[3] * mat.m[6];
|
||||||
|
|
||||||
|
inv.m[6] = -mat.m[0] * mat.m[6] * mat.m[15] +
|
||||||
|
mat.m[0] * mat.m[7] * mat.m[14] +
|
||||||
|
mat.m[4] * mat.m[2] * mat.m[15] -
|
||||||
|
mat.m[4] * mat.m[3] * mat.m[14] -
|
||||||
|
mat.m[12] * mat.m[2] * mat.m[7] +
|
||||||
|
mat.m[12] * mat.m[3] * mat.m[6];
|
||||||
|
|
||||||
|
inv.m[10] = mat.m[0] * mat.m[5] * mat.m[15] -
|
||||||
|
mat.m[0] * mat.m[7] * mat.m[13] -
|
||||||
|
mat.m[4] * mat.m[1] * mat.m[15] +
|
||||||
|
mat.m[4] * mat.m[3] * mat.m[13] +
|
||||||
|
mat.m[12] * mat.m[1] * mat.m[7] -
|
||||||
|
mat.m[12] * mat.m[3] * mat.m[5];
|
||||||
|
|
||||||
|
inv.m[14] = -mat.m[0] * mat.m[5] * mat.m[14] +
|
||||||
|
mat.m[0] * mat.m[6] * mat.m[13] +
|
||||||
|
mat.m[4] * mat.m[1] * mat.m[14] -
|
||||||
|
mat.m[4] * mat.m[2] * mat.m[13] -
|
||||||
|
mat.m[12] * mat.m[1] * mat.m[6] +
|
||||||
|
mat.m[12] * mat.m[2] * mat.m[5];
|
||||||
|
|
||||||
|
inv.m[3] = -mat.m[1] * mat.m[6] * mat.m[11] +
|
||||||
|
mat.m[1] * mat.m[7] * mat.m[10] +
|
||||||
|
mat.m[5] * mat.m[2] * mat.m[11] -
|
||||||
|
mat.m[5] * mat.m[3] * mat.m[10] -
|
||||||
|
mat.m[9] * mat.m[2] * mat.m[7] +
|
||||||
|
mat.m[9] * mat.m[3] * mat.m[6];
|
||||||
|
|
||||||
|
inv.m[7] = mat.m[0] * mat.m[6] * mat.m[11] -
|
||||||
|
mat.m[0] * mat.m[7] * mat.m[10] -
|
||||||
|
mat.m[4] * mat.m[2] * mat.m[11] +
|
||||||
|
mat.m[4] * mat.m[3] * mat.m[10] +
|
||||||
|
mat.m[8] * mat.m[2] * mat.m[7] -
|
||||||
|
mat.m[8] * mat.m[3] * mat.m[6];
|
||||||
|
|
||||||
|
inv.m[11] = -mat.m[0] * mat.m[5] * mat.m[11] +
|
||||||
|
mat.m[0] * mat.m[7] * mat.m[9] +
|
||||||
|
mat.m[4] * mat.m[1] * mat.m[11] -
|
||||||
|
mat.m[4] * mat.m[3] * mat.m[9] -
|
||||||
|
mat.m[8] * mat.m[1] * mat.m[7] +
|
||||||
|
mat.m[8] * mat.m[3] * mat.m[5];
|
||||||
|
|
||||||
|
inv.m[15] = mat.m[0] * mat.m[5] * mat.m[10] -
|
||||||
|
mat.m[0] * mat.m[6] * mat.m[9] -
|
||||||
|
mat.m[4] * mat.m[1] * mat.m[10] +
|
||||||
|
mat.m[4] * mat.m[2] * mat.m[9] +
|
||||||
|
mat.m[8] * mat.m[1] * mat.m[6] -
|
||||||
|
mat.m[8] * mat.m[2] * mat.m[5];
|
||||||
|
|
||||||
|
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||||
|
det = mat.m[0] * inv.m[0] + mat.m[1] * inv.m[4] + mat.m[2] * inv.m[8] + mat.m[3] * inv.m[12];
|
||||||
|
|
||||||
|
if (std::fabs(det) < 0.01f)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Error: matrix cannot be inversed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||||
|
det = 1.0f / det;
|
||||||
|
for (int i = 0; i < 16; i++)
|
||||||
|
{
|
||||||
|
inv.m[i] *= det;
|
||||||
|
}
|
||||||
|
|
||||||
|
return inv;
|
||||||
|
}
|
||||||
|
|
||||||
|
Matrix3f CreateZRotationMatrix(float angle)
|
||||||
|
{
|
||||||
|
Matrix3f result = Matrix3f::Identity();
|
||||||
|
|
||||||
|
result.m[0] = cosf(angle);
|
||||||
|
result.m[1] = -sinf(angle);
|
||||||
|
result.m[3] = sinf(angle);
|
||||||
|
result.m[4] = cosf(angle);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Matrix4f MultMatrixMatrix(const Matrix4f& m1, const Matrix4f& m2)
|
||||||
|
{
|
||||||
|
Matrix4f rx;
|
||||||
|
|
||||||
|
rx.m[0] = m1.m[0] * m2.m[0] + m1.m[4] * m2.m[1] + m1.m[8] * m2.m[2] + m1.m[12] * m2.m[3];
|
||||||
|
rx.m[1] = m1.m[1] * m2.m[0] + m1.m[5] * m2.m[1] + m1.m[9] * m2.m[2] + m1.m[13] * m2.m[3];
|
||||||
|
rx.m[2] = m1.m[2] * m2.m[0] + m1.m[6] * m2.m[1] + m1.m[10] * m2.m[2] + m1.m[14] * m2.m[3];
|
||||||
|
rx.m[3] = m1.m[3] * m2.m[0] + m1.m[7] * m2.m[1] + m1.m[11] * m2.m[2] + m1.m[15] * m2.m[3];
|
||||||
|
|
||||||
|
rx.m[4] = m1.m[0] * m2.m[4] + m1.m[4] * m2.m[5] + m1.m[8] * m2.m[6] + m1.m[12] * m2.m[7];
|
||||||
|
rx.m[5] = m1.m[1] * m2.m[4] + m1.m[5] * m2.m[5] + m1.m[9] * m2.m[6] + m1.m[13] * m2.m[7];
|
||||||
|
rx.m[6] = m1.m[2] * m2.m[4] + m1.m[6] * m2.m[5] + m1.m[10] * m2.m[6] + m1.m[14] * m2.m[7];
|
||||||
|
rx.m[7] = m1.m[3] * m2.m[4] + m1.m[7] * m2.m[5] + m1.m[11] * m2.m[6] + m1.m[15] * m2.m[7];
|
||||||
|
|
||||||
|
|
||||||
|
rx.m[8] = m1.m[0] * m2.m[8] + m1.m[4] * m2.m[9] + m1.m[8] * m2.m[10] + m1.m[12] * m2.m[11];
|
||||||
|
rx.m[9] = m1.m[1] * m2.m[8] + m1.m[5] * m2.m[9] + m1.m[9] * m2.m[10] + m1.m[13] * m2.m[11];
|
||||||
|
rx.m[10] = m1.m[2] * m2.m[8] + m1.m[6] * m2.m[9] + m1.m[10] * m2.m[10] + m1.m[14] * m2.m[11];
|
||||||
|
rx.m[11] = m1.m[3] * m2.m[8] + m1.m[7] * m2.m[9] + m1.m[11] * m2.m[10] + m1.m[15] * m2.m[11];
|
||||||
|
|
||||||
|
rx.m[12] = m1.m[0] * m2.m[12] + m1.m[4] * m2.m[13] + m1.m[8] * m2.m[14] + m1.m[12] * m2.m[15];
|
||||||
|
rx.m[13] = m1.m[1] * m2.m[12] + m1.m[5] * m2.m[13] + m1.m[9] * m2.m[14] + m1.m[13] * m2.m[15];
|
||||||
|
rx.m[14] = m1.m[2] * m2.m[12] + m1.m[6] * m2.m[13] + m1.m[10] * m2.m[14] + m1.m[14] * m2.m[15];
|
||||||
|
rx.m[15] = m1.m[3] * m2.m[12] + m1.m[7] * m2.m[13] + m1.m[11] * m2.m[14] + m1.m[15] * m2.m[15];
|
||||||
|
|
||||||
|
return rx;
|
||||||
|
}
|
||||||
|
|
||||||
|
Matrix3f MultMatrixMatrix(const Matrix3f& m1, const Matrix3f& m2)
|
||||||
|
{
|
||||||
|
Matrix3f r;
|
||||||
|
|
||||||
|
r.m[0] = m1.m[0] * m2.m[0] + m1.m[3] * m2.m[1] + m1.m[6] * m2.m[2];
|
||||||
|
r.m[1] = m1.m[1] * m2.m[0] + m1.m[4] * m2.m[1] + m1.m[7] * m2.m[2];
|
||||||
|
r.m[2] = m1.m[2] * m2.m[0] + m1.m[5] * m2.m[1] + m1.m[8] * m2.m[2];
|
||||||
|
|
||||||
|
r.m[3] = m1.m[0] * m2.m[3] + m1.m[3] * m2.m[4] + m1.m[6] * m2.m[5];
|
||||||
|
r.m[4] = m1.m[1] * m2.m[3] + m1.m[4] * m2.m[4] + m1.m[7] * m2.m[5];
|
||||||
|
r.m[5] = m1.m[2] * m2.m[3] + m1.m[5] * m2.m[4] + m1.m[8] * m2.m[5];
|
||||||
|
|
||||||
|
r.m[6] = m1.m[0] * m2.m[6] + m1.m[3] * m2.m[7] + m1.m[6] * m2.m[8] ;
|
||||||
|
r.m[7] = m1.m[1] * m2.m[6] + m1.m[4] * m2.m[7] + m1.m[7] * m2.m[8];
|
||||||
|
r.m[8] = m1.m[2] * m2.m[6] + m1.m[5] * m2.m[7] + m1.m[8] * m2.m[8];
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
Matrix3f MakeTranslationMatrix(const Vector3f& p)
|
||||||
|
{
|
||||||
|
Matrix3f r = Matrix3f::Identity();
|
||||||
|
|
||||||
|
r.m[12] = p.v[0];
|
||||||
|
r.m[13] = p.v[1];
|
||||||
|
r.m[14] = p.v[2];
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
Matrix3f MakeScaleMatrix(float scale)
|
||||||
|
{
|
||||||
|
Matrix3f r = Matrix3f::Identity();
|
||||||
|
|
||||||
|
r.m[0] = scale;
|
||||||
|
r.m[5] = scale;
|
||||||
|
r.m[10] = scale;
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
Matrix3f MakeRotationMatrix(const Vector3f& p)
|
||||||
|
{
|
||||||
|
Matrix3f r = Matrix3f::Identity();
|
||||||
|
|
||||||
|
r.m[12] = p.v[0];
|
||||||
|
r.m[13] = p.v[1];
|
||||||
|
r.m[14] = p.v[2];
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector2f operator*(Vector2f v, float scale)
|
||||||
|
{
|
||||||
|
Vector2f r = v;
|
||||||
|
|
||||||
|
r.v[0] = v.v[0] * scale;
|
||||||
|
r.v[1] = v.v[1] * scale;
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3f operator*(Vector3f v, float scale)
|
||||||
|
{
|
||||||
|
Vector3f r = v;
|
||||||
|
|
||||||
|
r.v[0] = v.v[0] * scale;
|
||||||
|
r.v[1] = v.v[1] * scale;
|
||||||
|
r.v[2] = v.v[2] * scale;
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector4f operator*(Vector4f v, float scale)
|
||||||
|
{
|
||||||
|
Vector4f r = v;
|
||||||
|
|
||||||
|
r.v[0] = v.v[0] * scale;
|
||||||
|
r.v[1] = v.v[1] * scale;
|
||||||
|
r.v[2] = v.v[2] * scale;
|
||||||
|
r.v[3] = v.v[3] * scale;
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3f MultVectorMatrix(Vector3f v, Matrix3f mt)
|
||||||
|
{
|
||||||
|
Vector3f r;
|
||||||
|
|
||||||
|
r.v[0] = v.v[0] * mt.m[0] + v.v[1] * mt.m[1] + v.v[2] * mt.m[2];
|
||||||
|
r.v[1] = v.v[0] * mt.m[3] + v.v[1] * mt.m[4] + v.v[2] * mt.m[5];
|
||||||
|
r.v[2] = v.v[0] * mt.m[6] + v.v[1] * mt.m[7] + v.v[2] * mt.m[8];
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector4f MultVectorMatrix(Vector4f v, Matrix4f mt)
|
||||||
|
{
|
||||||
|
Vector4f r;
|
||||||
|
|
||||||
|
r.v[0] = v.v[0] * mt.m[0] + v.v[1] * mt.m[1] + v.v[2] * mt.m[2] + v.v[3] * mt.m[3];
|
||||||
|
r.v[1] = v.v[0] * mt.m[4] + v.v[1] * mt.m[5] + v.v[2] * mt.m[6] + v.v[3] * mt.m[7];
|
||||||
|
r.v[2] = v.v[0] * mt.m[8] + v.v[1] * mt.m[9] + v.v[2] * mt.m[10] + v.v[3] * mt.m[11];
|
||||||
|
r.v[3] = v.v[0] * mt.m[12] + v.v[1] * mt.m[13] + v.v[2] * mt.m[14] + v.v[3] * mt.m[15];
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector4f MultMatrixVector(Matrix4f mt, Vector4f v)
|
||||||
|
{
|
||||||
|
Vector4f r;
|
||||||
|
|
||||||
|
r.v[0] = v.v[0] * mt.m[0] + v.v[1] * mt.m[4] + v.v[2] * mt.m[8] + v.v[3] * mt.m[12];
|
||||||
|
r.v[1] = v.v[0] * mt.m[1] + v.v[1] * mt.m[5] + v.v[2] * mt.m[9] + v.v[3] * mt.m[13];
|
||||||
|
r.v[2] = v.v[0] * mt.m[2] + v.v[1] * mt.m[6] + v.v[2] * mt.m[10] + v.v[3] * mt.m[14];
|
||||||
|
r.v[3] = v.v[0] * mt.m[3] + v.v[1] * mt.m[7] + v.v[2] * mt.m[11] + v.v[3] * mt.m[15];
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3f MultMatrixVector(Matrix3f mt, Vector3f v)
|
||||||
|
{
|
||||||
|
Vector3f r;
|
||||||
|
|
||||||
|
r.v[0] = v.v[0] * mt.m[0] + v.v[1] * mt.m[3] + v.v[2] * mt.m[6];
|
||||||
|
r.v[1] = v.v[0] * mt.m[1] + v.v[1] * mt.m[4] + v.v[2] * mt.m[7];
|
||||||
|
r.v[2] = v.v[0] * mt.m[2] + v.v[1] * mt.m[5] + v.v[2] * mt.m[8];
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector4f slerp(const Vector4f& q1, const Vector4f& q2, float t)
|
||||||
|
{
|
||||||
|
const float epsilon = 1e-6f;
|
||||||
|
|
||||||
|
// Нормализация входных кватернионов
|
||||||
|
Vector4f q1_norm = q1.normalized();
|
||||||
|
Vector4f q2_norm = q2.normalized();
|
||||||
|
|
||||||
|
float cosTheta = q1_norm.dot(q2_norm);
|
||||||
|
|
||||||
|
// Если q1 и q2 близки к противоположным направлениям, корректируем q2
|
||||||
|
Vector4f q2_adjusted = q2_norm;
|
||||||
|
if (cosTheta < 0.0f) {
|
||||||
|
q2_adjusted.v[0] = -q2_adjusted.v[0];
|
||||||
|
q2_adjusted.v[1] = -q2_adjusted.v[1];
|
||||||
|
q2_adjusted.v[2] = -q2_adjusted.v[2];
|
||||||
|
q2_adjusted.v[3] = -q2_adjusted.v[3];
|
||||||
|
cosTheta = -cosTheta;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если кватернионы близки, используем линейную интерполяцию
|
||||||
|
if (cosTheta > 1.0f - epsilon) {
|
||||||
|
Vector4f result;
|
||||||
|
|
||||||
|
result.v[0] = q1_norm.v[0] + t * (q2_adjusted.v[0] - q1_norm.v[0]);
|
||||||
|
result.v[1] = q1_norm.v[1] + t * (q2_adjusted.v[1] - q1_norm.v[1]);
|
||||||
|
result.v[2] = q1_norm.v[2] + t * (q2_adjusted.v[2] - q1_norm.v[2]);
|
||||||
|
result.v[3] = q1_norm.v[3] + t * (q2_adjusted.v[3] - q1_norm.v[3]);
|
||||||
|
|
||||||
|
return result.normalized();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Иначе используем сферическую интерполяцию
|
||||||
|
float theta = std::acos(cosTheta);
|
||||||
|
float sinTheta = std::sin(theta);
|
||||||
|
|
||||||
|
float coeff1 = std::sin((1.0f - t) * theta) / sinTheta;
|
||||||
|
float coeff2 = std::sin(t * theta) / sinTheta;
|
||||||
|
|
||||||
|
Vector4f result;
|
||||||
|
|
||||||
|
result.v[0] = coeff1 * q1_norm.v[0] + coeff2 * q2_adjusted.v[0];
|
||||||
|
result.v[1] = coeff1 * q1_norm.v[1] + coeff2 * q2_adjusted.v[1];
|
||||||
|
result.v[2] = coeff1 * q1_norm.v[2] + coeff2 * q2_adjusted.v[2];
|
||||||
|
result.v[3] = coeff1 * q1_norm.v[3] + coeff2 * q2_adjusted.v[3];
|
||||||
|
|
||||||
|
return result.normalized();
|
||||||
|
}
|
||||||
|
|
||||||
|
Matrix4f MakeMatrix4x4(const Matrix3f& m, const Vector3f pos)
|
||||||
|
{
|
||||||
|
Matrix4f r;
|
||||||
|
|
||||||
|
r.m[0] = m.m[0];
|
||||||
|
r.m[1] = m.m[1];
|
||||||
|
r.m[2] = m.m[2];
|
||||||
|
r.m[3] = 0;
|
||||||
|
|
||||||
|
r.m[4] = m.m[3];
|
||||||
|
r.m[5] = m.m[4];
|
||||||
|
r.m[6] = m.m[5];
|
||||||
|
r.m[7] = 0;
|
||||||
|
|
||||||
|
r.m[8] = m.m[6];
|
||||||
|
r.m[9] = m.m[7];
|
||||||
|
r.m[10] = m.m[8];
|
||||||
|
r.m[11] = 0;
|
||||||
|
|
||||||
|
r.m[12] = pos.v[0];
|
||||||
|
r.m[13] = pos.v[1];
|
||||||
|
r.m[14] = pos.v[2];
|
||||||
|
r.m[15] = 1.0;
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
179
ZLMath.h
Executable file
179
ZLMath.h
Executable file
@ -0,0 +1,179 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <exception>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
namespace ZL {
|
||||||
|
|
||||||
|
struct Vector4f
|
||||||
|
{
|
||||||
|
std::array<float, 4> v = { 0.f, 0.f, 0.f, 0.f };
|
||||||
|
|
||||||
|
Vector4f normalized() const {
|
||||||
|
double norm = std::sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2] + v[3] * v[3]);
|
||||||
|
if (norm <= 0.000001f) {
|
||||||
|
return { 0,0, 0, 0};
|
||||||
|
}
|
||||||
|
Vector4f r;
|
||||||
|
|
||||||
|
r.v[0] = v[0] / norm;
|
||||||
|
r.v[1] = v[1] / norm;
|
||||||
|
r.v[2] = v[2] / norm;
|
||||||
|
r.v[3] = v[3] / norm;
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
double dot(const Vector4f& other) const {
|
||||||
|
return v[0] * other.v[0] + v[1] * other.v[1] + v[2] * other.v[2] + v[3] * other.v[3];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Vector3f
|
||||||
|
{
|
||||||
|
std::array<float, 3> v = { 0.f, 0.f, 0.f };
|
||||||
|
|
||||||
|
Vector3f()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3f(float x, float y, float z)
|
||||||
|
: v{x,y,z}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3f normalized() const {
|
||||||
|
double norm = std::sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
|
||||||
|
if (norm <= 0.000001f) {
|
||||||
|
return { 0,0,0 };
|
||||||
|
}
|
||||||
|
Vector3f r;
|
||||||
|
|
||||||
|
r.v[0] = v[0] / norm;
|
||||||
|
r.v[1] = v[1] / norm;
|
||||||
|
r.v[2] = v[2] / norm;
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
float squaredNorm() const {
|
||||||
|
return v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
float length() const
|
||||||
|
{
|
||||||
|
return sqrt(squaredNorm());
|
||||||
|
}
|
||||||
|
|
||||||
|
float dot(const Vector3f& other) const {
|
||||||
|
return v[0] * other.v[0] + v[1] * other.v[1] + v[2] * other.v[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3f cross(const Vector3f& other) const {
|
||||||
|
return Vector3f(
|
||||||
|
v[1] * other.v[2] - v[2] * other.v[1],
|
||||||
|
v[2] * other.v[0] - v[0] * other.v[2],
|
||||||
|
v[0] * other.v[1] - v[1] * other.v[0]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator<(const Vector3f& other) const {
|
||||||
|
if (v[0] != other.v[0]) return v[0] < other.v[0];
|
||||||
|
if (v[1] != other.v[1]) return v[1] < other.v[1];
|
||||||
|
return v[2] < other.v[2];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
struct Vector2f
|
||||||
|
{
|
||||||
|
std::array<float, 2> v = {0.f, 0.f};
|
||||||
|
|
||||||
|
Vector2f()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector2f(float x, float y)
|
||||||
|
: v{ x,y }
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Vector2f operator+(const Vector2f& x, const Vector2f& y);
|
||||||
|
|
||||||
|
Vector2f operator-(const Vector2f& x, const Vector2f& y);
|
||||||
|
|
||||||
|
Vector3f operator+(const Vector3f& x, const Vector3f& y);
|
||||||
|
|
||||||
|
Vector3f operator-(const Vector3f& x, const Vector3f& y);
|
||||||
|
Vector4f operator+(const Vector4f& x, const Vector4f& y);
|
||||||
|
|
||||||
|
Vector4f operator-(const Vector4f& x, const Vector4f& y);
|
||||||
|
|
||||||
|
Vector3f operator-(const Vector3f& x);
|
||||||
|
|
||||||
|
|
||||||
|
struct Matrix3f
|
||||||
|
{
|
||||||
|
std::array<float, 9> m = { 0.f, 0.f, 0.f,
|
||||||
|
0.f, 0.f, 0.f,
|
||||||
|
0.f, 0.f, 0.f, };
|
||||||
|
|
||||||
|
static Matrix3f Identity();
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Matrix4f
|
||||||
|
{
|
||||||
|
std::array<float, 16> m = { 0.f, 0.f, 0.f, 0.f,
|
||||||
|
0.f, 0.f, 0.f, 0.f,
|
||||||
|
0.f, 0.f, 0.f, 0.f,
|
||||||
|
0.f, 0.f, 0.f, 0.f };
|
||||||
|
|
||||||
|
static Matrix4f Identity();
|
||||||
|
|
||||||
|
float& operator()(int row, int col) {
|
||||||
|
//return m[row * 4 + col]; //OpenGL specific
|
||||||
|
return m[col * 4 + row];
|
||||||
|
}
|
||||||
|
|
||||||
|
const float& operator()(int row, int col) const {
|
||||||
|
//return m[row * 4 + col];
|
||||||
|
return m[col * 4 + row];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Matrix4f operator*(const Matrix4f& m1, const Matrix4f& m2);
|
||||||
|
|
||||||
|
Matrix4f MakeOrthoMatrix(float width, float height, float zNear, float zFar);
|
||||||
|
Matrix4f MakeOrthoMatrix(float xmin, float xmax, float ymin, float ymax, float zNear, float zFar);
|
||||||
|
|
||||||
|
Matrix4f MakePerspectiveMatrix(float fovY, float aspectRatio, float zNear, float zFar);
|
||||||
|
|
||||||
|
Matrix3f QuatToMatrix(const Vector4f& q);
|
||||||
|
|
||||||
|
Vector4f MatrixToQuat(const Matrix3f& m);
|
||||||
|
|
||||||
|
Vector4f QuatFromRotateAroundX(float angle);
|
||||||
|
Vector4f QuatFromRotateAroundY(float angle);
|
||||||
|
Vector4f QuatFromRotateAroundZ(float angle);
|
||||||
|
|
||||||
|
Vector2f operator*(Vector2f v, float scale);
|
||||||
|
Vector3f operator*(Vector3f v, float scale);
|
||||||
|
Vector4f operator*(Vector4f v, float scale);
|
||||||
|
|
||||||
|
Vector3f MultVectorMatrix(Vector3f v, Matrix3f mt);
|
||||||
|
Vector4f MultVectorMatrix(Vector4f v, Matrix4f mt);
|
||||||
|
Vector4f MultMatrixVector(Matrix4f mt, Vector4f v);
|
||||||
|
Vector3f MultMatrixVector(Matrix3f mt, Vector3f v);
|
||||||
|
|
||||||
|
Vector4f slerp(const Vector4f& q1, const Vector4f& q2, float t);
|
||||||
|
Matrix3f InverseMatrix(const Matrix3f& m);
|
||||||
|
Matrix4f InverseMatrix(const Matrix4f& m);
|
||||||
|
Matrix3f MultMatrixMatrix(const Matrix3f& m1, const Matrix3f& m2);
|
||||||
|
Matrix4f MultMatrixMatrix(const Matrix4f& m1, const Matrix4f& m2);
|
||||||
|
Matrix4f MakeMatrix4x4(const Matrix3f& m, const Vector3f pos);
|
||||||
|
|
||||||
|
};
|
||||||
15036
blender scripts/output/spaceship005.txt
Normal file
15036
blender scripts/output/spaceship005.txt
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,49 +0,0 @@
|
|||||||
# cmake/FetchDependencies.cmake
|
|
||||||
|
|
||||||
set(THIRDPARTY_DIR "${CMAKE_CURRENT_LIST_DIR}/../thirdparty")
|
|
||||||
|
|
||||||
if(NOT EXISTS "${THIRDPARTY_DIR}")
|
|
||||||
file(MAKE_DIRECTORY "${THIRDPARTY_DIR}")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
macro(check_and_download URL ARCHIVE_NAME EXTRACTED_DIR_NAME CHECK_FILE)
|
|
||||||
set(ARCHIVE_PATH "${THIRDPARTY_DIR}/${ARCHIVE_NAME}")
|
|
||||||
set(SRC_PATH "${THIRDPARTY_DIR}/${EXTRACTED_DIR_NAME}")
|
|
||||||
|
|
||||||
if(NOT EXISTS "${ARCHIVE_PATH}")
|
|
||||||
message(STATUS "Downloading ${ARCHIVE_NAME}...")
|
|
||||||
file(DOWNLOAD "${URL}" "${ARCHIVE_PATH}" SHOW_PROGRESS)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(NOT EXISTS "${SRC_PATH}/${CHECK_FILE}")
|
|
||||||
message(STATUS "Extracting ${ARCHIVE_NAME}...")
|
|
||||||
execute_process(
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E tar xvf "${ARCHIVE_PATH}"
|
|
||||||
WORKING_DIRECTORY "${THIRDPARTY_DIR}"
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
endmacro()
|
|
||||||
|
|
||||||
# 1) ZLIB (Нужна только для инклудов, если не используете emscripten порты)
|
|
||||||
check_and_download("https://www.zlib.net/zlib131.zip" "zlib131.zip" "zlib-1.3.1" "CMakeLists.txt")
|
|
||||||
|
|
||||||
# 2) SDL2
|
|
||||||
check_and_download("https://github.com/libsdl-org/SDL/archive/refs/tags/release-2.32.10.zip" "release-2.32.10.zip" "SDL-release-2.32.10" "CMakeLists.txt")
|
|
||||||
|
|
||||||
# 3) LibPNG
|
|
||||||
check_and_download("https://github.com/pnggroup/libpng/archive/refs/tags/v1.6.51.zip" "v1.6.51.zip" "libpng-1.6.51" "CMakeLists.txt")
|
|
||||||
|
|
||||||
# 4) LibZip
|
|
||||||
check_and_download("https://github.com/nih-at/libzip/archive/refs/tags/v1.11.4.zip" "v1.11.4.zip" "libzip-1.11.4" "CMakeLists.txt")
|
|
||||||
|
|
||||||
# 5) Eigen
|
|
||||||
check_and_download("https://gitlab.com/libeigen/eigen/-/archive/5.0.0/eigen-5.0.0.zip" "eigen-5.0.0.zip" "eigen-5.0.0" "signature_of_eigen3_matrix_library")
|
|
||||||
|
|
||||||
# 6) Boost
|
|
||||||
check_and_download("https://archives.boost.io/release/1.90.0/source/boost_1_90_0.zip" "boost_1_90_0.zip" "boost_1_90_0" "boost")
|
|
||||||
|
|
||||||
# 7) FreeType
|
|
||||||
check_and_download("https://download.savannah.gnu.org/releases/freetype/freetype-2.14.1.tar.gz" "freetype-2.14.1.tar.gz" "freetype-2.14.1" "CMakeLists.txt")
|
|
||||||
|
|
||||||
# 8) SDL_ttf
|
|
||||||
check_and_download("https://github.com/libsdl-org/SDL_ttf/archive/refs/tags/release-2.24.0.zip" "release-2.24.0.zip" "SDL_ttf-release-2.24.0" "CMakeLists.txt")
|
|
||||||
@ -1,547 +0,0 @@
|
|||||||
# cmake/ThirdParty.cmake
|
|
||||||
|
|
||||||
include("${CMAKE_CURRENT_LIST_DIR}/FetchDependencies.cmake")
|
|
||||||
|
|
||||||
macro(log msg)
|
|
||||||
message(STATUS "[ThirdParty] ${msg}")
|
|
||||||
endmacro()
|
|
||||||
|
|
||||||
set(BUILD_CONFIGS Debug Release)
|
|
||||||
|
|
||||||
|
|
||||||
# ===========================================
|
|
||||||
# 1) ZLIB (zlib131.zip → zlib-1.3.1) - без изменений
|
|
||||||
# ===========================================
|
|
||||||
set(ZLIB_SRC_DIR "${THIRDPARTY_DIR}/zlib-1.3.1")
|
|
||||||
set(ZLIB_BUILD_DIR "${ZLIB_SRC_DIR}/build")
|
|
||||||
set(ZLIB_INSTALL_DIR "${ZLIB_SRC_DIR}/install")
|
|
||||||
|
|
||||||
|
|
||||||
file(MAKE_DIRECTORY "${ZLIB_BUILD_DIR}")
|
|
||||||
|
|
||||||
# проверяем, собран ли уже zlib
|
|
||||||
set(_have_zlib FALSE)
|
|
||||||
foreach(candidate
|
|
||||||
"${ZLIB_INSTALL_DIR}/lib/zlibstatic.lib"
|
|
||||||
)
|
|
||||||
if(EXISTS "${candidate}")
|
|
||||||
set(_have_zlib TRUE)
|
|
||||||
break()
|
|
||||||
endif()
|
|
||||||
endforeach()
|
|
||||||
|
|
||||||
|
|
||||||
if(NOT _have_zlib)
|
|
||||||
log("Configuring zlib ...")
|
|
||||||
execute_process(
|
|
||||||
COMMAND ${CMAKE_COMMAND}
|
|
||||||
-G "${CMAKE_GENERATOR}"
|
|
||||||
-S "${ZLIB_SRC_DIR}"
|
|
||||||
-B "${ZLIB_BUILD_DIR}"
|
|
||||||
-DCMAKE_INSTALL_PREFIX=${ZLIB_INSTALL_DIR}
|
|
||||||
RESULT_VARIABLE _zlib_cfg_res
|
|
||||||
)
|
|
||||||
if(NOT _zlib_cfg_res EQUAL 0)
|
|
||||||
message(FATAL_ERROR "zlib configure failed")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
foreach(cfg IN LISTS BUILD_CONFIGS)
|
|
||||||
log("Building ZLIB (${cfg}) ...")
|
|
||||||
execute_process(
|
|
||||||
COMMAND ${CMAKE_COMMAND}
|
|
||||||
--build "${ZLIB_BUILD_DIR}" --config ${cfg}
|
|
||||||
RESULT_VARIABLE _zlib_build_res
|
|
||||||
)
|
|
||||||
if(NOT _zlib_build_res EQUAL 0)
|
|
||||||
message(FATAL_ERROR "ZLIB build failed for configuration ${cfg}")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
log("Installing ZLIB (${cfg}) ...")
|
|
||||||
execute_process(
|
|
||||||
COMMAND ${CMAKE_COMMAND}
|
|
||||||
--install "${ZLIB_BUILD_DIR}" --config ${cfg}
|
|
||||||
RESULT_VARIABLE _zlib_inst_res
|
|
||||||
)
|
|
||||||
if(NOT _zlib_inst_res EQUAL 0)
|
|
||||||
message(FATAL_ERROR "ZLIB install failed for configuration ${cfg}")
|
|
||||||
endif()
|
|
||||||
endforeach()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# ИСПРАВЛЕНИЕ: Используем свойства для конкретных конфигураций
|
|
||||||
add_library(zlib_external_lib UNKNOWN IMPORTED GLOBAL)
|
|
||||||
set_target_properties(zlib_external_lib PROPERTIES
|
|
||||||
# Динамическая линковка (если zlib.lib - это импорт-библиотека для zlibd.dll)
|
|
||||||
#IMPORTED_LOCATION_DEBUG "${ZLIB_INSTALL_DIR}/lib/zlibd.lib"
|
|
||||||
#IMPORTED_LOCATION_RELEASE "${ZLIB_INSTALL_DIR}/lib/zlib.lib"
|
|
||||||
|
|
||||||
# Можно также указать статические библиотеки, если вы хотите их использовать
|
|
||||||
IMPORTED_LOCATION_DEBUG "${ZLIB_INSTALL_DIR}/lib/zlibstaticd.lib"
|
|
||||||
IMPORTED_LOCATION_RELEASE "${ZLIB_INSTALL_DIR}/lib/zlibstatic.lib"
|
|
||||||
|
|
||||||
INTERFACE_INCLUDE_DIRECTORIES "${ZLIB_INSTALL_DIR}/include"
|
|
||||||
)
|
|
||||||
|
|
||||||
# ===========================================
|
|
||||||
# 2) SDL2 (release-2.32.10.zip → SDL-release-2.32.10) - без изменений
|
|
||||||
# ===========================================
|
|
||||||
set(SDL2_SRC_DIR "${THIRDPARTY_DIR}/SDL-release-2.32.10")
|
|
||||||
set(SDL2_BUILD_DIR "${SDL2_SRC_DIR}/build")
|
|
||||||
set(SDL2_INSTALL_DIR "${SDL2_SRC_DIR}/install")
|
|
||||||
|
|
||||||
file(MAKE_DIRECTORY "${SDL2_BUILD_DIR}")
|
|
||||||
|
|
||||||
set(_have_sdl2 FALSE)
|
|
||||||
foreach(candidate
|
|
||||||
"${SDL2_INSTALL_DIR}/lib/SDL2.lib"
|
|
||||||
"${SDL2_INSTALL_DIR}/lib/SDL2-static.lib"
|
|
||||||
"${SDL2_INSTALL_DIR}/lib/SDL2d.lib"
|
|
||||||
)
|
|
||||||
if(EXISTS "${candidate}")
|
|
||||||
set(_have_sdl2 TRUE)
|
|
||||||
break()
|
|
||||||
endif()
|
|
||||||
endforeach()
|
|
||||||
|
|
||||||
if(NOT _have_sdl2)
|
|
||||||
log("Configuring SDL2 ...")
|
|
||||||
execute_process(
|
|
||||||
COMMAND ${CMAKE_COMMAND}
|
|
||||||
-G "${CMAKE_GENERATOR}"
|
|
||||||
-S "${SDL2_SRC_DIR}"
|
|
||||||
-B "${SDL2_BUILD_DIR}"
|
|
||||||
-DCMAKE_INSTALL_PREFIX=${SDL2_INSTALL_DIR}
|
|
||||||
-DCMAKE_PREFIX_PATH=${ZLIB_INSTALL_DIR} # путь к zlib для SDL2
|
|
||||||
RESULT_VARIABLE _sdl_cfg_res
|
|
||||||
)
|
|
||||||
if(NOT _sdl_cfg_res EQUAL 0)
|
|
||||||
message(FATAL_ERROR "SDL2 configure failed")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# --- ИЗМЕНЕНИЕ: Цикл по конфигурациям Debug и Release ---
|
|
||||||
foreach(cfg IN LISTS BUILD_CONFIGS)
|
|
||||||
log("Building SDL2 (${cfg}) ...")
|
|
||||||
execute_process(
|
|
||||||
COMMAND ${CMAKE_COMMAND}
|
|
||||||
--build "${SDL2_BUILD_DIR}" --config ${cfg}
|
|
||||||
RESULT_VARIABLE _sdl_build_res
|
|
||||||
)
|
|
||||||
if(NOT _sdl_build_res EQUAL 0)
|
|
||||||
message(FATAL_ERROR "SDL2 build failed for configuration ${cfg}")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
log("Installing SDL2 (${cfg}) ...")
|
|
||||||
execute_process(
|
|
||||||
COMMAND ${CMAKE_COMMAND}
|
|
||||||
--install "${SDL2_BUILD_DIR}" --config ${cfg}
|
|
||||||
RESULT_VARIABLE _sdl_inst_res
|
|
||||||
)
|
|
||||||
if(NOT _sdl_inst_res EQUAL 0)
|
|
||||||
message(FATAL_ERROR "SDL2 install failed for configuration ${cfg}")
|
|
||||||
endif()
|
|
||||||
endforeach()
|
|
||||||
# ------------------------------------------------------
|
|
||||||
endif()
|
|
||||||
|
|
||||||
|
|
||||||
# ИСПРАВЛЕНИЕ: SDL2: Используем свойства для конкретных конфигураций
|
|
||||||
add_library(SDL2_external_lib UNKNOWN IMPORTED GLOBAL)
|
|
||||||
set_target_properties(SDL2_external_lib PROPERTIES
|
|
||||||
# Динамическая линковка SDL2
|
|
||||||
IMPORTED_LOCATION_DEBUG "${SDL2_INSTALL_DIR}/lib/SDL2d.lib"
|
|
||||||
IMPORTED_LOCATION_RELEASE "${SDL2_INSTALL_DIR}/lib/SDL2.lib"
|
|
||||||
# Оба include-пути: и include, и include/SDL2
|
|
||||||
INTERFACE_INCLUDE_DIRECTORIES "${SDL2_INSTALL_DIR}/include;${SDL2_INSTALL_DIR}/include/SDL2"
|
|
||||||
)
|
|
||||||
|
|
||||||
# SDL2main (обычно статическая)
|
|
||||||
add_library(SDL2main_external_lib UNKNOWN IMPORTED GLOBAL)
|
|
||||||
set_target_properties(SDL2main_external_lib PROPERTIES
|
|
||||||
# ИСПРАВЛЕНО: Указываем пути для Debug и Release, используя
|
|
||||||
# соглашение, что Debug имеет суффикс 'd', а Release — нет.
|
|
||||||
IMPORTED_LOCATION_DEBUG "${SDL2_INSTALL_DIR}/lib/SDL2maind.lib"
|
|
||||||
IMPORTED_LOCATION_RELEASE "${SDL2_INSTALL_DIR}/lib/SDL2main.lib"
|
|
||||||
INTERFACE_INCLUDE_DIRECTORIES "${SDL2_INSTALL_DIR}/include"
|
|
||||||
)
|
|
||||||
|
|
||||||
log("-----${SDL2_INSTALL_DIR}/lib/SDL2maind.lib")
|
|
||||||
|
|
||||||
# ===========================================
|
|
||||||
# 3) libpng (v1.6.51.zip → libpng-1.6.51) - без изменений
|
|
||||||
# ===========================================
|
|
||||||
set(LIBPNG_SRC_DIR "${THIRDPARTY_DIR}/libpng-1.6.51")
|
|
||||||
set(LIBPNG_BUILD_DIR "${LIBPNG_SRC_DIR}/build")
|
|
||||||
set(LIBPNG_INSTALL_DIR "${LIBPNG_SRC_DIR}/install") # на будущее
|
|
||||||
|
|
||||||
file(MAKE_DIRECTORY "${LIBPNG_BUILD_DIR}")
|
|
||||||
|
|
||||||
# Проверяем, есть ли уже .lib (build/Debug или install/lib)
|
|
||||||
set(_libpng_candidates
|
|
||||||
"${LIBPNG_BUILD_DIR}/Debug/libpng16_staticd.lib"
|
|
||||||
"${LIBPNG_BUILD_DIR}/Release/libpng16_static.lib"
|
|
||||||
)
|
|
||||||
|
|
||||||
set(_have_png FALSE)
|
|
||||||
foreach(candidate IN LISTS _libpng_candidates)
|
|
||||||
if(EXISTS "${candidate}")
|
|
||||||
set(_have_png TRUE)
|
|
||||||
break()
|
|
||||||
endif()
|
|
||||||
endforeach()
|
|
||||||
|
|
||||||
if(NOT _have_png)
|
|
||||||
log("Configuring libpng ...")
|
|
||||||
execute_process(
|
|
||||||
COMMAND ${CMAKE_COMMAND}
|
|
||||||
-G "${CMAKE_GENERATOR}"
|
|
||||||
-S "${LIBPNG_SRC_DIR}"
|
|
||||||
-B "${LIBPNG_BUILD_DIR}"
|
|
||||||
-DCMAKE_INSTALL_PREFIX=${LIBPNG_INSTALL_DIR}
|
|
||||||
-DCMAKE_PREFIX_PATH=${ZLIB_INSTALL_DIR}
|
|
||||||
-DZLIB_ROOT=${ZLIB_INSTALL_DIR}
|
|
||||||
RESULT_VARIABLE _png_cfg_res
|
|
||||||
)
|
|
||||||
if(NOT _png_cfg_res EQUAL 0)
|
|
||||||
message(FATAL_ERROR "libpng configure failed")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# --- ИЗМЕНЕНИЕ: Цикл по конфигурациям Debug и Release ---
|
|
||||||
foreach(cfg IN LISTS BUILD_CONFIGS)
|
|
||||||
log("Building libpng (${cfg}) ...")
|
|
||||||
execute_process(
|
|
||||||
COMMAND ${CMAKE_COMMAND}
|
|
||||||
--build "${LIBPNG_BUILD_DIR}" --config ${cfg}
|
|
||||||
RESULT_VARIABLE _png_build_res
|
|
||||||
)
|
|
||||||
if(NOT _png_build_res EQUAL 0)
|
|
||||||
message(FATAL_ERROR "libpng build failed for configuration ${cfg}")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Поскольку вы не используете "cmake --install" для libpng,
|
|
||||||
# здесь нет необходимости в дополнительном шаге установки.
|
|
||||||
# Файлы .lib будут сгенерированы в подкаталоге ${LIBPNG_BUILD_DIR}/${cfg} (например, build/Debug или build/Release).
|
|
||||||
|
|
||||||
endforeach()
|
|
||||||
# ------------------------------------------------------
|
|
||||||
endif()
|
|
||||||
|
|
||||||
add_library(libpng_external_lib UNKNOWN IMPORTED GLOBAL)
|
|
||||||
set_target_properties(libpng_external_lib PROPERTIES
|
|
||||||
# Предполагая, что libpng использует статический вариант
|
|
||||||
IMPORTED_LOCATION_DEBUG "${LIBPNG_BUILD_DIR}/Debug/libpng16_staticd.lib"
|
|
||||||
IMPORTED_LOCATION_RELEASE "${LIBPNG_BUILD_DIR}/Release/libpng16_static.lib"
|
|
||||||
|
|
||||||
# png.h, pngconf.h – в SRC, pnglibconf.h – в BUILD
|
|
||||||
INTERFACE_INCLUDE_DIRECTORIES "${LIBPNG_SRC_DIR};${LIBPNG_BUILD_DIR}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# ===========================================
|
|
||||||
# 4) libzip (v1.11.4.zip → libzip-1.11.4) - НОВАЯ ЗАВИСИМОСТЬ
|
|
||||||
# ===========================================
|
|
||||||
set(LIBZIP_SRC_DIR "${THIRDPARTY_DIR}/libzip-1.11.4")
|
|
||||||
set(LIBZIP_BUILD_DIR "${LIBZIP_SRC_DIR}/build")
|
|
||||||
#set(LIBZIP_INSTALL_DIR "${LIBZIP_SRC_DIR}/install")
|
|
||||||
set(LIBZIP_BASE_DIR "${LIBZIP_SRC_DIR}/install")
|
|
||||||
|
|
||||||
file(MAKE_DIRECTORY "${LIBZIP_BUILD_DIR}")
|
|
||||||
|
|
||||||
# Проверяем, собран ли уже libzip
|
|
||||||
set(_have_zip FALSE)
|
|
||||||
foreach(candidate
|
|
||||||
"${LIBZIP_BASE_DIR}-Debug/lib/zip.lib"
|
|
||||||
"${LIBZIP_BASE_DIR}-Release/lib/zip.lib"
|
|
||||||
)
|
|
||||||
if(EXISTS "${candidate}")
|
|
||||||
set(_have_zip TRUE)
|
|
||||||
break()
|
|
||||||
endif()
|
|
||||||
endforeach()
|
|
||||||
|
|
||||||
if(NOT _have_zip)
|
|
||||||
foreach(cfg IN LISTS BUILD_CONFIGS)
|
|
||||||
log("Configuring libzip (${cfg})...")
|
|
||||||
execute_process(
|
|
||||||
COMMAND ${CMAKE_COMMAND}
|
|
||||||
-G "${CMAKE_GENERATOR}"
|
|
||||||
-S "${LIBZIP_SRC_DIR}"
|
|
||||||
-B "${LIBZIP_SRC_DIR}/build-${cfg}"
|
|
||||||
-DCMAKE_INSTALL_PREFIX=${LIBZIP_BASE_DIR}-${cfg}
|
|
||||||
-DCMAKE_PREFIX_PATH=${ZLIB_INSTALL_DIR}
|
|
||||||
-DZLIB_ROOT=${ZLIB_INSTALL_DIR}
|
|
||||||
-DENABLE_COMMONCRYPTO=OFF
|
|
||||||
-DENABLE_GNUTLS=OFF
|
|
||||||
-DENABLE_MBEDTLS=OFF
|
|
||||||
-DENABLE_OPENSSL=OFF
|
|
||||||
-DENABLE_WINDOWS_CRYPTO=OFF
|
|
||||||
-DENABLE_FUZZ=OFF
|
|
||||||
RESULT_VARIABLE _zip_cfg_res
|
|
||||||
)
|
|
||||||
if(NOT _zip_cfg_res EQUAL 0)
|
|
||||||
message(FATAL_ERROR "libzip configure failed")
|
|
||||||
endif()
|
|
||||||
log("Building libzip (${cfg}) ...")
|
|
||||||
|
|
||||||
|
|
||||||
execute_process(
|
|
||||||
COMMAND ${CMAKE_COMMAND} --build "${LIBZIP_SRC_DIR}/build-${cfg}" --config ${cfg} -v
|
|
||||||
RESULT_VARIABLE _zip_build_res
|
|
||||||
)
|
|
||||||
if(NOT _zip_build_res EQUAL 0)
|
|
||||||
message(FATAL_ERROR "libzip build failed for configuration ${cfg}")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
log("Installing libzip (${cfg}) ...")
|
|
||||||
|
|
||||||
execute_process(
|
|
||||||
COMMAND ${CMAKE_COMMAND} --install "${LIBZIP_SRC_DIR}/build-${cfg}" --config ${cfg} -v
|
|
||||||
RESULT_VARIABLE _zip_inst_res
|
|
||||||
)
|
|
||||||
if(NOT _zip_inst_res EQUAL 0)
|
|
||||||
message(FATAL_ERROR "libzip install failed for configuration ${cfg}")
|
|
||||||
endif()
|
|
||||||
endforeach()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
|
|
||||||
add_library(libzip_external_lib UNKNOWN IMPORTED GLOBAL)
|
|
||||||
set_target_properties(libzip_external_lib PROPERTIES
|
|
||||||
IMPORTED_LOCATION_DEBUG "${LIBZIP_BASE_DIR}-Debug/lib/zip.lib" # ИСПРАВЛЕНО
|
|
||||||
IMPORTED_LOCATION_RELEASE "${LIBZIP_BASE_DIR}-Release/lib/zip.lib" # ИСПРАВЛЕНО
|
|
||||||
|
|
||||||
INTERFACE_INCLUDE_DIRECTORIES "$<IF:$<CONFIG:Debug>,${LIBZIP_BASE_DIR}-Debug/include,${LIBZIP_BASE_DIR}-Release/include>"
|
|
||||||
# libzip требует zlib для линковки
|
|
||||||
INTERFACE_LINK_LIBRARIES zlib_external_lib
|
|
||||||
)
|
|
||||||
|
|
||||||
# ===========================================
|
|
||||||
# 5) FreeType (2.14.1) - dependency for SDL_ttf
|
|
||||||
# ===========================================
|
|
||||||
set(FREETYPE_SRC_DIR "${THIRDPARTY_DIR}/freetype-2.14.1")
|
|
||||||
set(FREETYPE_BASE_DIR "${FREETYPE_SRC_DIR}/install")
|
|
||||||
|
|
||||||
set(_have_freetype TRUE)
|
|
||||||
foreach(cfg IN LISTS BUILD_CONFIGS)
|
|
||||||
if(NOT EXISTS "${FREETYPE_BASE_DIR}-${cfg}/lib/freetype.lib" AND
|
|
||||||
NOT EXISTS "${FREETYPE_BASE_DIR}-${cfg}/lib/freetyped.lib")
|
|
||||||
set(_have_freetype FALSE)
|
|
||||||
endif()
|
|
||||||
endforeach()
|
|
||||||
|
|
||||||
if(NOT _have_freetype)
|
|
||||||
foreach(cfg IN LISTS BUILD_CONFIGS)
|
|
||||||
log("Configuring FreeType (${cfg}) ...")
|
|
||||||
execute_process(
|
|
||||||
COMMAND ${CMAKE_COMMAND}
|
|
||||||
-G "${CMAKE_GENERATOR}"
|
|
||||||
-S "${FREETYPE_SRC_DIR}"
|
|
||||||
-B "${FREETYPE_SRC_DIR}/build-${cfg}"
|
|
||||||
-DCMAKE_INSTALL_PREFIX=${FREETYPE_BASE_DIR}-${cfg}
|
|
||||||
-DCMAKE_PREFIX_PATH="${ZLIB_INSTALL_DIR};${LIBPNG_INSTALL_DIR}"
|
|
||||||
-DCMAKE_DISABLE_FIND_PACKAGE_HarfBuzz=TRUE
|
|
||||||
-DCMAKE_DISABLE_FIND_PACKAGE_BZip2=TRUE
|
|
||||||
-DFT_DISABLE_BROTLI=ON
|
|
||||||
-DBUILD_SHARED_LIBS=OFF
|
|
||||||
RESULT_VARIABLE _ft_cfg_res
|
|
||||||
)
|
|
||||||
if(NOT _ft_cfg_res EQUAL 0)
|
|
||||||
message(FATAL_ERROR "FreeType configure failed for ${cfg}")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
log("Building FreeType (${cfg}) ...")
|
|
||||||
execute_process(
|
|
||||||
COMMAND ${CMAKE_COMMAND}
|
|
||||||
--build "${FREETYPE_SRC_DIR}/build-${cfg}" --config ${cfg}
|
|
||||||
RESULT_VARIABLE _ft_build_res
|
|
||||||
)
|
|
||||||
if(NOT _ft_build_res EQUAL 0)
|
|
||||||
message(FATAL_ERROR "FreeType build failed for ${cfg}")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
log("Installing FreeType (${cfg}) ...")
|
|
||||||
execute_process(
|
|
||||||
COMMAND ${CMAKE_COMMAND}
|
|
||||||
--install "${FREETYPE_SRC_DIR}/build-${cfg}" --config ${cfg}
|
|
||||||
RESULT_VARIABLE _ft_inst_res
|
|
||||||
)
|
|
||||||
if(NOT _ft_inst_res EQUAL 0)
|
|
||||||
message(FATAL_ERROR "FreeType install failed for ${cfg}")
|
|
||||||
endif()
|
|
||||||
endforeach()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set(_ft_debug_lib "")
|
|
||||||
foreach(cand
|
|
||||||
"${FREETYPE_BASE_DIR}-Debug/lib/freetyped.lib"
|
|
||||||
"${FREETYPE_BASE_DIR}-Debug/lib/freetype.lib"
|
|
||||||
)
|
|
||||||
if(EXISTS "${cand}")
|
|
||||||
set(_ft_debug_lib "${cand}")
|
|
||||||
break()
|
|
||||||
endif()
|
|
||||||
endforeach()
|
|
||||||
|
|
||||||
set(_ft_release_lib "")
|
|
||||||
foreach(cand
|
|
||||||
"${FREETYPE_BASE_DIR}-Release/lib/freetype.lib"
|
|
||||||
)
|
|
||||||
if(EXISTS "${cand}")
|
|
||||||
set(_ft_release_lib "${cand}")
|
|
||||||
break()
|
|
||||||
endif()
|
|
||||||
endforeach()
|
|
||||||
|
|
||||||
if(_ft_debug_lib STREQUAL "" OR _ft_release_lib STREQUAL "")
|
|
||||||
message(FATAL_ERROR "FreeType libs not found in ${FREETYPE_BASE_DIR}-Debug/Release")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
add_library(freetype_external_lib UNKNOWN IMPORTED GLOBAL)
|
|
||||||
set_target_properties(freetype_external_lib PROPERTIES
|
|
||||||
IMPORTED_LOCATION_DEBUG "${_ft_debug_lib}"
|
|
||||||
IMPORTED_LOCATION_RELEASE "${_ft_release_lib}"
|
|
||||||
INTERFACE_INCLUDE_DIRECTORIES
|
|
||||||
"$<IF:$<CONFIG:Debug>,${FREETYPE_BASE_DIR}-Debug/include,${FREETYPE_BASE_DIR}-Release/include>"
|
|
||||||
)
|
|
||||||
|
|
||||||
# ===========================================
|
|
||||||
# 6) SDL_ttf (2.24.0)
|
|
||||||
# ===========================================
|
|
||||||
set(SDL2TTF_SRC_DIR "${THIRDPARTY_DIR}/SDL_ttf-release-2.24.0")
|
|
||||||
set(SDL2TTF_BASE_DIR "${SDL2TTF_SRC_DIR}/install")
|
|
||||||
|
|
||||||
set(_have_sdl2ttf TRUE)
|
|
||||||
foreach(cfg IN LISTS BUILD_CONFIGS)
|
|
||||||
if(NOT EXISTS "${SDL2TTF_BASE_DIR}-${cfg}/lib/SDL2_ttf.lib" AND
|
|
||||||
NOT EXISTS "${SDL2TTF_BASE_DIR}-${cfg}/lib/SDL2_ttfd.lib")
|
|
||||||
set(_have_sdl2ttf FALSE)
|
|
||||||
endif()
|
|
||||||
endforeach()
|
|
||||||
|
|
||||||
if(NOT _have_sdl2ttf)
|
|
||||||
foreach(cfg IN LISTS BUILD_CONFIGS)
|
|
||||||
|
|
||||||
if(cfg STREQUAL "Debug")
|
|
||||||
set(_SDL2_LIB "${SDL2_INSTALL_DIR}/lib/SDL2d.lib")
|
|
||||||
else()
|
|
||||||
set(_SDL2_LIB "${SDL2_INSTALL_DIR}/lib/SDL2.lib")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set(_FT_PREFIX "${FREETYPE_BASE_DIR}-${cfg}")
|
|
||||||
|
|
||||||
set(_FT_LIB "")
|
|
||||||
foreach(cand
|
|
||||||
"${_FT_PREFIX}/lib/freetyped.lib"
|
|
||||||
"${_FT_PREFIX}/lib/freetype.lib"
|
|
||||||
)
|
|
||||||
if(EXISTS "${cand}")
|
|
||||||
set(_FT_LIB "${cand}")
|
|
||||||
break()
|
|
||||||
endif()
|
|
||||||
endforeach()
|
|
||||||
|
|
||||||
if(_FT_LIB STREQUAL "")
|
|
||||||
message(FATAL_ERROR "FreeType library not found for ${cfg}")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
log("Configuring SDL_ttf (${cfg}) ...")
|
|
||||||
execute_process(
|
|
||||||
COMMAND ${CMAKE_COMMAND}
|
|
||||||
-G "${CMAKE_GENERATOR}"
|
|
||||||
-S "${SDL2TTF_SRC_DIR}"
|
|
||||||
-B "${SDL2TTF_SRC_DIR}/build-${cfg}"
|
|
||||||
-DCMAKE_INSTALL_PREFIX=${SDL2TTF_BASE_DIR}-${cfg}
|
|
||||||
-DCMAKE_PREFIX_PATH=${_FT_PREFIX};${SDL2_INSTALL_DIR}
|
|
||||||
-DSDL2_LIBRARY=${_SDL2_LIB}
|
|
||||||
-DSDL2_INCLUDE_DIR=${SDL2_INSTALL_DIR}/include/SDL2
|
|
||||||
-DFREETYPE_LIBRARY=${_FT_LIB}
|
|
||||||
-DFREETYPE_INCLUDE_DIR=${_FT_PREFIX}/include
|
|
||||||
-DFREETYPE_INCLUDE_DIRS=${_FT_PREFIX}/include
|
|
||||||
-DSDL2TTF_VENDORED=OFF
|
|
||||||
-DSDL2TTF_SAMPLES=OFF
|
|
||||||
RESULT_VARIABLE _ttf_cfg_res
|
|
||||||
)
|
|
||||||
if(NOT _ttf_cfg_res EQUAL 0)
|
|
||||||
message(FATAL_ERROR "SDL_ttf configure failed for ${cfg}")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
log("Building SDL_ttf (${cfg}) ...")
|
|
||||||
execute_process(
|
|
||||||
COMMAND ${CMAKE_COMMAND}
|
|
||||||
--build "${SDL2TTF_SRC_DIR}/build-${cfg}" --config ${cfg}
|
|
||||||
RESULT_VARIABLE _ttf_build_res
|
|
||||||
)
|
|
||||||
if(NOT _ttf_build_res EQUAL 0)
|
|
||||||
message(FATAL_ERROR "SDL_ttf build failed for ${cfg}")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
log("Installing SDL_ttf (${cfg}) ...")
|
|
||||||
execute_process(
|
|
||||||
COMMAND ${CMAKE_COMMAND}
|
|
||||||
--install "${SDL2TTF_SRC_DIR}/build-${cfg}" --config ${cfg}
|
|
||||||
RESULT_VARIABLE _ttf_inst_res
|
|
||||||
)
|
|
||||||
if(NOT _ttf_inst_res EQUAL 0)
|
|
||||||
message(FATAL_ERROR "SDL_ttf install failed for ${cfg}")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
endforeach()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set(_ttf_debug_lib "")
|
|
||||||
foreach(cand
|
|
||||||
"${SDL2TTF_BASE_DIR}-Debug/lib/SDL2_ttfd.lib"
|
|
||||||
"${SDL2TTF_BASE_DIR}-Debug/lib/SDL2_ttf.lib"
|
|
||||||
)
|
|
||||||
if(EXISTS "${cand}")
|
|
||||||
set(_ttf_debug_lib "${cand}")
|
|
||||||
break()
|
|
||||||
endif()
|
|
||||||
endforeach()
|
|
||||||
|
|
||||||
set(_ttf_release_lib "")
|
|
||||||
foreach(cand
|
|
||||||
"${SDL2TTF_BASE_DIR}-Release/lib/SDL2_ttf.lib"
|
|
||||||
)
|
|
||||||
if(EXISTS "${cand}")
|
|
||||||
set(_ttf_release_lib "${cand}")
|
|
||||||
break()
|
|
||||||
endif()
|
|
||||||
endforeach()
|
|
||||||
|
|
||||||
if(_ttf_debug_lib STREQUAL "" OR _ttf_release_lib STREQUAL "")
|
|
||||||
message(FATAL_ERROR "SDL_ttf libs not found in install-Debug / install-Release")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
add_library(SDL2_ttf_external_lib UNKNOWN IMPORTED GLOBAL)
|
|
||||||
set_target_properties(SDL2_ttf_external_lib PROPERTIES
|
|
||||||
IMPORTED_LOCATION_DEBUG "${_ttf_debug_lib}"
|
|
||||||
IMPORTED_LOCATION_RELEASE "${_ttf_release_lib}"
|
|
||||||
INTERFACE_INCLUDE_DIRECTORIES
|
|
||||||
"$<IF:$<CONFIG:Debug>,${SDL2TTF_BASE_DIR}-Debug/include,${SDL2TTF_BASE_DIR}-Release/include>;$<IF:$<CONFIG:Debug>,${SDL2TTF_BASE_DIR}-Debug/include/SDL2,${SDL2TTF_BASE_DIR}-Release/include/SDL2>"
|
|
||||||
INTERFACE_LINK_LIBRARIES
|
|
||||||
"SDL2_external_lib;freetype_external_lib"
|
|
||||||
)
|
|
||||||
|
|
||||||
# ===========================================
|
|
||||||
# 7) Eigen (5.0.0.zip → eigen-5.0.0) - HEADER-ONLY
|
|
||||||
# ===========================================
|
|
||||||
set(EIGEN_SRC_DIR "${THIRDPARTY_DIR}/eigen-5.0.0")
|
|
||||||
|
|
||||||
if(NOT TARGET eigen_external_lib)
|
|
||||||
add_library(eigen_external_lib INTERFACE)
|
|
||||||
target_include_directories(eigen_external_lib INTERFACE "${EIGEN_SRC_DIR}")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# ===========================================
|
|
||||||
# 8) Boost (1.90.0) - HEADER-ONLY
|
|
||||||
# ===========================================
|
|
||||||
set(BOOST_VERSION "1.90.0")
|
|
||||||
set(BOOST_ARCHIVE_NAME "boost_1_90_0.zip")
|
|
||||||
set(BOOST_ARCHIVE "${THIRDPARTY_DIR}/${BOOST_ARCHIVE_NAME}")
|
|
||||||
# Внутри архива папка называется boost_1_90_0
|
|
||||||
set(BOOST_SRC_DIR "${THIRDPARTY_DIR}/boost_1_90_0")
|
|
||||||
|
|
||||||
if(NOT TARGET boost_external_lib)
|
|
||||||
add_library(boost_external_lib INTERFACE)
|
|
||||||
# Boost заголовки находятся непосредственно в корне распакованной папки
|
|
||||||
target_include_directories(boost_external_lib INTERFACE "${BOOST_SRC_DIR}")
|
|
||||||
endif()
|
|
||||||
156
config/ui.json
156
config/ui.json
@ -1,156 +0,0 @@
|
|||||||
{
|
|
||||||
"root": {
|
|
||||||
"type": "FrameLayout",
|
|
||||||
"x": 0,
|
|
||||||
"y": 0,
|
|
||||||
"width": 1280,
|
|
||||||
"height": 720,
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"type": "FrameLayout",
|
|
||||||
"name": "leftPanel",
|
|
||||||
"x": 100,
|
|
||||||
"y": 100,
|
|
||||||
"width": 320,
|
|
||||||
"height": 400,
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"type": "LinearLayout",
|
|
||||||
"name": "mainButtons",
|
|
||||||
"orientation": "vertical",
|
|
||||||
"spacing": 10,
|
|
||||||
"x": 0,
|
|
||||||
"y": 0,
|
|
||||||
"width": 300,
|
|
||||||
"height": 300,
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"type": "Button",
|
|
||||||
"name": "playButton",
|
|
||||||
"x": 100,
|
|
||||||
"y": 300,
|
|
||||||
"width": 200,
|
|
||||||
"height": 50,
|
|
||||||
"animations": {
|
|
||||||
"buttonsExit": {
|
|
||||||
"repeat": false,
|
|
||||||
"steps": [
|
|
||||||
{
|
|
||||||
"type": "move",
|
|
||||||
"to": [
|
|
||||||
-400,
|
|
||||||
0
|
|
||||||
],
|
|
||||||
"duration": 1.0,
|
|
||||||
"easing": "easein"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"textures": {
|
|
||||||
"normal": "./resources/button.png",
|
|
||||||
"hover": "./resources/sand.png",
|
|
||||||
"pressed": "./resources/button.png"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "Button",
|
|
||||||
"name": "settingsButton",
|
|
||||||
"x": 100,
|
|
||||||
"y": 200,
|
|
||||||
"width": 200,
|
|
||||||
"height": 50,
|
|
||||||
"animations": {
|
|
||||||
"buttonsExit": {
|
|
||||||
"repeat": false,
|
|
||||||
"steps": [
|
|
||||||
{
|
|
||||||
"type": "wait",
|
|
||||||
"duration": 0.5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "move",
|
|
||||||
"to": [
|
|
||||||
-400,
|
|
||||||
0
|
|
||||||
],
|
|
||||||
"duration": 1.0,
|
|
||||||
"easing": "easein"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"textures": {
|
|
||||||
"normal": "./resources/sand.png",
|
|
||||||
"hover": "./resources/button.png",
|
|
||||||
"pressed": "./resources/sand.png"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "Button",
|
|
||||||
"name": "exitButton",
|
|
||||||
"x": 100,
|
|
||||||
"y": 100,
|
|
||||||
"width": 200,
|
|
||||||
"height": 50,
|
|
||||||
"animations": {
|
|
||||||
"buttonsExit": {
|
|
||||||
"repeat": false,
|
|
||||||
"steps": [
|
|
||||||
{
|
|
||||||
"type": "wait",
|
|
||||||
"duration": 1.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "move",
|
|
||||||
"to": [
|
|
||||||
-400,
|
|
||||||
0
|
|
||||||
],
|
|
||||||
"duration": 1.0,
|
|
||||||
"easing": "easein"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"bgScroll": {
|
|
||||||
"repeat": true,
|
|
||||||
"steps": [
|
|
||||||
{
|
|
||||||
"type": "move",
|
|
||||||
"to": [
|
|
||||||
1280,
|
|
||||||
0
|
|
||||||
],
|
|
||||||
"duration": 5.0,
|
|
||||||
"easing": "linear"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"textures": {
|
|
||||||
"normal": "./resources/rock.png",
|
|
||||||
"hover": "./resources/button.png",
|
|
||||||
"pressed": "./resources/rock.png"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "Slider",
|
|
||||||
"name": "musicVolumeSlider",
|
|
||||||
"x": 1140,
|
|
||||||
"y": 100,
|
|
||||||
"width": 10,
|
|
||||||
"height": 500,
|
|
||||||
"value": 0.5,
|
|
||||||
"orientation": "vertical",
|
|
||||||
"textures": {
|
|
||||||
"track": "./resources/musicVolumeBarTexture.png",
|
|
||||||
"knob": "./resources/musicVolumeBarButton.png"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
0
src/gl/glext.h → gl/glext.h
Normal file → Executable file
0
src/gl/glext.h → gl/glext.h
Normal file → Executable file
88
main.cpp
Executable file
88
main.cpp
Executable file
@ -0,0 +1,88 @@
|
|||||||
|
#include "Game.h"
|
||||||
|
#include "Environment.h"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
ZL::Game game;
|
||||||
|
void MainLoop() {
|
||||||
|
game.update();
|
||||||
|
}
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
try
|
||||||
|
{
|
||||||
|
constexpr int CONST_WIDTH = 1280;
|
||||||
|
constexpr int CONST_HEIGHT = 720;
|
||||||
|
|
||||||
|
ZL::Environment::width = CONST_WIDTH;
|
||||||
|
ZL::Environment::height = CONST_HEIGHT;
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef EMSCRIPTEN
|
||||||
|
if (SDL_Init(SDL_INIT_VIDEO) != 0) {
|
||||||
|
std::cerr << "SDL_Init failed: " << SDL_GetError() << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
|
||||||
|
|
||||||
|
SDL_Window* win = SDL_CreateWindow("Space Ship Game",
|
||||||
|
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
|
||||||
|
CONST_WIDTH, CONST_HEIGHT,
|
||||||
|
SDL_WINDOW_OPENGL);
|
||||||
|
|
||||||
|
if (!win) {
|
||||||
|
std::cerr << "SDL_CreateWindow failed: " << SDL_GetError() << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_GLContext glContext = SDL_GL_CreateContext(win);
|
||||||
|
if (!glContext) {
|
||||||
|
std::cerr << "SDL_GL_CreateContext failed: " << SDL_GetError() << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Привязка контекста к окну — важно!
|
||||||
|
SDL_GL_MakeCurrent(win, glContext);
|
||||||
|
|
||||||
|
ZL::Environment::window = win;
|
||||||
|
#else
|
||||||
|
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) != 0) {
|
||||||
|
SDL_Log("SDL init failed: %s", SDL_GetError());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||||
|
|
||||||
|
ZL::Environment::window = SDL_CreateWindow(
|
||||||
|
"Space Ship Game",
|
||||||
|
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
|
||||||
|
CONST_WIDTH, CONST_HEIGHT,
|
||||||
|
SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN
|
||||||
|
);
|
||||||
|
|
||||||
|
SDL_GLContext ctx = SDL_GL_CreateContext(ZL::Environment::window);
|
||||||
|
SDL_GL_MakeCurrent(ZL::Environment::window, ctx);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
game.setup();
|
||||||
|
|
||||||
|
#ifdef EMSCRIPTEN
|
||||||
|
emscripten_set_main_loop(MainLoop, 0, 1);
|
||||||
|
#else
|
||||||
|
while (!game.shouldExit()) {
|
||||||
|
game.update();
|
||||||
|
SDL_Delay(2);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (const std::exception& e)
|
||||||
|
{
|
||||||
|
std::cout << e.what() << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
65
proj-android/.gitignore
vendored
65
proj-android/.gitignore
vendored
@ -1,65 +0,0 @@
|
|||||||
# Built application files
|
|
||||||
*.apk
|
|
||||||
*.ap_
|
|
||||||
*.aab
|
|
||||||
|
|
||||||
# Files for the ART/Dalvik VM
|
|
||||||
*.dex
|
|
||||||
|
|
||||||
# Java class files
|
|
||||||
*.class
|
|
||||||
|
|
||||||
# Generated files
|
|
||||||
bin/
|
|
||||||
gen/
|
|
||||||
out/
|
|
||||||
|
|
||||||
# Gradle files
|
|
||||||
.gradle/
|
|
||||||
build/
|
|
||||||
.idea/
|
|
||||||
|
|
||||||
# Local configuration file (sdk path, etc)
|
|
||||||
local.properties
|
|
||||||
|
|
||||||
# CMake build files
|
|
||||||
CMakeFiles/
|
|
||||||
cmake_install.cmake
|
|
||||||
CMakeCache.txt
|
|
||||||
Makefile
|
|
||||||
*.cmake
|
|
||||||
|
|
||||||
# Android Studio
|
|
||||||
*.iml
|
|
||||||
.idea/
|
|
||||||
.cxx/
|
|
||||||
.gradle/
|
|
||||||
.DS_Store
|
|
||||||
/captures/
|
|
||||||
.externalNativeBuild/
|
|
||||||
|
|
||||||
# NDK
|
|
||||||
.obj/
|
|
||||||
*.o
|
|
||||||
*.so
|
|
||||||
|
|
||||||
# SDL2 (если используешь)
|
|
||||||
libs/
|
|
||||||
obj/
|
|
||||||
|
|
||||||
# Временные файлы
|
|
||||||
*.swp
|
|
||||||
*.swo
|
|
||||||
*~
|
|
||||||
.thumbs.db
|
|
||||||
desktop.ini
|
|
||||||
|
|
||||||
# Проектные специфичные
|
|
||||||
app/build/
|
|
||||||
*.log
|
|
||||||
app/jni/libpng
|
|
||||||
app/jni/SDL
|
|
||||||
app/jni/zlib
|
|
||||||
|
|
||||||
app/src/main/assets/resources
|
|
||||||
|
|
||||||
@ -1 +0,0 @@
|
|||||||
### Перед запуском в папке ```app/jni/```(рядом с src) нужно создать три папки с исходниками библиотек ```libpng```, ```SDL```, ```zlib```
|
|
||||||
@ -1,74 +0,0 @@
|
|||||||
def buildAsLibrary = project.hasProperty('BUILD_AS_LIBRARY');
|
|
||||||
def buildAsApplication = !buildAsLibrary
|
|
||||||
if (buildAsApplication) {
|
|
||||||
apply plugin: 'com.android.application'
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
apply plugin: 'com.android.library'
|
|
||||||
}
|
|
||||||
|
|
||||||
android {
|
|
||||||
if (buildAsApplication) {
|
|
||||||
namespace "org.libsdl.app"
|
|
||||||
}
|
|
||||||
compileSdkVersion 34
|
|
||||||
defaultConfig {
|
|
||||||
minSdkVersion 19
|
|
||||||
targetSdkVersion 34
|
|
||||||
versionCode 1
|
|
||||||
versionName "1.0"
|
|
||||||
externalNativeBuild {
|
|
||||||
/*ndkBuild {
|
|
||||||
arguments "APP_PLATFORM=android-19"
|
|
||||||
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
|
|
||||||
}*/
|
|
||||||
cmake {
|
|
||||||
arguments "-DANDROID_APP_PLATFORM=android-19", "-DANDROID_STL=c++_static"
|
|
||||||
cppFlags "-std=c++11 -frtti -fexceptions"
|
|
||||||
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buildTypes {
|
|
||||||
release {
|
|
||||||
minifyEnabled false
|
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
applicationVariants.all { variant ->
|
|
||||||
tasks["merge${variant.name.capitalize()}Assets"]
|
|
||||||
.dependsOn("externalNativeBuild${variant.name.capitalize()}")
|
|
||||||
}
|
|
||||||
if (!project.hasProperty('EXCLUDE_NATIVE_LIBS')) {
|
|
||||||
sourceSets.main {
|
|
||||||
jniLibs.srcDir 'libs'
|
|
||||||
}
|
|
||||||
externalNativeBuild {
|
|
||||||
/*ndkBuild {
|
|
||||||
path 'jni/Android.mk'
|
|
||||||
}*/
|
|
||||||
cmake {
|
|
||||||
path 'jni/CMakeLists.txt'
|
|
||||||
version '3.22.1'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
lint {
|
|
||||||
abortOnError false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buildAsLibrary) {
|
|
||||||
libraryVariants.all { variant ->
|
|
||||||
variant.outputs.all { output ->
|
|
||||||
if (output.outputFileName != null && output.outputFileName.endsWith(".aar")) {
|
|
||||||
output.outputFileName = "org.libsdl.app.aar"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
|
||||||
}
|
|
||||||
@ -1,68 +0,0 @@
|
|||||||
cmake_minimum_required(VERSION 3.6)
|
|
||||||
|
|
||||||
project(GAME)
|
|
||||||
|
|
||||||
if(POLICY CMP0079)
|
|
||||||
cmake_policy(SET CMP0079 NEW)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# ==============================================================================
|
|
||||||
# 1. АВТО-ЗАГРУЗКА ИСХОДНИКОВ
|
|
||||||
# ==============================================================================
|
|
||||||
# Путь относительно текущего файла к папке с общими скриптами
|
|
||||||
include("${CMAKE_CURRENT_SOURCE_DIR}/../../../cmake/FetchDependencies.cmake")
|
|
||||||
|
|
||||||
# Теперь мы уверены, что папка thirdparty заполнена исходниками.
|
|
||||||
# Определяем базовый путь для удобства:
|
|
||||||
set(TP_ROOT "${THIRDPARTY_DIR}")
|
|
||||||
|
|
||||||
# ==============================================================================
|
|
||||||
# 2. НАСТРОЙКА ЗАВИСИМОСТЕЙ (СОБИРАЕМ ИЗ ИСХОДНИКОВ)
|
|
||||||
# ==============================================================================
|
|
||||||
|
|
||||||
# --- ZLIB ---
|
|
||||||
add_subdirectory("${TP_ROOT}/zlib-1.3.1" zlib-build)
|
|
||||||
|
|
||||||
# --- LIBPNG ---
|
|
||||||
set(PNG_STATIC ON CACHE BOOL "Build static library" FORCE)
|
|
||||||
set(PNG_SHARED OFF CACHE BOOL "Don't build shared library" FORCE)
|
|
||||||
set(PNG_TESTS OFF CACHE BOOL "Disable tests" FORCE)
|
|
||||||
set(PNG_TOOLS OFF CACHE BOOL "Disable tools" FORCE)
|
|
||||||
set(PNG_EXECUTABLES OFF CACHE BOOL "Disable executables" FORCE)
|
|
||||||
set(PNG_DEBUG OFF CACHE BOOL "Disable debug" FORCE)
|
|
||||||
set(SKIP_INSTALL_ALL ON CACHE BOOL "Skip installation" FORCE)
|
|
||||||
set(PNG_HARDWARE_OPTIMIZATIONS OFF CACHE BOOL "Disable hardware optimizations" FORCE)
|
|
||||||
set(PNG_ARM_NEON "off" CACHE STRING "Disable ARM NEON" FORCE)
|
|
||||||
|
|
||||||
add_subdirectory("${TP_ROOT}/libpng-1.6.51" libpng-build)
|
|
||||||
|
|
||||||
# --- SDL2 ---
|
|
||||||
# Android-версия SDL требует специфичных настроек, но add_subdirectory обычно подхватывает их сама
|
|
||||||
add_subdirectory("${TP_ROOT}/SDL-release-2.32.10" sdl-build)
|
|
||||||
|
|
||||||
# --- LIBZIP ---
|
|
||||||
# Отключаем поиск системных крипто-библиотек, так как в NDK их может не быть в стандартных путях
|
|
||||||
set(ENABLE_GNUTLS OFF CACHE BOOL "" FORCE)
|
|
||||||
set(ENABLE_OPENSSL OFF CACHE BOOL "" FORCE)
|
|
||||||
set(ENABLE_WINDOWS_CRYPTO OFF CACHE BOOL "" FORCE)
|
|
||||||
set(ENABLE_COMMONCRYPTO OFF CACHE BOOL "" FORCE)
|
|
||||||
set(ENABLE_MBEDTLS OFF CACHE BOOL "" FORCE)
|
|
||||||
|
|
||||||
add_subdirectory("${TP_ROOT}/libzip-1.11.4" libzip-build)
|
|
||||||
|
|
||||||
# --- EIGEN & BOOST (Header-only) ---
|
|
||||||
# Мы создаем интерфейсные таргеты здесь, чтобы они были доступны в подпапке src
|
|
||||||
if(NOT TARGET eigen_external_lib)
|
|
||||||
add_library(eigen_external_lib INTERFACE)
|
|
||||||
target_include_directories(eigen_external_lib INTERFACE "${TP_ROOT}/eigen-5.0.0")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(NOT TARGET boost_external_lib)
|
|
||||||
add_library(boost_external_lib INTERFACE)
|
|
||||||
target_include_directories(boost_external_lib INTERFACE "${TP_ROOT}/boost_1_90_0")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# ==============================================================================
|
|
||||||
# 3. ОСНОВНОЙ КОД
|
|
||||||
# ==============================================================================
|
|
||||||
add_subdirectory(src)
|
|
||||||
@ -1,92 +0,0 @@
|
|||||||
cmake_minimum_required(VERSION 3.6)
|
|
||||||
|
|
||||||
project(MY_APP CXX)
|
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
||||||
|
|
||||||
|
|
||||||
# Определение путей
|
|
||||||
set(SOURCE_RES_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../../resources")
|
|
||||||
set(TARGET_RES_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../src/main/assets/resources")
|
|
||||||
|
|
||||||
# Получаем список всех файлов в исходной директории для отслеживания зависимостей
|
|
||||||
file(GLOB_RECURSE RES_FILES RELATIVE "${SOURCE_RES_DIR}" "${SOURCE_RES_DIR}/*")
|
|
||||||
|
|
||||||
set(COPY_COMMANDS "")
|
|
||||||
|
|
||||||
foreach(RES_FILE ${RES_FILES})
|
|
||||||
set(SRC "${SOURCE_RES_DIR}/${RES_FILE}")
|
|
||||||
set(DST "${TARGET_RES_DIR}/${RES_FILE}")
|
|
||||||
|
|
||||||
# Команда копирования конкретного файла, если он изменился
|
|
||||||
add_custom_command(
|
|
||||||
OUTPUT "${DST}"
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${TARGET_RES_DIR}"
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${SRC}" "${DST}"
|
|
||||||
DEPENDS "${SRC}"
|
|
||||||
COMMENT "Syncing resource: ${RES_FILE}"
|
|
||||||
)
|
|
||||||
list(APPEND RES_OUTPUTS "${DST}")
|
|
||||||
endforeach()
|
|
||||||
|
|
||||||
# Создаем кастомную цель, которая будет запускать процесс копирования
|
|
||||||
add_custom_target(sync_resources ALL DEPENDS ${RES_OUTPUTS})
|
|
||||||
|
|
||||||
|
|
||||||
add_library(main SHARED
|
|
||||||
SDL_android_main.c
|
|
||||||
../../../../src/BoneAnimatedModel.cpp
|
|
||||||
../../../../src/Environment.cpp
|
|
||||||
../../../../src/Game.cpp
|
|
||||||
../../../../src/main.cpp
|
|
||||||
../../../../src/Projectile.cpp
|
|
||||||
../../../../src/SparkEmitter.cpp
|
|
||||||
../../../../src/TextModel.cpp
|
|
||||||
../../../../src/UiManager.cpp
|
|
||||||
../../../../src/utils/Perlin.cpp
|
|
||||||
../../../../src/utils/TaskManager.cpp
|
|
||||||
../../../../src/utils/Utils.cpp
|
|
||||||
../../../../src/planet/PlanetData.cpp
|
|
||||||
../../../../src/planet/PlanetObject.cpp
|
|
||||||
../../../../src/planet/StoneObject.cpp
|
|
||||||
../../../../src/render/FrameBuffer.cpp
|
|
||||||
../../../../src/render/Renderer.cpp
|
|
||||||
../../../../src/render/ShaderManager.cpp
|
|
||||||
../../../../src/render/TextureManager.cpp
|
|
||||||
../../../../src/render/OpenGlExtensions.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
# Подключаем заголовки
|
|
||||||
target_include_directories(main PRIVATE
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/../SDL/include
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/../zlib
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/../libpng
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/../../../../thirdparty/eigen-5.0.0
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/../../../../thirdparty/boost_1_90_0
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/../../../../src
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/../libzip
|
|
||||||
)
|
|
||||||
|
|
||||||
# ВАЖНО: Линкуемся с png_static (статика) или png_shared (динамика)
|
|
||||||
# Так как мы установили PNG_STATIC=ON и PNG_SHARED=OFF,
|
|
||||||
# должна создаться цель png_static
|
|
||||||
target_link_libraries(main
|
|
||||||
png_static # ← ЭТО ПРАВИЛЬНОЕ ИМЯ ЦЕЛИ!
|
|
||||||
z
|
|
||||||
SDL2
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
find_library(OPENGLES2_LIB GLESv2)
|
|
||||||
|
|
||||||
target_link_libraries(main
|
|
||||||
${OPENGLES2_LIB} # OpenGL ES 2.0/3.0
|
|
||||||
log
|
|
||||||
android
|
|
||||||
OpenSLES
|
|
||||||
dl
|
|
||||||
zip
|
|
||||||
)
|
|
||||||
|
|
||||||
add_dependencies(main sync_resources)
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
#include <jni.h>
|
|
||||||
#include <android/log.h>
|
|
||||||
|
|
||||||
extern int SDL_main(int argc, char *argv[]);
|
|
||||||
|
|
||||||
void Java_org_libsdl_app_SDLActivity_nativeInit(JNIEnv* env, jclass cls, jobject obj) {
|
|
||||||
/* Run the application code! */
|
|
||||||
SDL_main(0, NULL);
|
|
||||||
}
|
|
||||||
98
proj-android/app/proguard-rules.pro
vendored
98
proj-android/app/proguard-rules.pro
vendored
@ -1,98 +0,0 @@
|
|||||||
# Add project specific ProGuard rules here.
|
|
||||||
# By default, the flags in this file are appended to flags specified
|
|
||||||
# in [sdk]/tools/proguard/proguard-android.txt
|
|
||||||
# You can edit the include path and order by changing the proguardFiles
|
|
||||||
# directive in build.gradle.
|
|
||||||
#
|
|
||||||
# For more details, see
|
|
||||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
|
||||||
|
|
||||||
# Add any project specific keep options here:
|
|
||||||
|
|
||||||
# If your project uses WebView with JS, uncomment the following
|
|
||||||
# and specify the fully qualified class name to the JavaScript interface
|
|
||||||
# class:
|
|
||||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
|
||||||
# public *;
|
|
||||||
#}
|
|
||||||
|
|
||||||
-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLInputConnection {
|
|
||||||
void nativeCommitText(java.lang.String, int);
|
|
||||||
void nativeGenerateScancodeForUnichar(char);
|
|
||||||
}
|
|
||||||
|
|
||||||
-keep,includedescriptorclasses class org.libsdl.app.SDLActivity {
|
|
||||||
# for some reason these aren't compatible with allowoptimization modifier
|
|
||||||
boolean supportsRelativeMouse();
|
|
||||||
void setWindowStyle(boolean);
|
|
||||||
}
|
|
||||||
|
|
||||||
-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLActivity {
|
|
||||||
java.lang.String nativeGetHint(java.lang.String); # Java-side doesn't use this, so it gets minified, but C-side still tries to register it
|
|
||||||
boolean onNativeSoftReturnKey();
|
|
||||||
void onNativeKeyboardFocusLost();
|
|
||||||
boolean isScreenKeyboardShown();
|
|
||||||
android.util.DisplayMetrics getDisplayDPI();
|
|
||||||
java.lang.String clipboardGetText();
|
|
||||||
boolean clipboardHasText();
|
|
||||||
void clipboardSetText(java.lang.String);
|
|
||||||
int createCustomCursor(int[], int, int, int, int);
|
|
||||||
void destroyCustomCursor(int);
|
|
||||||
android.content.Context getContext();
|
|
||||||
boolean getManifestEnvironmentVariables();
|
|
||||||
android.view.Surface getNativeSurface();
|
|
||||||
void initTouch();
|
|
||||||
boolean isAndroidTV();
|
|
||||||
boolean isChromebook();
|
|
||||||
boolean isDeXMode();
|
|
||||||
boolean isTablet();
|
|
||||||
void manualBackButton();
|
|
||||||
int messageboxShowMessageBox(int, java.lang.String, java.lang.String, int[], int[], java.lang.String[], int[]);
|
|
||||||
void minimizeWindow();
|
|
||||||
int openURL(java.lang.String);
|
|
||||||
void requestPermission(java.lang.String, int);
|
|
||||||
int showToast(java.lang.String, int, int, int, int);
|
|
||||||
boolean sendMessage(int, int);
|
|
||||||
boolean setActivityTitle(java.lang.String);
|
|
||||||
boolean setCustomCursor(int);
|
|
||||||
void setOrientation(int, int, boolean, java.lang.String);
|
|
||||||
boolean setRelativeMouseEnabled(boolean);
|
|
||||||
boolean setSystemCursor(int);
|
|
||||||
boolean shouldMinimizeOnFocusLoss();
|
|
||||||
boolean showTextInput(int, int, int, int);
|
|
||||||
}
|
|
||||||
|
|
||||||
-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.HIDDeviceManager {
|
|
||||||
boolean initialize(boolean, boolean);
|
|
||||||
boolean openDevice(int);
|
|
||||||
int sendOutputReport(int, byte[]);
|
|
||||||
int sendFeatureReport(int, byte[]);
|
|
||||||
boolean getFeatureReport(int, byte[]);
|
|
||||||
void closeDevice(int);
|
|
||||||
}
|
|
||||||
|
|
||||||
-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLAudioManager {
|
|
||||||
int[] getAudioOutputDevices();
|
|
||||||
int[] getAudioInputDevices();
|
|
||||||
int[] audioOpen(int, int, int, int, int);
|
|
||||||
void audioWriteFloatBuffer(float[]);
|
|
||||||
void audioWriteShortBuffer(short[]);
|
|
||||||
void audioWriteByteBuffer(byte[]);
|
|
||||||
void audioClose();
|
|
||||||
int[] captureOpen(int, int, int, int, int);
|
|
||||||
int captureReadFloatBuffer(float[], boolean);
|
|
||||||
int captureReadShortBuffer(short[], boolean);
|
|
||||||
int captureReadByteBuffer(byte[], boolean);
|
|
||||||
void captureClose();
|
|
||||||
void audioSetThreadPriority(boolean, int);
|
|
||||||
native int nativeSetupJNI();
|
|
||||||
native void removeAudioDevice(boolean, int);
|
|
||||||
native void addAudioDevice(boolean, int);
|
|
||||||
}
|
|
||||||
|
|
||||||
-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLControllerManager {
|
|
||||||
void pollInputDevices();
|
|
||||||
void pollHapticDevices();
|
|
||||||
void hapticRun(int, float, int);
|
|
||||||
void hapticStop(int);
|
|
||||||
}
|
|
||||||
@ -1,99 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- Replace com.test.game with the identifier of your game below, e.g.
|
|
||||||
com.gamemaker.game
|
|
||||||
-->
|
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:versionCode="1"
|
|
||||||
android:versionName="1.0"
|
|
||||||
android:installLocation="auto">
|
|
||||||
|
|
||||||
<!-- OpenGL ES 2.0 -->
|
|
||||||
<uses-feature android:glEsVersion="0x00020000" />
|
|
||||||
|
|
||||||
<!-- Touchscreen support -->
|
|
||||||
<uses-feature
|
|
||||||
android:name="android.hardware.touchscreen"
|
|
||||||
android:required="false" />
|
|
||||||
|
|
||||||
<!-- Game controller support -->
|
|
||||||
<uses-feature
|
|
||||||
android:name="android.hardware.bluetooth"
|
|
||||||
android:required="false" />
|
|
||||||
<uses-feature
|
|
||||||
android:name="android.hardware.gamepad"
|
|
||||||
android:required="false" />
|
|
||||||
<uses-feature
|
|
||||||
android:name="android.hardware.usb.host"
|
|
||||||
android:required="false" />
|
|
||||||
|
|
||||||
<!-- External mouse input events -->
|
|
||||||
<uses-feature
|
|
||||||
android:name="android.hardware.type.pc"
|
|
||||||
android:required="false" />
|
|
||||||
|
|
||||||
<!-- Audio recording support -->
|
|
||||||
<!-- if you want to capture audio, uncomment this. -->
|
|
||||||
<!-- <uses-feature
|
|
||||||
android:name="android.hardware.microphone"
|
|
||||||
android:required="false" /> -->
|
|
||||||
|
|
||||||
<!-- Allow downloading to the external storage on Android 5.1 and older -->
|
|
||||||
<!-- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="22" /> -->
|
|
||||||
|
|
||||||
<!-- Allow access to Bluetooth devices -->
|
|
||||||
<!-- Currently this is just for Steam Controller support and requires setting SDL_HINT_JOYSTICK_HIDAPI_STEAM -->
|
|
||||||
<!-- <uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" /> -->
|
|
||||||
<!-- <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> -->
|
|
||||||
|
|
||||||
<!-- Allow access to the vibrator -->
|
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
|
||||||
|
|
||||||
<!-- if you want to capture audio, uncomment this. -->
|
|
||||||
<!-- <uses-permission android:name="android.permission.RECORD_AUDIO" /> -->
|
|
||||||
|
|
||||||
<!-- Create a Java class extending SDLActivity and place it in a
|
|
||||||
directory under app/src/main/java matching the package, e.g. app/src/main/java/com/gamemaker/game/MyGame.java
|
|
||||||
|
|
||||||
then replace "SDLActivity" with the name of your class (e.g. "MyGame")
|
|
||||||
in the XML below.
|
|
||||||
|
|
||||||
An example Java class can be found in README-android.md
|
|
||||||
-->
|
|
||||||
<application android:label="@string/app_name"
|
|
||||||
android:icon="@mipmap/ic_launcher"
|
|
||||||
android:allowBackup="true"
|
|
||||||
android:theme="@style/AppTheme"
|
|
||||||
android:hardwareAccelerated="true" >
|
|
||||||
|
|
||||||
<!-- Example of setting SDL hints from AndroidManifest.xml:
|
|
||||||
<meta-data android:name="SDL_ENV.SDL_ACCELEROMETER_AS_JOYSTICK" android:value="0"/>
|
|
||||||
-->
|
|
||||||
|
|
||||||
<activity android:name="SDLActivity"
|
|
||||||
android:label="@string/app_name"
|
|
||||||
android:alwaysRetainTaskState="true"
|
|
||||||
android:launchMode="singleInstance"
|
|
||||||
android:configChanges="layoutDirection|locale|orientation|uiMode|screenLayout|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation"
|
|
||||||
android:preferMinimalPostProcessing="true"
|
|
||||||
android:exported="true"
|
|
||||||
>
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.MAIN" />
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
|
||||||
</intent-filter>
|
|
||||||
<!-- Let Android know that we can handle some USB devices and should receive this event -->
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
|
|
||||||
</intent-filter>
|
|
||||||
<!-- Drop file event -->
|
|
||||||
<!--
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<data android:mimeType="*/*" />
|
|
||||||
</intent-filter>
|
|
||||||
-->
|
|
||||||
</activity>
|
|
||||||
</application>
|
|
||||||
|
|
||||||
</manifest>
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
package org.libsdl.app;
|
|
||||||
|
|
||||||
import android.hardware.usb.UsbDevice;
|
|
||||||
|
|
||||||
interface HIDDevice
|
|
||||||
{
|
|
||||||
public int getId();
|
|
||||||
public int getVendorId();
|
|
||||||
public int getProductId();
|
|
||||||
public String getSerialNumber();
|
|
||||||
public int getVersion();
|
|
||||||
public String getManufacturerName();
|
|
||||||
public String getProductName();
|
|
||||||
public UsbDevice getDevice();
|
|
||||||
public boolean open();
|
|
||||||
public int sendFeatureReport(byte[] report);
|
|
||||||
public int sendOutputReport(byte[] report);
|
|
||||||
public boolean getFeatureReport(byte[] report);
|
|
||||||
public void setFrozen(boolean frozen);
|
|
||||||
public void close();
|
|
||||||
public void shutdown();
|
|
||||||
}
|
|
||||||
@ -1,650 +0,0 @@
|
|||||||
package org.libsdl.app;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.bluetooth.BluetoothDevice;
|
|
||||||
import android.bluetooth.BluetoothGatt;
|
|
||||||
import android.bluetooth.BluetoothGattCallback;
|
|
||||||
import android.bluetooth.BluetoothGattCharacteristic;
|
|
||||||
import android.bluetooth.BluetoothGattDescriptor;
|
|
||||||
import android.bluetooth.BluetoothManager;
|
|
||||||
import android.bluetooth.BluetoothProfile;
|
|
||||||
import android.bluetooth.BluetoothGattService;
|
|
||||||
import android.hardware.usb.UsbDevice;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.Looper;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.os.*;
|
|
||||||
|
|
||||||
//import com.android.internal.util.HexDump;
|
|
||||||
|
|
||||||
import java.lang.Runnable;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDevice {
|
|
||||||
|
|
||||||
private static final String TAG = "hidapi";
|
|
||||||
private HIDDeviceManager mManager;
|
|
||||||
private BluetoothDevice mDevice;
|
|
||||||
private int mDeviceId;
|
|
||||||
private BluetoothGatt mGatt;
|
|
||||||
private boolean mIsRegistered = false;
|
|
||||||
private boolean mIsConnected = false;
|
|
||||||
private boolean mIsChromebook = false;
|
|
||||||
private boolean mIsReconnecting = false;
|
|
||||||
private boolean mFrozen = false;
|
|
||||||
private LinkedList<GattOperation> mOperations;
|
|
||||||
GattOperation mCurrentOperation = null;
|
|
||||||
private Handler mHandler;
|
|
||||||
|
|
||||||
private static final int TRANSPORT_AUTO = 0;
|
|
||||||
private static final int TRANSPORT_BREDR = 1;
|
|
||||||
private static final int TRANSPORT_LE = 2;
|
|
||||||
|
|
||||||
private static final int CHROMEBOOK_CONNECTION_CHECK_INTERVAL = 10000;
|
|
||||||
|
|
||||||
static public final UUID steamControllerService = UUID.fromString("100F6C32-1735-4313-B402-38567131E5F3");
|
|
||||||
static public final UUID inputCharacteristic = UUID.fromString("100F6C33-1735-4313-B402-38567131E5F3");
|
|
||||||
static public final UUID reportCharacteristic = UUID.fromString("100F6C34-1735-4313-B402-38567131E5F3");
|
|
||||||
static private final byte[] enterValveMode = new byte[] { (byte)0xC0, (byte)0x87, 0x03, 0x08, 0x07, 0x00 };
|
|
||||||
|
|
||||||
static class GattOperation {
|
|
||||||
private enum Operation {
|
|
||||||
CHR_READ,
|
|
||||||
CHR_WRITE,
|
|
||||||
ENABLE_NOTIFICATION
|
|
||||||
}
|
|
||||||
|
|
||||||
Operation mOp;
|
|
||||||
UUID mUuid;
|
|
||||||
byte[] mValue;
|
|
||||||
BluetoothGatt mGatt;
|
|
||||||
boolean mResult = true;
|
|
||||||
|
|
||||||
private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid) {
|
|
||||||
mGatt = gatt;
|
|
||||||
mOp = operation;
|
|
||||||
mUuid = uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid, byte[] value) {
|
|
||||||
mGatt = gatt;
|
|
||||||
mOp = operation;
|
|
||||||
mUuid = uuid;
|
|
||||||
mValue = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void run() {
|
|
||||||
// This is executed in main thread
|
|
||||||
BluetoothGattCharacteristic chr;
|
|
||||||
|
|
||||||
switch (mOp) {
|
|
||||||
case CHR_READ:
|
|
||||||
chr = getCharacteristic(mUuid);
|
|
||||||
//Log.v(TAG, "Reading characteristic " + chr.getUuid());
|
|
||||||
if (!mGatt.readCharacteristic(chr)) {
|
|
||||||
Log.e(TAG, "Unable to read characteristic " + mUuid.toString());
|
|
||||||
mResult = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
mResult = true;
|
|
||||||
break;
|
|
||||||
case CHR_WRITE:
|
|
||||||
chr = getCharacteristic(mUuid);
|
|
||||||
//Log.v(TAG, "Writing characteristic " + chr.getUuid() + " value=" + HexDump.toHexString(value));
|
|
||||||
chr.setValue(mValue);
|
|
||||||
if (!mGatt.writeCharacteristic(chr)) {
|
|
||||||
Log.e(TAG, "Unable to write characteristic " + mUuid.toString());
|
|
||||||
mResult = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
mResult = true;
|
|
||||||
break;
|
|
||||||
case ENABLE_NOTIFICATION:
|
|
||||||
chr = getCharacteristic(mUuid);
|
|
||||||
//Log.v(TAG, "Writing descriptor of " + chr.getUuid());
|
|
||||||
if (chr != null) {
|
|
||||||
BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
|
|
||||||
if (cccd != null) {
|
|
||||||
int properties = chr.getProperties();
|
|
||||||
byte[] value;
|
|
||||||
if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == BluetoothGattCharacteristic.PROPERTY_NOTIFY) {
|
|
||||||
value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE;
|
|
||||||
} else if ((properties & BluetoothGattCharacteristic.PROPERTY_INDICATE) == BluetoothGattCharacteristic.PROPERTY_INDICATE) {
|
|
||||||
value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE;
|
|
||||||
} else {
|
|
||||||
Log.e(TAG, "Unable to start notifications on input characteristic");
|
|
||||||
mResult = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mGatt.setCharacteristicNotification(chr, true);
|
|
||||||
cccd.setValue(value);
|
|
||||||
if (!mGatt.writeDescriptor(cccd)) {
|
|
||||||
Log.e(TAG, "Unable to write descriptor " + mUuid.toString());
|
|
||||||
mResult = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mResult = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean finish() {
|
|
||||||
return mResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
private BluetoothGattCharacteristic getCharacteristic(UUID uuid) {
|
|
||||||
BluetoothGattService valveService = mGatt.getService(steamControllerService);
|
|
||||||
if (valveService == null)
|
|
||||||
return null;
|
|
||||||
return valveService.getCharacteristic(uuid);
|
|
||||||
}
|
|
||||||
|
|
||||||
static public GattOperation readCharacteristic(BluetoothGatt gatt, UUID uuid) {
|
|
||||||
return new GattOperation(gatt, Operation.CHR_READ, uuid);
|
|
||||||
}
|
|
||||||
|
|
||||||
static public GattOperation writeCharacteristic(BluetoothGatt gatt, UUID uuid, byte[] value) {
|
|
||||||
return new GattOperation(gatt, Operation.CHR_WRITE, uuid, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
static public GattOperation enableNotification(BluetoothGatt gatt, UUID uuid) {
|
|
||||||
return new GattOperation(gatt, Operation.ENABLE_NOTIFICATION, uuid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public HIDDeviceBLESteamController(HIDDeviceManager manager, BluetoothDevice device) {
|
|
||||||
mManager = manager;
|
|
||||||
mDevice = device;
|
|
||||||
mDeviceId = mManager.getDeviceIDForIdentifier(getIdentifier());
|
|
||||||
mIsRegistered = false;
|
|
||||||
mIsChromebook = mManager.getContext().getPackageManager().hasSystemFeature("org.chromium.arc.device_management");
|
|
||||||
mOperations = new LinkedList<GattOperation>();
|
|
||||||
mHandler = new Handler(Looper.getMainLooper());
|
|
||||||
|
|
||||||
mGatt = connectGatt();
|
|
||||||
// final HIDDeviceBLESteamController finalThis = this;
|
|
||||||
// mHandler.postDelayed(new Runnable() {
|
|
||||||
// @Override
|
|
||||||
// public void run() {
|
|
||||||
// finalThis.checkConnectionForChromebookIssue();
|
|
||||||
// }
|
|
||||||
// }, CHROMEBOOK_CONNECTION_CHECK_INTERVAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getIdentifier() {
|
|
||||||
return String.format("SteamController.%s", mDevice.getAddress());
|
|
||||||
}
|
|
||||||
|
|
||||||
public BluetoothGatt getGatt() {
|
|
||||||
return mGatt;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Because on Chromebooks we show up as a dual-mode device, it will attempt to connect TRANSPORT_AUTO, which will use TRANSPORT_BREDR instead
|
|
||||||
// of TRANSPORT_LE. Let's force ourselves to connect low energy.
|
|
||||||
private BluetoothGatt connectGatt(boolean managed) {
|
|
||||||
if (Build.VERSION.SDK_INT >= 23 /* Android 6.0 (M) */) {
|
|
||||||
try {
|
|
||||||
return mDevice.connectGatt(mManager.getContext(), managed, this, TRANSPORT_LE);
|
|
||||||
} catch (Exception e) {
|
|
||||||
return mDevice.connectGatt(mManager.getContext(), managed, this);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return mDevice.connectGatt(mManager.getContext(), managed, this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private BluetoothGatt connectGatt() {
|
|
||||||
return connectGatt(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected int getConnectionState() {
|
|
||||||
|
|
||||||
Context context = mManager.getContext();
|
|
||||||
if (context == null) {
|
|
||||||
// We are lacking any context to get our Bluetooth information. We'll just assume disconnected.
|
|
||||||
return BluetoothProfile.STATE_DISCONNECTED;
|
|
||||||
}
|
|
||||||
|
|
||||||
BluetoothManager btManager = (BluetoothManager)context.getSystemService(Context.BLUETOOTH_SERVICE);
|
|
||||||
if (btManager == null) {
|
|
||||||
// This device doesn't support Bluetooth. We should never be here, because how did
|
|
||||||
// we instantiate a device to start with?
|
|
||||||
return BluetoothProfile.STATE_DISCONNECTED;
|
|
||||||
}
|
|
||||||
|
|
||||||
return btManager.getConnectionState(mDevice, BluetoothProfile.GATT);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void reconnect() {
|
|
||||||
|
|
||||||
if (getConnectionState() != BluetoothProfile.STATE_CONNECTED) {
|
|
||||||
mGatt.disconnect();
|
|
||||||
mGatt = connectGatt();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void checkConnectionForChromebookIssue() {
|
|
||||||
if (!mIsChromebook) {
|
|
||||||
// We only do this on Chromebooks, because otherwise it's really annoying to just attempt
|
|
||||||
// over and over.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int connectionState = getConnectionState();
|
|
||||||
|
|
||||||
switch (connectionState) {
|
|
||||||
case BluetoothProfile.STATE_CONNECTED:
|
|
||||||
if (!mIsConnected) {
|
|
||||||
// We are in the Bad Chromebook Place. We can force a disconnect
|
|
||||||
// to try to recover.
|
|
||||||
Log.v(TAG, "Chromebook: We are in a very bad state; the controller shows as connected in the underlying Bluetooth layer, but we never received a callback. Forcing a reconnect.");
|
|
||||||
mIsReconnecting = true;
|
|
||||||
mGatt.disconnect();
|
|
||||||
mGatt = connectGatt(false);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else if (!isRegistered()) {
|
|
||||||
if (mGatt.getServices().size() > 0) {
|
|
||||||
Log.v(TAG, "Chromebook: We are connected to a controller, but never got our registration. Trying to recover.");
|
|
||||||
probeService(this);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Log.v(TAG, "Chromebook: We are connected to a controller, but never discovered services. Trying to recover.");
|
|
||||||
mIsReconnecting = true;
|
|
||||||
mGatt.disconnect();
|
|
||||||
mGatt = connectGatt(false);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Log.v(TAG, "Chromebook: We are connected, and registered. Everything's good!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case BluetoothProfile.STATE_DISCONNECTED:
|
|
||||||
Log.v(TAG, "Chromebook: We have either been disconnected, or the Chromebook BtGatt.ContextMap bug has bitten us. Attempting a disconnect/reconnect, but we may not be able to recover.");
|
|
||||||
|
|
||||||
mIsReconnecting = true;
|
|
||||||
mGatt.disconnect();
|
|
||||||
mGatt = connectGatt(false);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case BluetoothProfile.STATE_CONNECTING:
|
|
||||||
Log.v(TAG, "Chromebook: We're still trying to connect. Waiting a bit longer.");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
final HIDDeviceBLESteamController finalThis = this;
|
|
||||||
mHandler.postDelayed(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
finalThis.checkConnectionForChromebookIssue();
|
|
||||||
}
|
|
||||||
}, CHROMEBOOK_CONNECTION_CHECK_INTERVAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isRegistered() {
|
|
||||||
return mIsRegistered;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setRegistered() {
|
|
||||||
mIsRegistered = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean probeService(HIDDeviceBLESteamController controller) {
|
|
||||||
|
|
||||||
if (isRegistered()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!mIsConnected) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.v(TAG, "probeService controller=" + controller);
|
|
||||||
|
|
||||||
for (BluetoothGattService service : mGatt.getServices()) {
|
|
||||||
if (service.getUuid().equals(steamControllerService)) {
|
|
||||||
Log.v(TAG, "Found Valve steam controller service " + service.getUuid());
|
|
||||||
|
|
||||||
for (BluetoothGattCharacteristic chr : service.getCharacteristics()) {
|
|
||||||
if (chr.getUuid().equals(inputCharacteristic)) {
|
|
||||||
Log.v(TAG, "Found input characteristic");
|
|
||||||
// Start notifications
|
|
||||||
BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
|
|
||||||
if (cccd != null) {
|
|
||||||
enableNotification(chr.getUuid());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((mGatt.getServices().size() == 0) && mIsChromebook && !mIsReconnecting) {
|
|
||||||
Log.e(TAG, "Chromebook: Discovered services were empty; this almost certainly means the BtGatt.ContextMap bug has bitten us.");
|
|
||||||
mIsConnected = false;
|
|
||||||
mIsReconnecting = true;
|
|
||||||
mGatt.disconnect();
|
|
||||||
mGatt = connectGatt(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
private void finishCurrentGattOperation() {
|
|
||||||
GattOperation op = null;
|
|
||||||
synchronized (mOperations) {
|
|
||||||
if (mCurrentOperation != null) {
|
|
||||||
op = mCurrentOperation;
|
|
||||||
mCurrentOperation = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (op != null) {
|
|
||||||
boolean result = op.finish(); // TODO: Maybe in main thread as well?
|
|
||||||
|
|
||||||
// Our operation failed, let's add it back to the beginning of our queue.
|
|
||||||
if (!result) {
|
|
||||||
mOperations.addFirst(op);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
executeNextGattOperation();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void executeNextGattOperation() {
|
|
||||||
synchronized (mOperations) {
|
|
||||||
if (mCurrentOperation != null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (mOperations.isEmpty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
mCurrentOperation = mOperations.removeFirst();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run in main thread
|
|
||||||
mHandler.post(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
synchronized (mOperations) {
|
|
||||||
if (mCurrentOperation == null) {
|
|
||||||
Log.e(TAG, "Current operation null in executor?");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mCurrentOperation.run();
|
|
||||||
// now wait for the GATT callback and when it comes, finish this operation
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void queueGattOperation(GattOperation op) {
|
|
||||||
synchronized (mOperations) {
|
|
||||||
mOperations.add(op);
|
|
||||||
}
|
|
||||||
executeNextGattOperation();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void enableNotification(UUID chrUuid) {
|
|
||||||
GattOperation op = HIDDeviceBLESteamController.GattOperation.enableNotification(mGatt, chrUuid);
|
|
||||||
queueGattOperation(op);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void writeCharacteristic(UUID uuid, byte[] value) {
|
|
||||||
GattOperation op = HIDDeviceBLESteamController.GattOperation.writeCharacteristic(mGatt, uuid, value);
|
|
||||||
queueGattOperation(op);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void readCharacteristic(UUID uuid) {
|
|
||||||
GattOperation op = HIDDeviceBLESteamController.GattOperation.readCharacteristic(mGatt, uuid);
|
|
||||||
queueGattOperation(op);
|
|
||||||
}
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
////////////// BluetoothGattCallback overridden methods
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
public void onConnectionStateChange(BluetoothGatt g, int status, int newState) {
|
|
||||||
//Log.v(TAG, "onConnectionStateChange status=" + status + " newState=" + newState);
|
|
||||||
mIsReconnecting = false;
|
|
||||||
if (newState == 2) {
|
|
||||||
mIsConnected = true;
|
|
||||||
// Run directly, without GattOperation
|
|
||||||
if (!isRegistered()) {
|
|
||||||
mHandler.post(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
mGatt.discoverServices();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (newState == 0) {
|
|
||||||
mIsConnected = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disconnection is handled in SteamLink using the ACTION_ACL_DISCONNECTED Intent.
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
|
|
||||||
//Log.v(TAG, "onServicesDiscovered status=" + status);
|
|
||||||
if (status == 0) {
|
|
||||||
if (gatt.getServices().size() == 0) {
|
|
||||||
Log.v(TAG, "onServicesDiscovered returned zero services; something has gone horribly wrong down in Android's Bluetooth stack.");
|
|
||||||
mIsReconnecting = true;
|
|
||||||
mIsConnected = false;
|
|
||||||
gatt.disconnect();
|
|
||||||
mGatt = connectGatt(false);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
probeService(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
|
|
||||||
//Log.v(TAG, "onCharacteristicRead status=" + status + " uuid=" + characteristic.getUuid());
|
|
||||||
|
|
||||||
if (characteristic.getUuid().equals(reportCharacteristic) && !mFrozen) {
|
|
||||||
mManager.HIDDeviceFeatureReport(getId(), characteristic.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
finishCurrentGattOperation();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
|
|
||||||
//Log.v(TAG, "onCharacteristicWrite status=" + status + " uuid=" + characteristic.getUuid());
|
|
||||||
|
|
||||||
if (characteristic.getUuid().equals(reportCharacteristic)) {
|
|
||||||
// Only register controller with the native side once it has been fully configured
|
|
||||||
if (!isRegistered()) {
|
|
||||||
Log.v(TAG, "Registering Steam Controller with ID: " + getId());
|
|
||||||
mManager.HIDDeviceConnected(getId(), getIdentifier(), getVendorId(), getProductId(), getSerialNumber(), getVersion(), getManufacturerName(), getProductName(), 0, 0, 0, 0);
|
|
||||||
setRegistered();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
finishCurrentGattOperation();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
|
|
||||||
// Enable this for verbose logging of controller input reports
|
|
||||||
//Log.v(TAG, "onCharacteristicChanged uuid=" + characteristic.getUuid() + " data=" + HexDump.dumpHexString(characteristic.getValue()));
|
|
||||||
|
|
||||||
if (characteristic.getUuid().equals(inputCharacteristic) && !mFrozen) {
|
|
||||||
mManager.HIDDeviceInputReport(getId(), characteristic.getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
|
|
||||||
//Log.v(TAG, "onDescriptorRead status=" + status);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
|
|
||||||
BluetoothGattCharacteristic chr = descriptor.getCharacteristic();
|
|
||||||
//Log.v(TAG, "onDescriptorWrite status=" + status + " uuid=" + chr.getUuid() + " descriptor=" + descriptor.getUuid());
|
|
||||||
|
|
||||||
if (chr.getUuid().equals(inputCharacteristic)) {
|
|
||||||
boolean hasWrittenInputDescriptor = true;
|
|
||||||
BluetoothGattCharacteristic reportChr = chr.getService().getCharacteristic(reportCharacteristic);
|
|
||||||
if (reportChr != null) {
|
|
||||||
Log.v(TAG, "Writing report characteristic to enter valve mode");
|
|
||||||
reportChr.setValue(enterValveMode);
|
|
||||||
gatt.writeCharacteristic(reportChr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
finishCurrentGattOperation();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
|
|
||||||
//Log.v(TAG, "onReliableWriteCompleted status=" + status);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
|
|
||||||
//Log.v(TAG, "onReadRemoteRssi status=" + status);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
|
|
||||||
//Log.v(TAG, "onMtuChanged status=" + status);
|
|
||||||
}
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
//////// Public API
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getId() {
|
|
||||||
return mDeviceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getVendorId() {
|
|
||||||
// Valve Corporation
|
|
||||||
final int VALVE_USB_VID = 0x28DE;
|
|
||||||
return VALVE_USB_VID;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getProductId() {
|
|
||||||
// We don't have an easy way to query from the Bluetooth device, but we know what it is
|
|
||||||
final int D0G_BLE2_PID = 0x1106;
|
|
||||||
return D0G_BLE2_PID;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getSerialNumber() {
|
|
||||||
// This will be read later via feature report by Steam
|
|
||||||
return "12345";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getVersion() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getManufacturerName() {
|
|
||||||
return "Valve Corporation";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getProductName() {
|
|
||||||
return "Steam Controller";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public UsbDevice getDevice() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean open() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int sendFeatureReport(byte[] report) {
|
|
||||||
if (!isRegistered()) {
|
|
||||||
Log.e(TAG, "Attempted sendFeatureReport before Steam Controller is registered!");
|
|
||||||
if (mIsConnected) {
|
|
||||||
probeService(this);
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We need to skip the first byte, as that doesn't go over the air
|
|
||||||
byte[] actual_report = Arrays.copyOfRange(report, 1, report.length - 1);
|
|
||||||
//Log.v(TAG, "sendFeatureReport " + HexDump.dumpHexString(actual_report));
|
|
||||||
writeCharacteristic(reportCharacteristic, actual_report);
|
|
||||||
return report.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int sendOutputReport(byte[] report) {
|
|
||||||
if (!isRegistered()) {
|
|
||||||
Log.e(TAG, "Attempted sendOutputReport before Steam Controller is registered!");
|
|
||||||
if (mIsConnected) {
|
|
||||||
probeService(this);
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Log.v(TAG, "sendFeatureReport " + HexDump.dumpHexString(report));
|
|
||||||
writeCharacteristic(reportCharacteristic, report);
|
|
||||||
return report.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean getFeatureReport(byte[] report) {
|
|
||||||
if (!isRegistered()) {
|
|
||||||
Log.e(TAG, "Attempted getFeatureReport before Steam Controller is registered!");
|
|
||||||
if (mIsConnected) {
|
|
||||||
probeService(this);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Log.v(TAG, "getFeatureReport");
|
|
||||||
readCharacteristic(reportCharacteristic);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setFrozen(boolean frozen) {
|
|
||||||
mFrozen = frozen;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void shutdown() {
|
|
||||||
close();
|
|
||||||
|
|
||||||
BluetoothGatt g = mGatt;
|
|
||||||
if (g != null) {
|
|
||||||
g.disconnect();
|
|
||||||
g.close();
|
|
||||||
mGatt = null;
|
|
||||||
}
|
|
||||||
mManager = null;
|
|
||||||
mIsRegistered = false;
|
|
||||||
mIsConnected = false;
|
|
||||||
mOperations.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,698 +0,0 @@
|
|||||||
package org.libsdl.app;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.AlertDialog;
|
|
||||||
import android.app.PendingIntent;
|
|
||||||
import android.bluetooth.BluetoothAdapter;
|
|
||||||
import android.bluetooth.BluetoothDevice;
|
|
||||||
import android.bluetooth.BluetoothManager;
|
|
||||||
import android.bluetooth.BluetoothProfile;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.content.BroadcastReceiver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.IntentFilter;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.hardware.usb.*;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.Looper;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class HIDDeviceManager {
|
|
||||||
private static final String TAG = "hidapi";
|
|
||||||
private static final String ACTION_USB_PERMISSION = "org.libsdl.app.USB_PERMISSION";
|
|
||||||
|
|
||||||
private static HIDDeviceManager sManager;
|
|
||||||
private static int sManagerRefCount = 0;
|
|
||||||
|
|
||||||
public static HIDDeviceManager acquire(Context context) {
|
|
||||||
if (sManagerRefCount == 0) {
|
|
||||||
sManager = new HIDDeviceManager(context);
|
|
||||||
}
|
|
||||||
++sManagerRefCount;
|
|
||||||
return sManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void release(HIDDeviceManager manager) {
|
|
||||||
if (manager == sManager) {
|
|
||||||
--sManagerRefCount;
|
|
||||||
if (sManagerRefCount == 0) {
|
|
||||||
sManager.close();
|
|
||||||
sManager = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Context mContext;
|
|
||||||
private HashMap<Integer, HIDDevice> mDevicesById = new HashMap<Integer, HIDDevice>();
|
|
||||||
private HashMap<BluetoothDevice, HIDDeviceBLESteamController> mBluetoothDevices = new HashMap<BluetoothDevice, HIDDeviceBLESteamController>();
|
|
||||||
private int mNextDeviceId = 0;
|
|
||||||
private SharedPreferences mSharedPreferences = null;
|
|
||||||
private boolean mIsChromebook = false;
|
|
||||||
private UsbManager mUsbManager;
|
|
||||||
private Handler mHandler;
|
|
||||||
private BluetoothManager mBluetoothManager;
|
|
||||||
private List<BluetoothDevice> mLastBluetoothDevices;
|
|
||||||
|
|
||||||
private final BroadcastReceiver mUsbBroadcast = new BroadcastReceiver() {
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context context, Intent intent) {
|
|
||||||
String action = intent.getAction();
|
|
||||||
if (action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
|
|
||||||
UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
|
|
||||||
handleUsbDeviceAttached(usbDevice);
|
|
||||||
} else if (action.equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) {
|
|
||||||
UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
|
|
||||||
handleUsbDeviceDetached(usbDevice);
|
|
||||||
} else if (action.equals(HIDDeviceManager.ACTION_USB_PERMISSION)) {
|
|
||||||
UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
|
|
||||||
handleUsbDevicePermission(usbDevice, intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final BroadcastReceiver mBluetoothBroadcast = new BroadcastReceiver() {
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context context, Intent intent) {
|
|
||||||
String action = intent.getAction();
|
|
||||||
// Bluetooth device was connected. If it was a Steam Controller, handle it
|
|
||||||
if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) {
|
|
||||||
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
|
|
||||||
Log.d(TAG, "Bluetooth device connected: " + device);
|
|
||||||
|
|
||||||
if (isSteamController(device)) {
|
|
||||||
connectBluetoothDevice(device);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bluetooth device was disconnected, remove from controller manager (if any)
|
|
||||||
if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
|
|
||||||
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
|
|
||||||
Log.d(TAG, "Bluetooth device disconnected: " + device);
|
|
||||||
|
|
||||||
disconnectBluetoothDevice(device);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private HIDDeviceManager(final Context context) {
|
|
||||||
mContext = context;
|
|
||||||
|
|
||||||
HIDDeviceRegisterCallback();
|
|
||||||
|
|
||||||
mSharedPreferences = mContext.getSharedPreferences("hidapi", Context.MODE_PRIVATE);
|
|
||||||
mIsChromebook = mContext.getPackageManager().hasSystemFeature("org.chromium.arc.device_management");
|
|
||||||
|
|
||||||
// if (shouldClear) {
|
|
||||||
// SharedPreferences.Editor spedit = mSharedPreferences.edit();
|
|
||||||
// spedit.clear();
|
|
||||||
// spedit.commit();
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
{
|
|
||||||
mNextDeviceId = mSharedPreferences.getInt("next_device_id", 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Context getContext() {
|
|
||||||
return mContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getDeviceIDForIdentifier(String identifier) {
|
|
||||||
SharedPreferences.Editor spedit = mSharedPreferences.edit();
|
|
||||||
|
|
||||||
int result = mSharedPreferences.getInt(identifier, 0);
|
|
||||||
if (result == 0) {
|
|
||||||
result = mNextDeviceId++;
|
|
||||||
spedit.putInt("next_device_id", mNextDeviceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
spedit.putInt(identifier, result);
|
|
||||||
spedit.commit();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initializeUSB() {
|
|
||||||
mUsbManager = (UsbManager)mContext.getSystemService(Context.USB_SERVICE);
|
|
||||||
if (mUsbManager == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
// Logging
|
|
||||||
for (UsbDevice device : mUsbManager.getDeviceList().values()) {
|
|
||||||
Log.i(TAG,"Path: " + device.getDeviceName());
|
|
||||||
Log.i(TAG,"Manufacturer: " + device.getManufacturerName());
|
|
||||||
Log.i(TAG,"Product: " + device.getProductName());
|
|
||||||
Log.i(TAG,"ID: " + device.getDeviceId());
|
|
||||||
Log.i(TAG,"Class: " + device.getDeviceClass());
|
|
||||||
Log.i(TAG,"Protocol: " + device.getDeviceProtocol());
|
|
||||||
Log.i(TAG,"Vendor ID " + device.getVendorId());
|
|
||||||
Log.i(TAG,"Product ID: " + device.getProductId());
|
|
||||||
Log.i(TAG,"Interface count: " + device.getInterfaceCount());
|
|
||||||
Log.i(TAG,"---------------------------------------");
|
|
||||||
|
|
||||||
// Get interface details
|
|
||||||
for (int index = 0; index < device.getInterfaceCount(); index++) {
|
|
||||||
UsbInterface mUsbInterface = device.getInterface(index);
|
|
||||||
Log.i(TAG," ***** *****");
|
|
||||||
Log.i(TAG," Interface index: " + index);
|
|
||||||
Log.i(TAG," Interface ID: " + mUsbInterface.getId());
|
|
||||||
Log.i(TAG," Interface class: " + mUsbInterface.getInterfaceClass());
|
|
||||||
Log.i(TAG," Interface subclass: " + mUsbInterface.getInterfaceSubclass());
|
|
||||||
Log.i(TAG," Interface protocol: " + mUsbInterface.getInterfaceProtocol());
|
|
||||||
Log.i(TAG," Endpoint count: " + mUsbInterface.getEndpointCount());
|
|
||||||
|
|
||||||
// Get endpoint details
|
|
||||||
for (int epi = 0; epi < mUsbInterface.getEndpointCount(); epi++)
|
|
||||||
{
|
|
||||||
UsbEndpoint mEndpoint = mUsbInterface.getEndpoint(epi);
|
|
||||||
Log.i(TAG," ++++ ++++ ++++");
|
|
||||||
Log.i(TAG," Endpoint index: " + epi);
|
|
||||||
Log.i(TAG," Attributes: " + mEndpoint.getAttributes());
|
|
||||||
Log.i(TAG," Direction: " + mEndpoint.getDirection());
|
|
||||||
Log.i(TAG," Number: " + mEndpoint.getEndpointNumber());
|
|
||||||
Log.i(TAG," Interval: " + mEndpoint.getInterval());
|
|
||||||
Log.i(TAG," Packet size: " + mEndpoint.getMaxPacketSize());
|
|
||||||
Log.i(TAG," Type: " + mEndpoint.getType());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Log.i(TAG," No more devices connected.");
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Register for USB broadcasts and permission completions
|
|
||||||
IntentFilter filter = new IntentFilter();
|
|
||||||
filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
|
|
||||||
filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
|
|
||||||
filter.addAction(HIDDeviceManager.ACTION_USB_PERMISSION);
|
|
||||||
mContext.registerReceiver(mUsbBroadcast, filter);
|
|
||||||
|
|
||||||
for (UsbDevice usbDevice : mUsbManager.getDeviceList().values()) {
|
|
||||||
handleUsbDeviceAttached(usbDevice);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
UsbManager getUSBManager() {
|
|
||||||
return mUsbManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void shutdownUSB() {
|
|
||||||
try {
|
|
||||||
mContext.unregisterReceiver(mUsbBroadcast);
|
|
||||||
} catch (Exception e) {
|
|
||||||
// We may not have registered, that's okay
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isHIDDeviceInterface(UsbDevice usbDevice, UsbInterface usbInterface) {
|
|
||||||
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_HID) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (isXbox360Controller(usbDevice, usbInterface) || isXboxOneController(usbDevice, usbInterface)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isXbox360Controller(UsbDevice usbDevice, UsbInterface usbInterface) {
|
|
||||||
final int XB360_IFACE_SUBCLASS = 93;
|
|
||||||
final int XB360_IFACE_PROTOCOL = 1; // Wired
|
|
||||||
final int XB360W_IFACE_PROTOCOL = 129; // Wireless
|
|
||||||
final int[] SUPPORTED_VENDORS = {
|
|
||||||
0x0079, // GPD Win 2
|
|
||||||
0x044f, // Thrustmaster
|
|
||||||
0x045e, // Microsoft
|
|
||||||
0x046d, // Logitech
|
|
||||||
0x056e, // Elecom
|
|
||||||
0x06a3, // Saitek
|
|
||||||
0x0738, // Mad Catz
|
|
||||||
0x07ff, // Mad Catz
|
|
||||||
0x0e6f, // PDP
|
|
||||||
0x0f0d, // Hori
|
|
||||||
0x1038, // SteelSeries
|
|
||||||
0x11c9, // Nacon
|
|
||||||
0x12ab, // Unknown
|
|
||||||
0x1430, // RedOctane
|
|
||||||
0x146b, // BigBen
|
|
||||||
0x1532, // Razer Sabertooth
|
|
||||||
0x15e4, // Numark
|
|
||||||
0x162e, // Joytech
|
|
||||||
0x1689, // Razer Onza
|
|
||||||
0x1949, // Lab126, Inc.
|
|
||||||
0x1bad, // Harmonix
|
|
||||||
0x20d6, // PowerA
|
|
||||||
0x24c6, // PowerA
|
|
||||||
0x2c22, // Qanba
|
|
||||||
0x2dc8, // 8BitDo
|
|
||||||
0x9886, // ASTRO Gaming
|
|
||||||
};
|
|
||||||
|
|
||||||
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&
|
|
||||||
usbInterface.getInterfaceSubclass() == XB360_IFACE_SUBCLASS &&
|
|
||||||
(usbInterface.getInterfaceProtocol() == XB360_IFACE_PROTOCOL ||
|
|
||||||
usbInterface.getInterfaceProtocol() == XB360W_IFACE_PROTOCOL)) {
|
|
||||||
int vendor_id = usbDevice.getVendorId();
|
|
||||||
for (int supportedVid : SUPPORTED_VENDORS) {
|
|
||||||
if (vendor_id == supportedVid) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isXboxOneController(UsbDevice usbDevice, UsbInterface usbInterface) {
|
|
||||||
final int XB1_IFACE_SUBCLASS = 71;
|
|
||||||
final int XB1_IFACE_PROTOCOL = 208;
|
|
||||||
final int[] SUPPORTED_VENDORS = {
|
|
||||||
0x03f0, // HP
|
|
||||||
0x044f, // Thrustmaster
|
|
||||||
0x045e, // Microsoft
|
|
||||||
0x0738, // Mad Catz
|
|
||||||
0x0b05, // ASUS
|
|
||||||
0x0e6f, // PDP
|
|
||||||
0x0f0d, // Hori
|
|
||||||
0x10f5, // Turtle Beach
|
|
||||||
0x1532, // Razer Wildcat
|
|
||||||
0x20d6, // PowerA
|
|
||||||
0x24c6, // PowerA
|
|
||||||
0x2dc8, // 8BitDo
|
|
||||||
0x2e24, // Hyperkin
|
|
||||||
0x3537, // GameSir
|
|
||||||
};
|
|
||||||
|
|
||||||
if (usbInterface.getId() == 0 &&
|
|
||||||
usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&
|
|
||||||
usbInterface.getInterfaceSubclass() == XB1_IFACE_SUBCLASS &&
|
|
||||||
usbInterface.getInterfaceProtocol() == XB1_IFACE_PROTOCOL) {
|
|
||||||
int vendor_id = usbDevice.getVendorId();
|
|
||||||
for (int supportedVid : SUPPORTED_VENDORS) {
|
|
||||||
if (vendor_id == supportedVid) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleUsbDeviceAttached(UsbDevice usbDevice) {
|
|
||||||
connectHIDDeviceUSB(usbDevice);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleUsbDeviceDetached(UsbDevice usbDevice) {
|
|
||||||
List<Integer> devices = new ArrayList<Integer>();
|
|
||||||
for (HIDDevice device : mDevicesById.values()) {
|
|
||||||
if (usbDevice.equals(device.getDevice())) {
|
|
||||||
devices.add(device.getId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (int id : devices) {
|
|
||||||
HIDDevice device = mDevicesById.get(id);
|
|
||||||
mDevicesById.remove(id);
|
|
||||||
device.shutdown();
|
|
||||||
HIDDeviceDisconnected(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleUsbDevicePermission(UsbDevice usbDevice, boolean permission_granted) {
|
|
||||||
for (HIDDevice device : mDevicesById.values()) {
|
|
||||||
if (usbDevice.equals(device.getDevice())) {
|
|
||||||
boolean opened = false;
|
|
||||||
if (permission_granted) {
|
|
||||||
opened = device.open();
|
|
||||||
}
|
|
||||||
HIDDeviceOpenResult(device.getId(), opened);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void connectHIDDeviceUSB(UsbDevice usbDevice) {
|
|
||||||
synchronized (this) {
|
|
||||||
int interface_mask = 0;
|
|
||||||
for (int interface_index = 0; interface_index < usbDevice.getInterfaceCount(); interface_index++) {
|
|
||||||
UsbInterface usbInterface = usbDevice.getInterface(interface_index);
|
|
||||||
if (isHIDDeviceInterface(usbDevice, usbInterface)) {
|
|
||||||
// Check to see if we've already added this interface
|
|
||||||
// This happens with the Xbox Series X controller which has a duplicate interface 0, which is inactive
|
|
||||||
int interface_id = usbInterface.getId();
|
|
||||||
if ((interface_mask & (1 << interface_id)) != 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
interface_mask |= (1 << interface_id);
|
|
||||||
|
|
||||||
HIDDeviceUSB device = new HIDDeviceUSB(this, usbDevice, interface_index);
|
|
||||||
int id = device.getId();
|
|
||||||
mDevicesById.put(id, device);
|
|
||||||
HIDDeviceConnected(id, device.getIdentifier(), device.getVendorId(), device.getProductId(), device.getSerialNumber(), device.getVersion(), device.getManufacturerName(), device.getProductName(), usbInterface.getId(), usbInterface.getInterfaceClass(), usbInterface.getInterfaceSubclass(), usbInterface.getInterfaceProtocol());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initializeBluetooth() {
|
|
||||||
Log.d(TAG, "Initializing Bluetooth");
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= 31 /* Android 12 */ &&
|
|
||||||
mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH_CONNECT, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) {
|
|
||||||
Log.d(TAG, "Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH_CONNECT");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT <= 30 /* Android 11.0 (R) */ &&
|
|
||||||
mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) {
|
|
||||||
Log.d(TAG, "Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) || (Build.VERSION.SDK_INT < 18 /* Android 4.3 (JELLY_BEAN_MR2) */)) {
|
|
||||||
Log.d(TAG, "Couldn't initialize Bluetooth, this version of Android does not support Bluetooth LE");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find bonded bluetooth controllers and create SteamControllers for them
|
|
||||||
mBluetoothManager = (BluetoothManager)mContext.getSystemService(Context.BLUETOOTH_SERVICE);
|
|
||||||
if (mBluetoothManager == null) {
|
|
||||||
// This device doesn't support Bluetooth.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
BluetoothAdapter btAdapter = mBluetoothManager.getAdapter();
|
|
||||||
if (btAdapter == null) {
|
|
||||||
// This device has Bluetooth support in the codebase, but has no available adapters.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get our bonded devices.
|
|
||||||
for (BluetoothDevice device : btAdapter.getBondedDevices()) {
|
|
||||||
|
|
||||||
Log.d(TAG, "Bluetooth device available: " + device);
|
|
||||||
if (isSteamController(device)) {
|
|
||||||
connectBluetoothDevice(device);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: These don't work on Chromebooks, to my undying dismay.
|
|
||||||
IntentFilter filter = new IntentFilter();
|
|
||||||
filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
|
|
||||||
filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
|
|
||||||
mContext.registerReceiver(mBluetoothBroadcast, filter);
|
|
||||||
|
|
||||||
if (mIsChromebook) {
|
|
||||||
mHandler = new Handler(Looper.getMainLooper());
|
|
||||||
mLastBluetoothDevices = new ArrayList<BluetoothDevice>();
|
|
||||||
|
|
||||||
// final HIDDeviceManager finalThis = this;
|
|
||||||
// mHandler.postDelayed(new Runnable() {
|
|
||||||
// @Override
|
|
||||||
// public void run() {
|
|
||||||
// finalThis.chromebookConnectionHandler();
|
|
||||||
// }
|
|
||||||
// }, 5000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void shutdownBluetooth() {
|
|
||||||
try {
|
|
||||||
mContext.unregisterReceiver(mBluetoothBroadcast);
|
|
||||||
} catch (Exception e) {
|
|
||||||
// We may not have registered, that's okay
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chromebooks do not pass along ACTION_ACL_CONNECTED / ACTION_ACL_DISCONNECTED properly.
|
|
||||||
// This function provides a sort of dummy version of that, watching for changes in the
|
|
||||||
// connected devices and attempting to add controllers as things change.
|
|
||||||
public void chromebookConnectionHandler() {
|
|
||||||
if (!mIsChromebook) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ArrayList<BluetoothDevice> disconnected = new ArrayList<BluetoothDevice>();
|
|
||||||
ArrayList<BluetoothDevice> connected = new ArrayList<BluetoothDevice>();
|
|
||||||
|
|
||||||
List<BluetoothDevice> currentConnected = mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT);
|
|
||||||
|
|
||||||
for (BluetoothDevice bluetoothDevice : currentConnected) {
|
|
||||||
if (!mLastBluetoothDevices.contains(bluetoothDevice)) {
|
|
||||||
connected.add(bluetoothDevice);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (BluetoothDevice bluetoothDevice : mLastBluetoothDevices) {
|
|
||||||
if (!currentConnected.contains(bluetoothDevice)) {
|
|
||||||
disconnected.add(bluetoothDevice);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mLastBluetoothDevices = currentConnected;
|
|
||||||
|
|
||||||
for (BluetoothDevice bluetoothDevice : disconnected) {
|
|
||||||
disconnectBluetoothDevice(bluetoothDevice);
|
|
||||||
}
|
|
||||||
for (BluetoothDevice bluetoothDevice : connected) {
|
|
||||||
connectBluetoothDevice(bluetoothDevice);
|
|
||||||
}
|
|
||||||
|
|
||||||
final HIDDeviceManager finalThis = this;
|
|
||||||
mHandler.postDelayed(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
finalThis.chromebookConnectionHandler();
|
|
||||||
}
|
|
||||||
}, 10000);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean connectBluetoothDevice(BluetoothDevice bluetoothDevice) {
|
|
||||||
Log.v(TAG, "connectBluetoothDevice device=" + bluetoothDevice);
|
|
||||||
synchronized (this) {
|
|
||||||
if (mBluetoothDevices.containsKey(bluetoothDevice)) {
|
|
||||||
Log.v(TAG, "Steam controller with address " + bluetoothDevice + " already exists, attempting reconnect");
|
|
||||||
|
|
||||||
HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice);
|
|
||||||
device.reconnect();
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
HIDDeviceBLESteamController device = new HIDDeviceBLESteamController(this, bluetoothDevice);
|
|
||||||
int id = device.getId();
|
|
||||||
mBluetoothDevices.put(bluetoothDevice, device);
|
|
||||||
mDevicesById.put(id, device);
|
|
||||||
|
|
||||||
// The Steam Controller will mark itself connected once initialization is complete
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void disconnectBluetoothDevice(BluetoothDevice bluetoothDevice) {
|
|
||||||
synchronized (this) {
|
|
||||||
HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice);
|
|
||||||
if (device == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
int id = device.getId();
|
|
||||||
mBluetoothDevices.remove(bluetoothDevice);
|
|
||||||
mDevicesById.remove(id);
|
|
||||||
device.shutdown();
|
|
||||||
HIDDeviceDisconnected(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSteamController(BluetoothDevice bluetoothDevice) {
|
|
||||||
// Sanity check. If you pass in a null device, by definition it is never a Steam Controller.
|
|
||||||
if (bluetoothDevice == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the device has no local name, we really don't want to try an equality check against it.
|
|
||||||
if (bluetoothDevice.getName() == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return bluetoothDevice.getName().equals("SteamController") && ((bluetoothDevice.getType() & BluetoothDevice.DEVICE_TYPE_LE) != 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void close() {
|
|
||||||
shutdownUSB();
|
|
||||||
shutdownBluetooth();
|
|
||||||
synchronized (this) {
|
|
||||||
for (HIDDevice device : mDevicesById.values()) {
|
|
||||||
device.shutdown();
|
|
||||||
}
|
|
||||||
mDevicesById.clear();
|
|
||||||
mBluetoothDevices.clear();
|
|
||||||
HIDDeviceReleaseCallback();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFrozen(boolean frozen) {
|
|
||||||
synchronized (this) {
|
|
||||||
for (HIDDevice device : mDevicesById.values()) {
|
|
||||||
device.setFrozen(frozen);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
private HIDDevice getDevice(int id) {
|
|
||||||
synchronized (this) {
|
|
||||||
HIDDevice result = mDevicesById.get(id);
|
|
||||||
if (result == null) {
|
|
||||||
Log.v(TAG, "No device for id: " + id);
|
|
||||||
Log.v(TAG, "Available devices: " + mDevicesById.keySet());
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
////////// JNI interface functions
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
public boolean initialize(boolean usb, boolean bluetooth) {
|
|
||||||
Log.v(TAG, "initialize(" + usb + ", " + bluetooth + ")");
|
|
||||||
|
|
||||||
if (usb) {
|
|
||||||
initializeUSB();
|
|
||||||
}
|
|
||||||
if (bluetooth) {
|
|
||||||
initializeBluetooth();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean openDevice(int deviceID) {
|
|
||||||
Log.v(TAG, "openDevice deviceID=" + deviceID);
|
|
||||||
HIDDevice device = getDevice(deviceID);
|
|
||||||
if (device == null) {
|
|
||||||
HIDDeviceDisconnected(deviceID);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Look to see if this is a USB device and we have permission to access it
|
|
||||||
UsbDevice usbDevice = device.getDevice();
|
|
||||||
if (usbDevice != null && !mUsbManager.hasPermission(usbDevice)) {
|
|
||||||
HIDDeviceOpenPending(deviceID);
|
|
||||||
try {
|
|
||||||
final int FLAG_MUTABLE = 0x02000000; // PendingIntent.FLAG_MUTABLE, but don't require SDK 31
|
|
||||||
int flags;
|
|
||||||
if (Build.VERSION.SDK_INT >= 31 /* Android 12.0 (S) */) {
|
|
||||||
flags = FLAG_MUTABLE;
|
|
||||||
} else {
|
|
||||||
flags = 0;
|
|
||||||
}
|
|
||||||
if (Build.VERSION.SDK_INT >= 33 /* Android 14.0 (U) */) {
|
|
||||||
Intent intent = new Intent(HIDDeviceManager.ACTION_USB_PERMISSION);
|
|
||||||
intent.setPackage(mContext.getPackageName());
|
|
||||||
mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, intent, flags));
|
|
||||||
} else {
|
|
||||||
mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, new Intent(HIDDeviceManager.ACTION_USB_PERMISSION), flags));
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.v(TAG, "Couldn't request permission for USB device " + usbDevice);
|
|
||||||
HIDDeviceOpenResult(deviceID, false);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return device.open();
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int sendOutputReport(int deviceID, byte[] report) {
|
|
||||||
try {
|
|
||||||
//Log.v(TAG, "sendOutputReport deviceID=" + deviceID + " length=" + report.length);
|
|
||||||
HIDDevice device;
|
|
||||||
device = getDevice(deviceID);
|
|
||||||
if (device == null) {
|
|
||||||
HIDDeviceDisconnected(deviceID);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return device.sendOutputReport(report);
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int sendFeatureReport(int deviceID, byte[] report) {
|
|
||||||
try {
|
|
||||||
//Log.v(TAG, "sendFeatureReport deviceID=" + deviceID + " length=" + report.length);
|
|
||||||
HIDDevice device;
|
|
||||||
device = getDevice(deviceID);
|
|
||||||
if (device == null) {
|
|
||||||
HIDDeviceDisconnected(deviceID);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return device.sendFeatureReport(report);
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean getFeatureReport(int deviceID, byte[] report) {
|
|
||||||
try {
|
|
||||||
//Log.v(TAG, "getFeatureReport deviceID=" + deviceID);
|
|
||||||
HIDDevice device;
|
|
||||||
device = getDevice(deviceID);
|
|
||||||
if (device == null) {
|
|
||||||
HIDDeviceDisconnected(deviceID);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return device.getFeatureReport(report);
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void closeDevice(int deviceID) {
|
|
||||||
try {
|
|
||||||
Log.v(TAG, "closeDevice deviceID=" + deviceID);
|
|
||||||
HIDDevice device;
|
|
||||||
device = getDevice(deviceID);
|
|
||||||
if (device == null) {
|
|
||||||
HIDDeviceDisconnected(deviceID);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
device.close();
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
/////////////// Native methods
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
private native void HIDDeviceRegisterCallback();
|
|
||||||
private native void HIDDeviceReleaseCallback();
|
|
||||||
|
|
||||||
native void HIDDeviceConnected(int deviceID, String identifier, int vendorId, int productId, String serial_number, int release_number, String manufacturer_string, String product_string, int interface_number, int interface_class, int interface_subclass, int interface_protocol);
|
|
||||||
native void HIDDeviceOpenPending(int deviceID);
|
|
||||||
native void HIDDeviceOpenResult(int deviceID, boolean opened);
|
|
||||||
native void HIDDeviceDisconnected(int deviceID);
|
|
||||||
|
|
||||||
native void HIDDeviceInputReport(int deviceID, byte[] report);
|
|
||||||
native void HIDDeviceFeatureReport(int deviceID, byte[] report);
|
|
||||||
}
|
|
||||||
@ -1,309 +0,0 @@
|
|||||||
package org.libsdl.app;
|
|
||||||
|
|
||||||
import android.hardware.usb.*;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.util.Log;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
class HIDDeviceUSB implements HIDDevice {
|
|
||||||
|
|
||||||
private static final String TAG = "hidapi";
|
|
||||||
|
|
||||||
protected HIDDeviceManager mManager;
|
|
||||||
protected UsbDevice mDevice;
|
|
||||||
protected int mInterfaceIndex;
|
|
||||||
protected int mInterface;
|
|
||||||
protected int mDeviceId;
|
|
||||||
protected UsbDeviceConnection mConnection;
|
|
||||||
protected UsbEndpoint mInputEndpoint;
|
|
||||||
protected UsbEndpoint mOutputEndpoint;
|
|
||||||
protected InputThread mInputThread;
|
|
||||||
protected boolean mRunning;
|
|
||||||
protected boolean mFrozen;
|
|
||||||
|
|
||||||
public HIDDeviceUSB(HIDDeviceManager manager, UsbDevice usbDevice, int interface_index) {
|
|
||||||
mManager = manager;
|
|
||||||
mDevice = usbDevice;
|
|
||||||
mInterfaceIndex = interface_index;
|
|
||||||
mInterface = mDevice.getInterface(mInterfaceIndex).getId();
|
|
||||||
mDeviceId = manager.getDeviceIDForIdentifier(getIdentifier());
|
|
||||||
mRunning = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getIdentifier() {
|
|
||||||
return String.format("%s/%x/%x/%d", mDevice.getDeviceName(), mDevice.getVendorId(), mDevice.getProductId(), mInterfaceIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getId() {
|
|
||||||
return mDeviceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getVendorId() {
|
|
||||||
return mDevice.getVendorId();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getProductId() {
|
|
||||||
return mDevice.getProductId();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getSerialNumber() {
|
|
||||||
String result = null;
|
|
||||||
if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {
|
|
||||||
try {
|
|
||||||
result = mDevice.getSerialNumber();
|
|
||||||
}
|
|
||||||
catch (SecurityException exception) {
|
|
||||||
//Log.w(TAG, "App permissions mean we cannot get serial number for device " + getDeviceName() + " message: " + exception.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (result == null) {
|
|
||||||
result = "";
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getVersion() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getManufacturerName() {
|
|
||||||
String result = null;
|
|
||||||
if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {
|
|
||||||
result = mDevice.getManufacturerName();
|
|
||||||
}
|
|
||||||
if (result == null) {
|
|
||||||
result = String.format("%x", getVendorId());
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getProductName() {
|
|
||||||
String result = null;
|
|
||||||
if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {
|
|
||||||
result = mDevice.getProductName();
|
|
||||||
}
|
|
||||||
if (result == null) {
|
|
||||||
result = String.format("%x", getProductId());
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public UsbDevice getDevice() {
|
|
||||||
return mDevice;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDeviceName() {
|
|
||||||
return getManufacturerName() + " " + getProductName() + "(0x" + String.format("%x", getVendorId()) + "/0x" + String.format("%x", getProductId()) + ")";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean open() {
|
|
||||||
mConnection = mManager.getUSBManager().openDevice(mDevice);
|
|
||||||
if (mConnection == null) {
|
|
||||||
Log.w(TAG, "Unable to open USB device " + getDeviceName());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Force claim our interface
|
|
||||||
UsbInterface iface = mDevice.getInterface(mInterfaceIndex);
|
|
||||||
if (!mConnection.claimInterface(iface, true)) {
|
|
||||||
Log.w(TAG, "Failed to claim interfaces on USB device " + getDeviceName());
|
|
||||||
close();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the endpoints
|
|
||||||
for (int j = 0; j < iface.getEndpointCount(); j++) {
|
|
||||||
UsbEndpoint endpt = iface.getEndpoint(j);
|
|
||||||
switch (endpt.getDirection()) {
|
|
||||||
case UsbConstants.USB_DIR_IN:
|
|
||||||
if (mInputEndpoint == null) {
|
|
||||||
mInputEndpoint = endpt;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case UsbConstants.USB_DIR_OUT:
|
|
||||||
if (mOutputEndpoint == null) {
|
|
||||||
mOutputEndpoint = endpt;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure the required endpoints were present
|
|
||||||
if (mInputEndpoint == null || mOutputEndpoint == null) {
|
|
||||||
Log.w(TAG, "Missing required endpoint on USB device " + getDeviceName());
|
|
||||||
close();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start listening for input
|
|
||||||
mRunning = true;
|
|
||||||
mInputThread = new InputThread();
|
|
||||||
mInputThread.start();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int sendFeatureReport(byte[] report) {
|
|
||||||
int res = -1;
|
|
||||||
int offset = 0;
|
|
||||||
int length = report.length;
|
|
||||||
boolean skipped_report_id = false;
|
|
||||||
byte report_number = report[0];
|
|
||||||
|
|
||||||
if (report_number == 0x0) {
|
|
||||||
++offset;
|
|
||||||
--length;
|
|
||||||
skipped_report_id = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
res = mConnection.controlTransfer(
|
|
||||||
UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_OUT,
|
|
||||||
0x09/*HID set_report*/,
|
|
||||||
(3/*HID feature*/ << 8) | report_number,
|
|
||||||
mInterface,
|
|
||||||
report, offset, length,
|
|
||||||
1000/*timeout millis*/);
|
|
||||||
|
|
||||||
if (res < 0) {
|
|
||||||
Log.w(TAG, "sendFeatureReport() returned " + res + " on device " + getDeviceName());
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (skipped_report_id) {
|
|
||||||
++length;
|
|
||||||
}
|
|
||||||
return length;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int sendOutputReport(byte[] report) {
|
|
||||||
int r = mConnection.bulkTransfer(mOutputEndpoint, report, report.length, 1000);
|
|
||||||
if (r != report.length) {
|
|
||||||
Log.w(TAG, "sendOutputReport() returned " + r + " on device " + getDeviceName());
|
|
||||||
}
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean getFeatureReport(byte[] report) {
|
|
||||||
int res = -1;
|
|
||||||
int offset = 0;
|
|
||||||
int length = report.length;
|
|
||||||
boolean skipped_report_id = false;
|
|
||||||
byte report_number = report[0];
|
|
||||||
|
|
||||||
if (report_number == 0x0) {
|
|
||||||
/* Offset the return buffer by 1, so that the report ID
|
|
||||||
will remain in byte 0. */
|
|
||||||
++offset;
|
|
||||||
--length;
|
|
||||||
skipped_report_id = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
res = mConnection.controlTransfer(
|
|
||||||
UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_IN,
|
|
||||||
0x01/*HID get_report*/,
|
|
||||||
(3/*HID feature*/ << 8) | report_number,
|
|
||||||
mInterface,
|
|
||||||
report, offset, length,
|
|
||||||
1000/*timeout millis*/);
|
|
||||||
|
|
||||||
if (res < 0) {
|
|
||||||
Log.w(TAG, "getFeatureReport() returned " + res + " on device " + getDeviceName());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (skipped_report_id) {
|
|
||||||
++res;
|
|
||||||
++length;
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] data;
|
|
||||||
if (res == length) {
|
|
||||||
data = report;
|
|
||||||
} else {
|
|
||||||
data = Arrays.copyOfRange(report, 0, res);
|
|
||||||
}
|
|
||||||
mManager.HIDDeviceFeatureReport(mDeviceId, data);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
mRunning = false;
|
|
||||||
if (mInputThread != null) {
|
|
||||||
while (mInputThread.isAlive()) {
|
|
||||||
mInputThread.interrupt();
|
|
||||||
try {
|
|
||||||
mInputThread.join();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
// Keep trying until we're done
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mInputThread = null;
|
|
||||||
}
|
|
||||||
if (mConnection != null) {
|
|
||||||
UsbInterface iface = mDevice.getInterface(mInterfaceIndex);
|
|
||||||
mConnection.releaseInterface(iface);
|
|
||||||
mConnection.close();
|
|
||||||
mConnection = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void shutdown() {
|
|
||||||
close();
|
|
||||||
mManager = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setFrozen(boolean frozen) {
|
|
||||||
mFrozen = frozen;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected class InputThread extends Thread {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
int packetSize = mInputEndpoint.getMaxPacketSize();
|
|
||||||
byte[] packet = new byte[packetSize];
|
|
||||||
while (mRunning) {
|
|
||||||
int r;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
r = mConnection.bulkTransfer(mInputEndpoint, packet, packetSize, 1000);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.v(TAG, "Exception in UsbDeviceConnection bulktransfer: " + e);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (r < 0) {
|
|
||||||
// Could be a timeout or an I/O error
|
|
||||||
}
|
|
||||||
if (r > 0) {
|
|
||||||
byte[] data;
|
|
||||||
if (r == packetSize) {
|
|
||||||
data = packet;
|
|
||||||
} else {
|
|
||||||
data = Arrays.copyOfRange(packet, 0, r);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!mFrozen) {
|
|
||||||
mManager.HIDDeviceInputReport(mDeviceId, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,90 +0,0 @@
|
|||||||
package org.libsdl.app;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
import java.lang.Class;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
|
|
||||||
/**
|
|
||||||
SDL library initialization
|
|
||||||
*/
|
|
||||||
public class SDL {
|
|
||||||
|
|
||||||
// This function should be called first and sets up the native code
|
|
||||||
// so it can call into the Java classes
|
|
||||||
public static void setupJNI() {
|
|
||||||
SDLActivity.nativeSetupJNI();
|
|
||||||
SDLAudioManager.nativeSetupJNI();
|
|
||||||
SDLControllerManager.nativeSetupJNI();
|
|
||||||
}
|
|
||||||
|
|
||||||
// This function should be called each time the activity is started
|
|
||||||
public static void initialize() {
|
|
||||||
setContext(null);
|
|
||||||
|
|
||||||
SDLActivity.initialize();
|
|
||||||
SDLAudioManager.initialize();
|
|
||||||
SDLControllerManager.initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
// This function stores the current activity (SDL or not)
|
|
||||||
public static void setContext(Context context) {
|
|
||||||
SDLAudioManager.setContext(context);
|
|
||||||
mContext = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Context getContext() {
|
|
||||||
return mContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void loadLibrary(String libraryName) throws UnsatisfiedLinkError, SecurityException, NullPointerException {
|
|
||||||
loadLibrary(libraryName, mContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void loadLibrary(String libraryName, Context context) throws UnsatisfiedLinkError, SecurityException, NullPointerException {
|
|
||||||
|
|
||||||
if (libraryName == null) {
|
|
||||||
throw new NullPointerException("No library name provided.");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Let's see if we have ReLinker available in the project. This is necessary for
|
|
||||||
// some projects that have huge numbers of local libraries bundled, and thus may
|
|
||||||
// trip a bug in Android's native library loader which ReLinker works around. (If
|
|
||||||
// loadLibrary works properly, ReLinker will simply use the normal Android method
|
|
||||||
// internally.)
|
|
||||||
//
|
|
||||||
// To use ReLinker, just add it as a dependency. For more information, see
|
|
||||||
// https://github.com/KeepSafe/ReLinker for ReLinker's repository.
|
|
||||||
//
|
|
||||||
Class<?> relinkClass = context.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker");
|
|
||||||
Class<?> relinkListenerClass = context.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker$LoadListener");
|
|
||||||
Class<?> contextClass = context.getClassLoader().loadClass("android.content.Context");
|
|
||||||
Class<?> stringClass = context.getClassLoader().loadClass("java.lang.String");
|
|
||||||
|
|
||||||
// Get a 'force' instance of the ReLinker, so we can ensure libraries are reinstalled if
|
|
||||||
// they've changed during updates.
|
|
||||||
Method forceMethod = relinkClass.getDeclaredMethod("force");
|
|
||||||
Object relinkInstance = forceMethod.invoke(null);
|
|
||||||
Class<?> relinkInstanceClass = relinkInstance.getClass();
|
|
||||||
|
|
||||||
// Actually load the library!
|
|
||||||
Method loadMethod = relinkInstanceClass.getDeclaredMethod("loadLibrary", contextClass, stringClass, stringClass, relinkListenerClass);
|
|
||||||
loadMethod.invoke(relinkInstance, context, libraryName, null, null);
|
|
||||||
}
|
|
||||||
catch (final Throwable e) {
|
|
||||||
// Fall back
|
|
||||||
try {
|
|
||||||
System.loadLibrary(libraryName);
|
|
||||||
}
|
|
||||||
catch (final UnsatisfiedLinkError ule) {
|
|
||||||
throw ule;
|
|
||||||
}
|
|
||||||
catch (final SecurityException se) {
|
|
||||||
throw se;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static Context mContext;
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,514 +0,0 @@
|
|||||||
package org.libsdl.app;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.media.AudioDeviceCallback;
|
|
||||||
import android.media.AudioDeviceInfo;
|
|
||||||
import android.media.AudioFormat;
|
|
||||||
import android.media.AudioManager;
|
|
||||||
import android.media.AudioRecord;
|
|
||||||
import android.media.AudioTrack;
|
|
||||||
import android.media.MediaRecorder;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
public class SDLAudioManager {
|
|
||||||
protected static final String TAG = "SDLAudio";
|
|
||||||
|
|
||||||
protected static AudioTrack mAudioTrack;
|
|
||||||
protected static AudioRecord mAudioRecord;
|
|
||||||
protected static Context mContext;
|
|
||||||
|
|
||||||
private static final int[] NO_DEVICES = {};
|
|
||||||
|
|
||||||
private static AudioDeviceCallback mAudioDeviceCallback;
|
|
||||||
|
|
||||||
public static void initialize() {
|
|
||||||
mAudioTrack = null;
|
|
||||||
mAudioRecord = null;
|
|
||||||
mAudioDeviceCallback = null;
|
|
||||||
|
|
||||||
if(Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */)
|
|
||||||
{
|
|
||||||
mAudioDeviceCallback = new AudioDeviceCallback() {
|
|
||||||
@Override
|
|
||||||
public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
|
|
||||||
Arrays.stream(addedDevices).forEach(deviceInfo -> addAudioDevice(deviceInfo.isSink(), deviceInfo.getId()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
|
|
||||||
Arrays.stream(removedDevices).forEach(deviceInfo -> removeAudioDevice(deviceInfo.isSink(), deviceInfo.getId()));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setContext(Context context) {
|
|
||||||
mContext = context;
|
|
||||||
if (context != null) {
|
|
||||||
registerAudioDeviceCallback();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void release(Context context) {
|
|
||||||
unregisterAudioDeviceCallback(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Audio
|
|
||||||
|
|
||||||
protected static String getAudioFormatString(int audioFormat) {
|
|
||||||
switch (audioFormat) {
|
|
||||||
case AudioFormat.ENCODING_PCM_8BIT:
|
|
||||||
return "8-bit";
|
|
||||||
case AudioFormat.ENCODING_PCM_16BIT:
|
|
||||||
return "16-bit";
|
|
||||||
case AudioFormat.ENCODING_PCM_FLOAT:
|
|
||||||
return "float";
|
|
||||||
default:
|
|
||||||
return Integer.toString(audioFormat);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static int[] open(boolean isCapture, int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {
|
|
||||||
int channelConfig;
|
|
||||||
int sampleSize;
|
|
||||||
int frameSize;
|
|
||||||
|
|
||||||
Log.v(TAG, "Opening " + (isCapture ? "capture" : "playback") + ", requested " + desiredFrames + " frames of " + desiredChannels + " channel " + getAudioFormatString(audioFormat) + " audio at " + sampleRate + " Hz");
|
|
||||||
|
|
||||||
/* On older devices let's use known good settings */
|
|
||||||
if (Build.VERSION.SDK_INT < 21 /* Android 5.0 (LOLLIPOP) */) {
|
|
||||||
if (desiredChannels > 2) {
|
|
||||||
desiredChannels = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* AudioTrack has sample rate limitation of 48000 (fixed in 5.0.2) */
|
|
||||||
if (Build.VERSION.SDK_INT < 22 /* Android 5.1 (LOLLIPOP_MR1) */) {
|
|
||||||
if (sampleRate < 8000) {
|
|
||||||
sampleRate = 8000;
|
|
||||||
} else if (sampleRate > 48000) {
|
|
||||||
sampleRate = 48000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (audioFormat == AudioFormat.ENCODING_PCM_FLOAT) {
|
|
||||||
int minSDKVersion = (isCapture ? 23 /* Android 6.0 (M) */ : 21 /* Android 5.0 (LOLLIPOP) */);
|
|
||||||
if (Build.VERSION.SDK_INT < minSDKVersion) {
|
|
||||||
audioFormat = AudioFormat.ENCODING_PCM_16BIT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch (audioFormat)
|
|
||||||
{
|
|
||||||
case AudioFormat.ENCODING_PCM_8BIT:
|
|
||||||
sampleSize = 1;
|
|
||||||
break;
|
|
||||||
case AudioFormat.ENCODING_PCM_16BIT:
|
|
||||||
sampleSize = 2;
|
|
||||||
break;
|
|
||||||
case AudioFormat.ENCODING_PCM_FLOAT:
|
|
||||||
sampleSize = 4;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Log.v(TAG, "Requested format " + audioFormat + ", getting ENCODING_PCM_16BIT");
|
|
||||||
audioFormat = AudioFormat.ENCODING_PCM_16BIT;
|
|
||||||
sampleSize = 2;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isCapture) {
|
|
||||||
switch (desiredChannels) {
|
|
||||||
case 1:
|
|
||||||
channelConfig = AudioFormat.CHANNEL_IN_MONO;
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
channelConfig = AudioFormat.CHANNEL_IN_STEREO;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Log.v(TAG, "Requested " + desiredChannels + " channels, getting stereo");
|
|
||||||
desiredChannels = 2;
|
|
||||||
channelConfig = AudioFormat.CHANNEL_IN_STEREO;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
switch (desiredChannels) {
|
|
||||||
case 1:
|
|
||||||
channelConfig = AudioFormat.CHANNEL_OUT_MONO;
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
channelConfig = AudioFormat.CHANNEL_OUT_STEREO | AudioFormat.CHANNEL_OUT_FRONT_CENTER;
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
channelConfig = AudioFormat.CHANNEL_OUT_QUAD;
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
channelConfig = AudioFormat.CHANNEL_OUT_QUAD | AudioFormat.CHANNEL_OUT_FRONT_CENTER;
|
|
||||||
break;
|
|
||||||
case 6:
|
|
||||||
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;
|
|
||||||
break;
|
|
||||||
case 7:
|
|
||||||
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1 | AudioFormat.CHANNEL_OUT_BACK_CENTER;
|
|
||||||
break;
|
|
||||||
case 8:
|
|
||||||
if (Build.VERSION.SDK_INT >= 23 /* Android 6.0 (M) */) {
|
|
||||||
channelConfig = AudioFormat.CHANNEL_OUT_7POINT1_SURROUND;
|
|
||||||
} else {
|
|
||||||
Log.v(TAG, "Requested " + desiredChannels + " channels, getting 5.1 surround");
|
|
||||||
desiredChannels = 6;
|
|
||||||
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Log.v(TAG, "Requested " + desiredChannels + " channels, getting stereo");
|
|
||||||
desiredChannels = 2;
|
|
||||||
channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Log.v(TAG, "Speaker configuration (and order of channels):");
|
|
||||||
|
|
||||||
if ((channelConfig & 0x00000004) != 0) {
|
|
||||||
Log.v(TAG, " CHANNEL_OUT_FRONT_LEFT");
|
|
||||||
}
|
|
||||||
if ((channelConfig & 0x00000008) != 0) {
|
|
||||||
Log.v(TAG, " CHANNEL_OUT_FRONT_RIGHT");
|
|
||||||
}
|
|
||||||
if ((channelConfig & 0x00000010) != 0) {
|
|
||||||
Log.v(TAG, " CHANNEL_OUT_FRONT_CENTER");
|
|
||||||
}
|
|
||||||
if ((channelConfig & 0x00000020) != 0) {
|
|
||||||
Log.v(TAG, " CHANNEL_OUT_LOW_FREQUENCY");
|
|
||||||
}
|
|
||||||
if ((channelConfig & 0x00000040) != 0) {
|
|
||||||
Log.v(TAG, " CHANNEL_OUT_BACK_LEFT");
|
|
||||||
}
|
|
||||||
if ((channelConfig & 0x00000080) != 0) {
|
|
||||||
Log.v(TAG, " CHANNEL_OUT_BACK_RIGHT");
|
|
||||||
}
|
|
||||||
if ((channelConfig & 0x00000100) != 0) {
|
|
||||||
Log.v(TAG, " CHANNEL_OUT_FRONT_LEFT_OF_CENTER");
|
|
||||||
}
|
|
||||||
if ((channelConfig & 0x00000200) != 0) {
|
|
||||||
Log.v(TAG, " CHANNEL_OUT_FRONT_RIGHT_OF_CENTER");
|
|
||||||
}
|
|
||||||
if ((channelConfig & 0x00000400) != 0) {
|
|
||||||
Log.v(TAG, " CHANNEL_OUT_BACK_CENTER");
|
|
||||||
}
|
|
||||||
if ((channelConfig & 0x00000800) != 0) {
|
|
||||||
Log.v(TAG, " CHANNEL_OUT_SIDE_LEFT");
|
|
||||||
}
|
|
||||||
if ((channelConfig & 0x00001000) != 0) {
|
|
||||||
Log.v(TAG, " CHANNEL_OUT_SIDE_RIGHT");
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
frameSize = (sampleSize * desiredChannels);
|
|
||||||
|
|
||||||
// Let the user pick a larger buffer if they really want -- but ye
|
|
||||||
// gods they probably shouldn't, the minimums are horrifyingly high
|
|
||||||
// latency already
|
|
||||||
int minBufferSize;
|
|
||||||
if (isCapture) {
|
|
||||||
minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);
|
|
||||||
} else {
|
|
||||||
minBufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat);
|
|
||||||
}
|
|
||||||
desiredFrames = Math.max(desiredFrames, (minBufferSize + frameSize - 1) / frameSize);
|
|
||||||
|
|
||||||
int[] results = new int[4];
|
|
||||||
|
|
||||||
if (isCapture) {
|
|
||||||
if (mAudioRecord == null) {
|
|
||||||
mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, sampleRate,
|
|
||||||
channelConfig, audioFormat, desiredFrames * frameSize);
|
|
||||||
|
|
||||||
// see notes about AudioTrack state in audioOpen(), above. Probably also applies here.
|
|
||||||
if (mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
|
|
||||||
Log.e(TAG, "Failed during initialization of AudioRecord");
|
|
||||||
mAudioRecord.release();
|
|
||||||
mAudioRecord = null;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */ && deviceId != 0) {
|
|
||||||
mAudioRecord.setPreferredDevice(getOutputAudioDeviceInfo(deviceId));
|
|
||||||
}
|
|
||||||
|
|
||||||
mAudioRecord.startRecording();
|
|
||||||
}
|
|
||||||
|
|
||||||
results[0] = mAudioRecord.getSampleRate();
|
|
||||||
results[1] = mAudioRecord.getAudioFormat();
|
|
||||||
results[2] = mAudioRecord.getChannelCount();
|
|
||||||
|
|
||||||
} else {
|
|
||||||
if (mAudioTrack == null) {
|
|
||||||
mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, channelConfig, audioFormat, desiredFrames * frameSize, AudioTrack.MODE_STREAM);
|
|
||||||
|
|
||||||
// Instantiating AudioTrack can "succeed" without an exception and the track may still be invalid
|
|
||||||
// Ref: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/media/java/android/media/AudioTrack.java
|
|
||||||
// Ref: http://developer.android.com/reference/android/media/AudioTrack.html#getState()
|
|
||||||
if (mAudioTrack.getState() != AudioTrack.STATE_INITIALIZED) {
|
|
||||||
/* Try again, with safer values */
|
|
||||||
|
|
||||||
Log.e(TAG, "Failed during initialization of Audio Track");
|
|
||||||
mAudioTrack.release();
|
|
||||||
mAudioTrack = null;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */ && deviceId != 0) {
|
|
||||||
mAudioTrack.setPreferredDevice(getInputAudioDeviceInfo(deviceId));
|
|
||||||
}
|
|
||||||
|
|
||||||
mAudioTrack.play();
|
|
||||||
}
|
|
||||||
|
|
||||||
results[0] = mAudioTrack.getSampleRate();
|
|
||||||
results[1] = mAudioTrack.getAudioFormat();
|
|
||||||
results[2] = mAudioTrack.getChannelCount();
|
|
||||||
}
|
|
||||||
results[3] = desiredFrames;
|
|
||||||
|
|
||||||
Log.v(TAG, "Opening " + (isCapture ? "capture" : "playback") + ", got " + results[3] + " frames of " + results[2] + " channel " + getAudioFormatString(results[1]) + " audio at " + results[0] + " Hz");
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static AudioDeviceInfo getInputAudioDeviceInfo(int deviceId) {
|
|
||||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
|
||||||
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
|
||||||
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS))
|
|
||||||
.filter(deviceInfo -> deviceInfo.getId() == deviceId)
|
|
||||||
.findFirst()
|
|
||||||
.orElse(null);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static AudioDeviceInfo getOutputAudioDeviceInfo(int deviceId) {
|
|
||||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
|
||||||
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
|
||||||
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS))
|
|
||||||
.filter(deviceInfo -> deviceInfo.getId() == deviceId)
|
|
||||||
.findFirst()
|
|
||||||
.orElse(null);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void registerAudioDeviceCallback() {
|
|
||||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
|
||||||
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
|
||||||
audioManager.registerAudioDeviceCallback(mAudioDeviceCallback, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void unregisterAudioDeviceCallback(Context context) {
|
|
||||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
|
||||||
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
|
||||||
audioManager.unregisterAudioDeviceCallback(mAudioDeviceCallback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is called by SDL using JNI.
|
|
||||||
*/
|
|
||||||
public static int[] getAudioOutputDevices() {
|
|
||||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
|
||||||
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
|
||||||
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)).mapToInt(AudioDeviceInfo::getId).toArray();
|
|
||||||
} else {
|
|
||||||
return NO_DEVICES;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is called by SDL using JNI.
|
|
||||||
*/
|
|
||||||
public static int[] getAudioInputDevices() {
|
|
||||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
|
||||||
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
|
||||||
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).mapToInt(AudioDeviceInfo::getId).toArray();
|
|
||||||
} else {
|
|
||||||
return NO_DEVICES;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is called by SDL using JNI.
|
|
||||||
*/
|
|
||||||
public static int[] audioOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {
|
|
||||||
return open(false, sampleRate, audioFormat, desiredChannels, desiredFrames, deviceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is called by SDL using JNI.
|
|
||||||
*/
|
|
||||||
public static void audioWriteFloatBuffer(float[] buffer) {
|
|
||||||
if (mAudioTrack == null) {
|
|
||||||
Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (android.os.Build.VERSION.SDK_INT < 21 /* Android 5.0 (LOLLIPOP) */) {
|
|
||||||
Log.e(TAG, "Attempted to make an incompatible audio call with uninitialized audio! (floating-point output is supported since Android 5.0 Lollipop)");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < buffer.length;) {
|
|
||||||
int result = mAudioTrack.write(buffer, i, buffer.length - i, AudioTrack.WRITE_BLOCKING);
|
|
||||||
if (result > 0) {
|
|
||||||
i += result;
|
|
||||||
} else if (result == 0) {
|
|
||||||
try {
|
|
||||||
Thread.sleep(1);
|
|
||||||
} catch(InterruptedException e) {
|
|
||||||
// Nom nom
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Log.w(TAG, "SDL audio: error return from write(float)");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is called by SDL using JNI.
|
|
||||||
*/
|
|
||||||
public static void audioWriteShortBuffer(short[] buffer) {
|
|
||||||
if (mAudioTrack == null) {
|
|
||||||
Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < buffer.length;) {
|
|
||||||
int result = mAudioTrack.write(buffer, i, buffer.length - i);
|
|
||||||
if (result > 0) {
|
|
||||||
i += result;
|
|
||||||
} else if (result == 0) {
|
|
||||||
try {
|
|
||||||
Thread.sleep(1);
|
|
||||||
} catch(InterruptedException e) {
|
|
||||||
// Nom nom
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Log.w(TAG, "SDL audio: error return from write(short)");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is called by SDL using JNI.
|
|
||||||
*/
|
|
||||||
public static void audioWriteByteBuffer(byte[] buffer) {
|
|
||||||
if (mAudioTrack == null) {
|
|
||||||
Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < buffer.length; ) {
|
|
||||||
int result = mAudioTrack.write(buffer, i, buffer.length - i);
|
|
||||||
if (result > 0) {
|
|
||||||
i += result;
|
|
||||||
} else if (result == 0) {
|
|
||||||
try {
|
|
||||||
Thread.sleep(1);
|
|
||||||
} catch(InterruptedException e) {
|
|
||||||
// Nom nom
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Log.w(TAG, "SDL audio: error return from write(byte)");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is called by SDL using JNI.
|
|
||||||
*/
|
|
||||||
public static int[] captureOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {
|
|
||||||
return open(true, sampleRate, audioFormat, desiredChannels, desiredFrames, deviceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** This method is called by SDL using JNI. */
|
|
||||||
public static int captureReadFloatBuffer(float[] buffer, boolean blocking) {
|
|
||||||
if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** This method is called by SDL using JNI. */
|
|
||||||
public static int captureReadShortBuffer(short[] buffer, boolean blocking) {
|
|
||||||
if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {
|
|
||||||
return mAudioRecord.read(buffer, 0, buffer.length);
|
|
||||||
} else {
|
|
||||||
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** This method is called by SDL using JNI. */
|
|
||||||
public static int captureReadByteBuffer(byte[] buffer, boolean blocking) {
|
|
||||||
if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {
|
|
||||||
return mAudioRecord.read(buffer, 0, buffer.length);
|
|
||||||
} else {
|
|
||||||
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** This method is called by SDL using JNI. */
|
|
||||||
public static void audioClose() {
|
|
||||||
if (mAudioTrack != null) {
|
|
||||||
mAudioTrack.stop();
|
|
||||||
mAudioTrack.release();
|
|
||||||
mAudioTrack = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** This method is called by SDL using JNI. */
|
|
||||||
public static void captureClose() {
|
|
||||||
if (mAudioRecord != null) {
|
|
||||||
mAudioRecord.stop();
|
|
||||||
mAudioRecord.release();
|
|
||||||
mAudioRecord = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** This method is called by SDL using JNI. */
|
|
||||||
public static void audioSetThreadPriority(boolean iscapture, int device_id) {
|
|
||||||
try {
|
|
||||||
|
|
||||||
/* Set thread name */
|
|
||||||
if (iscapture) {
|
|
||||||
Thread.currentThread().setName("SDLAudioC" + device_id);
|
|
||||||
} else {
|
|
||||||
Thread.currentThread().setName("SDLAudioP" + device_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set thread priority */
|
|
||||||
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO);
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.v(TAG, "modify thread properties failed " + e.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static native int nativeSetupJNI();
|
|
||||||
|
|
||||||
public static native void removeAudioDevice(boolean isCapture, int deviceId);
|
|
||||||
|
|
||||||
public static native void addAudioDevice(boolean isCapture, int deviceId);
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,856 +0,0 @@
|
|||||||
package org.libsdl.app;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.VibrationEffect;
|
|
||||||
import android.os.Vibrator;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.InputDevice;
|
|
||||||
import android.view.KeyEvent;
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
|
|
||||||
public class SDLControllerManager
|
|
||||||
{
|
|
||||||
|
|
||||||
public static native int nativeSetupJNI();
|
|
||||||
|
|
||||||
public static native int nativeAddJoystick(int device_id, String name, String desc,
|
|
||||||
int vendor_id, int product_id,
|
|
||||||
boolean is_accelerometer, int button_mask,
|
|
||||||
int naxes, int axis_mask, int nhats, int nballs);
|
|
||||||
public static native int nativeRemoveJoystick(int device_id);
|
|
||||||
public static native int nativeAddHaptic(int device_id, String name);
|
|
||||||
public static native int nativeRemoveHaptic(int device_id);
|
|
||||||
public static native int onNativePadDown(int device_id, int keycode);
|
|
||||||
public static native int onNativePadUp(int device_id, int keycode);
|
|
||||||
public static native void onNativeJoy(int device_id, int axis,
|
|
||||||
float value);
|
|
||||||
public static native void onNativeHat(int device_id, int hat_id,
|
|
||||||
int x, int y);
|
|
||||||
|
|
||||||
protected static SDLJoystickHandler mJoystickHandler;
|
|
||||||
protected static SDLHapticHandler mHapticHandler;
|
|
||||||
|
|
||||||
private static final String TAG = "SDLControllerManager";
|
|
||||||
|
|
||||||
public static void initialize() {
|
|
||||||
if (mJoystickHandler == null) {
|
|
||||||
if (Build.VERSION.SDK_INT >= 19 /* Android 4.4 (KITKAT) */) {
|
|
||||||
mJoystickHandler = new SDLJoystickHandler_API19();
|
|
||||||
} else {
|
|
||||||
mJoystickHandler = new SDLJoystickHandler_API16();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mHapticHandler == null) {
|
|
||||||
if (Build.VERSION.SDK_INT >= 26 /* Android 8.0 (O) */) {
|
|
||||||
mHapticHandler = new SDLHapticHandler_API26();
|
|
||||||
} else {
|
|
||||||
mHapticHandler = new SDLHapticHandler();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Joystick glue code, just a series of stubs that redirect to the SDLJoystickHandler instance
|
|
||||||
public static boolean handleJoystickMotionEvent(MotionEvent event) {
|
|
||||||
return mJoystickHandler.handleMotionEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is called by SDL using JNI.
|
|
||||||
*/
|
|
||||||
public static void pollInputDevices() {
|
|
||||||
mJoystickHandler.pollInputDevices();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is called by SDL using JNI.
|
|
||||||
*/
|
|
||||||
public static void pollHapticDevices() {
|
|
||||||
mHapticHandler.pollHapticDevices();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is called by SDL using JNI.
|
|
||||||
*/
|
|
||||||
public static void hapticRun(int device_id, float intensity, int length) {
|
|
||||||
mHapticHandler.run(device_id, intensity, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is called by SDL using JNI.
|
|
||||||
*/
|
|
||||||
public static void hapticStop(int device_id)
|
|
||||||
{
|
|
||||||
mHapticHandler.stop(device_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if a given device is considered a possible SDL joystick
|
|
||||||
public static boolean isDeviceSDLJoystick(int deviceId) {
|
|
||||||
InputDevice device = InputDevice.getDevice(deviceId);
|
|
||||||
// We cannot use InputDevice.isVirtual before API 16, so let's accept
|
|
||||||
// only nonnegative device ids (VIRTUAL_KEYBOARD equals -1)
|
|
||||||
if ((device == null) || (deviceId < 0)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
int sources = device.getSources();
|
|
||||||
|
|
||||||
/* This is called for every button press, so let's not spam the logs */
|
|
||||||
/*
|
|
||||||
if ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
|
|
||||||
Log.v(TAG, "Input device " + device.getName() + " has class joystick.");
|
|
||||||
}
|
|
||||||
if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) {
|
|
||||||
Log.v(TAG, "Input device " + device.getName() + " is a dpad.");
|
|
||||||
}
|
|
||||||
if ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) {
|
|
||||||
Log.v(TAG, "Input device " + device.getName() + " is a gamepad.");
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
return ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) != 0 ||
|
|
||||||
((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) ||
|
|
||||||
((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class SDLJoystickHandler {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles given MotionEvent.
|
|
||||||
* @param event the event to be handled.
|
|
||||||
* @return if given event was processed.
|
|
||||||
*/
|
|
||||||
public boolean handleMotionEvent(MotionEvent event) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles adding and removing of input devices.
|
|
||||||
*/
|
|
||||||
public void pollInputDevices() {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Actual joystick functionality available for API >= 12 devices */
|
|
||||||
class SDLJoystickHandler_API16 extends SDLJoystickHandler {
|
|
||||||
|
|
||||||
static class SDLJoystick {
|
|
||||||
public int device_id;
|
|
||||||
public String name;
|
|
||||||
public String desc;
|
|
||||||
public ArrayList<InputDevice.MotionRange> axes;
|
|
||||||
public ArrayList<InputDevice.MotionRange> hats;
|
|
||||||
}
|
|
||||||
static class RangeComparator implements Comparator<InputDevice.MotionRange> {
|
|
||||||
@Override
|
|
||||||
public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) {
|
|
||||||
// Some controllers, like the Moga Pro 2, return AXIS_GAS (22) for right trigger and AXIS_BRAKE (23) for left trigger - swap them so they're sorted in the right order for SDL
|
|
||||||
int arg0Axis = arg0.getAxis();
|
|
||||||
int arg1Axis = arg1.getAxis();
|
|
||||||
if (arg0Axis == MotionEvent.AXIS_GAS) {
|
|
||||||
arg0Axis = MotionEvent.AXIS_BRAKE;
|
|
||||||
} else if (arg0Axis == MotionEvent.AXIS_BRAKE) {
|
|
||||||
arg0Axis = MotionEvent.AXIS_GAS;
|
|
||||||
}
|
|
||||||
if (arg1Axis == MotionEvent.AXIS_GAS) {
|
|
||||||
arg1Axis = MotionEvent.AXIS_BRAKE;
|
|
||||||
} else if (arg1Axis == MotionEvent.AXIS_BRAKE) {
|
|
||||||
arg1Axis = MotionEvent.AXIS_GAS;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure the AXIS_Z is sorted between AXIS_RY and AXIS_RZ.
|
|
||||||
// This is because the usual pairing are:
|
|
||||||
// - AXIS_X + AXIS_Y (left stick).
|
|
||||||
// - AXIS_RX, AXIS_RY (sometimes the right stick, sometimes triggers).
|
|
||||||
// - AXIS_Z, AXIS_RZ (sometimes the right stick, sometimes triggers).
|
|
||||||
// This sorts the axes in the above order, which tends to be correct
|
|
||||||
// for Xbox-ish game pads that have the right stick on RX/RY and the
|
|
||||||
// triggers on Z/RZ.
|
|
||||||
//
|
|
||||||
// Gamepads that don't have AXIS_Z/AXIS_RZ but use
|
|
||||||
// AXIS_LTRIGGER/AXIS_RTRIGGER are unaffected by this.
|
|
||||||
//
|
|
||||||
// References:
|
|
||||||
// - https://developer.android.com/develop/ui/views/touch-and-input/game-controllers/controller-input
|
|
||||||
// - https://www.kernel.org/doc/html/latest/input/gamepad.html
|
|
||||||
if (arg0Axis == MotionEvent.AXIS_Z) {
|
|
||||||
arg0Axis = MotionEvent.AXIS_RZ - 1;
|
|
||||||
} else if (arg0Axis > MotionEvent.AXIS_Z && arg0Axis < MotionEvent.AXIS_RZ) {
|
|
||||||
--arg0Axis;
|
|
||||||
}
|
|
||||||
if (arg1Axis == MotionEvent.AXIS_Z) {
|
|
||||||
arg1Axis = MotionEvent.AXIS_RZ - 1;
|
|
||||||
} else if (arg1Axis > MotionEvent.AXIS_Z && arg1Axis < MotionEvent.AXIS_RZ) {
|
|
||||||
--arg1Axis;
|
|
||||||
}
|
|
||||||
|
|
||||||
return arg0Axis - arg1Axis;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final ArrayList<SDLJoystick> mJoysticks;
|
|
||||||
|
|
||||||
public SDLJoystickHandler_API16() {
|
|
||||||
|
|
||||||
mJoysticks = new ArrayList<SDLJoystick>();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void pollInputDevices() {
|
|
||||||
int[] deviceIds = InputDevice.getDeviceIds();
|
|
||||||
|
|
||||||
for (int device_id : deviceIds) {
|
|
||||||
if (SDLControllerManager.isDeviceSDLJoystick(device_id)) {
|
|
||||||
SDLJoystick joystick = getJoystick(device_id);
|
|
||||||
if (joystick == null) {
|
|
||||||
InputDevice joystickDevice = InputDevice.getDevice(device_id);
|
|
||||||
joystick = new SDLJoystick();
|
|
||||||
joystick.device_id = device_id;
|
|
||||||
joystick.name = joystickDevice.getName();
|
|
||||||
joystick.desc = getJoystickDescriptor(joystickDevice);
|
|
||||||
joystick.axes = new ArrayList<InputDevice.MotionRange>();
|
|
||||||
joystick.hats = new ArrayList<InputDevice.MotionRange>();
|
|
||||||
|
|
||||||
List<InputDevice.MotionRange> ranges = joystickDevice.getMotionRanges();
|
|
||||||
Collections.sort(ranges, new RangeComparator());
|
|
||||||
for (InputDevice.MotionRange range : ranges) {
|
|
||||||
if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
|
|
||||||
if (range.getAxis() == MotionEvent.AXIS_HAT_X || range.getAxis() == MotionEvent.AXIS_HAT_Y) {
|
|
||||||
joystick.hats.add(range);
|
|
||||||
} else {
|
|
||||||
joystick.axes.add(range);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mJoysticks.add(joystick);
|
|
||||||
SDLControllerManager.nativeAddJoystick(joystick.device_id, joystick.name, joystick.desc,
|
|
||||||
getVendorId(joystickDevice), getProductId(joystickDevice), false,
|
|
||||||
getButtonMask(joystickDevice), joystick.axes.size(), getAxisMask(joystick.axes), joystick.hats.size()/2, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Check removed devices */
|
|
||||||
ArrayList<Integer> removedDevices = null;
|
|
||||||
for (SDLJoystick joystick : mJoysticks) {
|
|
||||||
int device_id = joystick.device_id;
|
|
||||||
int i;
|
|
||||||
for (i = 0; i < deviceIds.length; i++) {
|
|
||||||
if (device_id == deviceIds[i]) break;
|
|
||||||
}
|
|
||||||
if (i == deviceIds.length) {
|
|
||||||
if (removedDevices == null) {
|
|
||||||
removedDevices = new ArrayList<Integer>();
|
|
||||||
}
|
|
||||||
removedDevices.add(device_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (removedDevices != null) {
|
|
||||||
for (int device_id : removedDevices) {
|
|
||||||
SDLControllerManager.nativeRemoveJoystick(device_id);
|
|
||||||
for (int i = 0; i < mJoysticks.size(); i++) {
|
|
||||||
if (mJoysticks.get(i).device_id == device_id) {
|
|
||||||
mJoysticks.remove(i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected SDLJoystick getJoystick(int device_id) {
|
|
||||||
for (SDLJoystick joystick : mJoysticks) {
|
|
||||||
if (joystick.device_id == device_id) {
|
|
||||||
return joystick;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean handleMotionEvent(MotionEvent event) {
|
|
||||||
int actionPointerIndex = event.getActionIndex();
|
|
||||||
int action = event.getActionMasked();
|
|
||||||
if (action == MotionEvent.ACTION_MOVE) {
|
|
||||||
SDLJoystick joystick = getJoystick(event.getDeviceId());
|
|
||||||
if (joystick != null) {
|
|
||||||
for (int i = 0; i < joystick.axes.size(); i++) {
|
|
||||||
InputDevice.MotionRange range = joystick.axes.get(i);
|
|
||||||
/* Normalize the value to -1...1 */
|
|
||||||
float value = (event.getAxisValue(range.getAxis(), actionPointerIndex) - range.getMin()) / range.getRange() * 2.0f - 1.0f;
|
|
||||||
SDLControllerManager.onNativeJoy(joystick.device_id, i, value);
|
|
||||||
}
|
|
||||||
for (int i = 0; i < joystick.hats.size() / 2; i++) {
|
|
||||||
int hatX = Math.round(event.getAxisValue(joystick.hats.get(2 * i).getAxis(), actionPointerIndex));
|
|
||||||
int hatY = Math.round(event.getAxisValue(joystick.hats.get(2 * i + 1).getAxis(), actionPointerIndex));
|
|
||||||
SDLControllerManager.onNativeHat(joystick.device_id, i, hatX, hatY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getJoystickDescriptor(InputDevice joystickDevice) {
|
|
||||||
String desc = joystickDevice.getDescriptor();
|
|
||||||
|
|
||||||
if (desc != null && !desc.isEmpty()) {
|
|
||||||
return desc;
|
|
||||||
}
|
|
||||||
|
|
||||||
return joystickDevice.getName();
|
|
||||||
}
|
|
||||||
public int getProductId(InputDevice joystickDevice) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
public int getVendorId(InputDevice joystickDevice) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
public int getAxisMask(List<InputDevice.MotionRange> ranges) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
public int getButtonMask(InputDevice joystickDevice) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SDLJoystickHandler_API19 extends SDLJoystickHandler_API16 {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getProductId(InputDevice joystickDevice) {
|
|
||||||
return joystickDevice.getProductId();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getVendorId(InputDevice joystickDevice) {
|
|
||||||
return joystickDevice.getVendorId();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getAxisMask(List<InputDevice.MotionRange> ranges) {
|
|
||||||
// For compatibility, keep computing the axis mask like before,
|
|
||||||
// only really distinguishing 2, 4 and 6 axes.
|
|
||||||
int axis_mask = 0;
|
|
||||||
if (ranges.size() >= 2) {
|
|
||||||
// ((1 << SDL_GAMEPAD_AXIS_LEFTX) | (1 << SDL_GAMEPAD_AXIS_LEFTY))
|
|
||||||
axis_mask |= 0x0003;
|
|
||||||
}
|
|
||||||
if (ranges.size() >= 4) {
|
|
||||||
// ((1 << SDL_GAMEPAD_AXIS_RIGHTX) | (1 << SDL_GAMEPAD_AXIS_RIGHTY))
|
|
||||||
axis_mask |= 0x000c;
|
|
||||||
}
|
|
||||||
if (ranges.size() >= 6) {
|
|
||||||
// ((1 << SDL_GAMEPAD_AXIS_LEFT_TRIGGER) | (1 << SDL_GAMEPAD_AXIS_RIGHT_TRIGGER))
|
|
||||||
axis_mask |= 0x0030;
|
|
||||||
}
|
|
||||||
// Also add an indicator bit for whether the sorting order has changed.
|
|
||||||
// This serves to disable outdated gamecontrollerdb.txt mappings.
|
|
||||||
boolean have_z = false;
|
|
||||||
boolean have_past_z_before_rz = false;
|
|
||||||
for (InputDevice.MotionRange range : ranges) {
|
|
||||||
int axis = range.getAxis();
|
|
||||||
if (axis == MotionEvent.AXIS_Z) {
|
|
||||||
have_z = true;
|
|
||||||
} else if (axis > MotionEvent.AXIS_Z && axis < MotionEvent.AXIS_RZ) {
|
|
||||||
have_past_z_before_rz = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (have_z && have_past_z_before_rz) {
|
|
||||||
// If both these exist, the compare() function changed sorting order.
|
|
||||||
// Set a bit to indicate this fact.
|
|
||||||
axis_mask |= 0x8000;
|
|
||||||
}
|
|
||||||
return axis_mask;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getButtonMask(InputDevice joystickDevice) {
|
|
||||||
int button_mask = 0;
|
|
||||||
int[] keys = new int[] {
|
|
||||||
KeyEvent.KEYCODE_BUTTON_A,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_B,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_X,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_Y,
|
|
||||||
KeyEvent.KEYCODE_BACK,
|
|
||||||
KeyEvent.KEYCODE_MENU,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_MODE,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_START,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_THUMBL,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_THUMBR,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_L1,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_R1,
|
|
||||||
KeyEvent.KEYCODE_DPAD_UP,
|
|
||||||
KeyEvent.KEYCODE_DPAD_DOWN,
|
|
||||||
KeyEvent.KEYCODE_DPAD_LEFT,
|
|
||||||
KeyEvent.KEYCODE_DPAD_RIGHT,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_SELECT,
|
|
||||||
KeyEvent.KEYCODE_DPAD_CENTER,
|
|
||||||
|
|
||||||
// These don't map into any SDL controller buttons directly
|
|
||||||
KeyEvent.KEYCODE_BUTTON_L2,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_R2,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_C,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_Z,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_1,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_2,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_3,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_4,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_5,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_6,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_7,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_8,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_9,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_10,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_11,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_12,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_13,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_14,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_15,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_16,
|
|
||||||
};
|
|
||||||
int[] masks = new int[] {
|
|
||||||
(1 << 0), // A -> A
|
|
||||||
(1 << 1), // B -> B
|
|
||||||
(1 << 2), // X -> X
|
|
||||||
(1 << 3), // Y -> Y
|
|
||||||
(1 << 4), // BACK -> BACK
|
|
||||||
(1 << 6), // MENU -> START
|
|
||||||
(1 << 5), // MODE -> GUIDE
|
|
||||||
(1 << 6), // START -> START
|
|
||||||
(1 << 7), // THUMBL -> LEFTSTICK
|
|
||||||
(1 << 8), // THUMBR -> RIGHTSTICK
|
|
||||||
(1 << 9), // L1 -> LEFTSHOULDER
|
|
||||||
(1 << 10), // R1 -> RIGHTSHOULDER
|
|
||||||
(1 << 11), // DPAD_UP -> DPAD_UP
|
|
||||||
(1 << 12), // DPAD_DOWN -> DPAD_DOWN
|
|
||||||
(1 << 13), // DPAD_LEFT -> DPAD_LEFT
|
|
||||||
(1 << 14), // DPAD_RIGHT -> DPAD_RIGHT
|
|
||||||
(1 << 4), // SELECT -> BACK
|
|
||||||
(1 << 0), // DPAD_CENTER -> A
|
|
||||||
(1 << 15), // L2 -> ??
|
|
||||||
(1 << 16), // R2 -> ??
|
|
||||||
(1 << 17), // C -> ??
|
|
||||||
(1 << 18), // Z -> ??
|
|
||||||
(1 << 20), // 1 -> ??
|
|
||||||
(1 << 21), // 2 -> ??
|
|
||||||
(1 << 22), // 3 -> ??
|
|
||||||
(1 << 23), // 4 -> ??
|
|
||||||
(1 << 24), // 5 -> ??
|
|
||||||
(1 << 25), // 6 -> ??
|
|
||||||
(1 << 26), // 7 -> ??
|
|
||||||
(1 << 27), // 8 -> ??
|
|
||||||
(1 << 28), // 9 -> ??
|
|
||||||
(1 << 29), // 10 -> ??
|
|
||||||
(1 << 30), // 11 -> ??
|
|
||||||
(1 << 31), // 12 -> ??
|
|
||||||
// We're out of room...
|
|
||||||
0xFFFFFFFF, // 13 -> ??
|
|
||||||
0xFFFFFFFF, // 14 -> ??
|
|
||||||
0xFFFFFFFF, // 15 -> ??
|
|
||||||
0xFFFFFFFF, // 16 -> ??
|
|
||||||
};
|
|
||||||
boolean[] has_keys = joystickDevice.hasKeys(keys);
|
|
||||||
for (int i = 0; i < keys.length; ++i) {
|
|
||||||
if (has_keys[i]) {
|
|
||||||
button_mask |= masks[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return button_mask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SDLHapticHandler_API26 extends SDLHapticHandler {
|
|
||||||
@Override
|
|
||||||
public void run(int device_id, float intensity, int length) {
|
|
||||||
SDLHaptic haptic = getHaptic(device_id);
|
|
||||||
if (haptic != null) {
|
|
||||||
Log.d("SDL", "Rtest: Vibe with intensity " + intensity + " for " + length);
|
|
||||||
if (intensity == 0.0f) {
|
|
||||||
stop(device_id);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int vibeValue = Math.round(intensity * 255);
|
|
||||||
|
|
||||||
if (vibeValue > 255) {
|
|
||||||
vibeValue = 255;
|
|
||||||
}
|
|
||||||
if (vibeValue < 1) {
|
|
||||||
stop(device_id);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
haptic.vib.vibrate(VibrationEffect.createOneShot(length, vibeValue));
|
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
// Fall back to the generic method, which uses DEFAULT_AMPLITUDE, but works even if
|
|
||||||
// something went horribly wrong with the Android 8.0 APIs.
|
|
||||||
haptic.vib.vibrate(length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SDLHapticHandler {
|
|
||||||
|
|
||||||
static class SDLHaptic {
|
|
||||||
public int device_id;
|
|
||||||
public String name;
|
|
||||||
public Vibrator vib;
|
|
||||||
}
|
|
||||||
|
|
||||||
private final ArrayList<SDLHaptic> mHaptics;
|
|
||||||
|
|
||||||
public SDLHapticHandler() {
|
|
||||||
mHaptics = new ArrayList<SDLHaptic>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void run(int device_id, float intensity, int length) {
|
|
||||||
SDLHaptic haptic = getHaptic(device_id);
|
|
||||||
if (haptic != null) {
|
|
||||||
haptic.vib.vibrate(length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stop(int device_id) {
|
|
||||||
SDLHaptic haptic = getHaptic(device_id);
|
|
||||||
if (haptic != null) {
|
|
||||||
haptic.vib.cancel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void pollHapticDevices() {
|
|
||||||
|
|
||||||
final int deviceId_VIBRATOR_SERVICE = 999999;
|
|
||||||
boolean hasVibratorService = false;
|
|
||||||
|
|
||||||
int[] deviceIds = InputDevice.getDeviceIds();
|
|
||||||
// It helps processing the device ids in reverse order
|
|
||||||
// For example, in the case of the XBox 360 wireless dongle,
|
|
||||||
// so the first controller seen by SDL matches what the receiver
|
|
||||||
// considers to be the first controller
|
|
||||||
|
|
||||||
for (int i = deviceIds.length - 1; i > -1; i--) {
|
|
||||||
SDLHaptic haptic = getHaptic(deviceIds[i]);
|
|
||||||
if (haptic == null) {
|
|
||||||
InputDevice device = InputDevice.getDevice(deviceIds[i]);
|
|
||||||
Vibrator vib = device.getVibrator();
|
|
||||||
if (vib != null) {
|
|
||||||
if (vib.hasVibrator()) {
|
|
||||||
haptic = new SDLHaptic();
|
|
||||||
haptic.device_id = deviceIds[i];
|
|
||||||
haptic.name = device.getName();
|
|
||||||
haptic.vib = vib;
|
|
||||||
mHaptics.add(haptic);
|
|
||||||
SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Check VIBRATOR_SERVICE */
|
|
||||||
Vibrator vib = (Vibrator) SDL.getContext().getSystemService(Context.VIBRATOR_SERVICE);
|
|
||||||
if (vib != null) {
|
|
||||||
hasVibratorService = vib.hasVibrator();
|
|
||||||
|
|
||||||
if (hasVibratorService) {
|
|
||||||
SDLHaptic haptic = getHaptic(deviceId_VIBRATOR_SERVICE);
|
|
||||||
if (haptic == null) {
|
|
||||||
haptic = new SDLHaptic();
|
|
||||||
haptic.device_id = deviceId_VIBRATOR_SERVICE;
|
|
||||||
haptic.name = "VIBRATOR_SERVICE";
|
|
||||||
haptic.vib = vib;
|
|
||||||
mHaptics.add(haptic);
|
|
||||||
SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Check removed devices */
|
|
||||||
ArrayList<Integer> removedDevices = null;
|
|
||||||
for (SDLHaptic haptic : mHaptics) {
|
|
||||||
int device_id = haptic.device_id;
|
|
||||||
int i;
|
|
||||||
for (i = 0; i < deviceIds.length; i++) {
|
|
||||||
if (device_id == deviceIds[i]) break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (device_id != deviceId_VIBRATOR_SERVICE || !hasVibratorService) {
|
|
||||||
if (i == deviceIds.length) {
|
|
||||||
if (removedDevices == null) {
|
|
||||||
removedDevices = new ArrayList<Integer>();
|
|
||||||
}
|
|
||||||
removedDevices.add(device_id);
|
|
||||||
}
|
|
||||||
} // else: don't remove the vibrator if it is still present
|
|
||||||
}
|
|
||||||
|
|
||||||
if (removedDevices != null) {
|
|
||||||
for (int device_id : removedDevices) {
|
|
||||||
SDLControllerManager.nativeRemoveHaptic(device_id);
|
|
||||||
for (int i = 0; i < mHaptics.size(); i++) {
|
|
||||||
if (mHaptics.get(i).device_id == device_id) {
|
|
||||||
mHaptics.remove(i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected SDLHaptic getHaptic(int device_id) {
|
|
||||||
for (SDLHaptic haptic : mHaptics) {
|
|
||||||
if (haptic.device_id == device_id) {
|
|
||||||
return haptic;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SDLGenericMotionListener_API12 implements View.OnGenericMotionListener {
|
|
||||||
// Generic Motion (mouse hover, joystick...) events go here
|
|
||||||
@Override
|
|
||||||
public boolean onGenericMotion(View v, MotionEvent event) {
|
|
||||||
float x, y;
|
|
||||||
int action;
|
|
||||||
|
|
||||||
switch ( event.getSource() ) {
|
|
||||||
case InputDevice.SOURCE_JOYSTICK:
|
|
||||||
return SDLControllerManager.handleJoystickMotionEvent(event);
|
|
||||||
|
|
||||||
case InputDevice.SOURCE_MOUSE:
|
|
||||||
action = event.getActionMasked();
|
|
||||||
switch (action) {
|
|
||||||
case MotionEvent.ACTION_SCROLL:
|
|
||||||
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
|
|
||||||
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
|
|
||||||
SDLActivity.onNativeMouse(0, action, x, y, false);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case MotionEvent.ACTION_HOVER_MOVE:
|
|
||||||
x = event.getX(0);
|
|
||||||
y = event.getY(0);
|
|
||||||
|
|
||||||
SDLActivity.onNativeMouse(0, action, x, y, false);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Event was not managed
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean supportsRelativeMouse() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean inRelativeMode() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean setRelativeMouseEnabled(boolean enabled) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void reclaimRelativeMouseModeIfNeeded()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public float getEventX(MotionEvent event) {
|
|
||||||
return event.getX(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public float getEventY(MotionEvent event) {
|
|
||||||
return event.getY(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class SDLGenericMotionListener_API24 extends SDLGenericMotionListener_API12 {
|
|
||||||
// Generic Motion (mouse hover, joystick...) events go here
|
|
||||||
|
|
||||||
private boolean mRelativeModeEnabled;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onGenericMotion(View v, MotionEvent event) {
|
|
||||||
|
|
||||||
// Handle relative mouse mode
|
|
||||||
if (mRelativeModeEnabled) {
|
|
||||||
if (event.getSource() == InputDevice.SOURCE_MOUSE) {
|
|
||||||
int action = event.getActionMasked();
|
|
||||||
if (action == MotionEvent.ACTION_HOVER_MOVE) {
|
|
||||||
float x = event.getAxisValue(MotionEvent.AXIS_RELATIVE_X);
|
|
||||||
float y = event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y);
|
|
||||||
SDLActivity.onNativeMouse(0, action, x, y, true);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Event was not managed, call SDLGenericMotionListener_API12 method
|
|
||||||
return super.onGenericMotion(v, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsRelativeMouse() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean inRelativeMode() {
|
|
||||||
return mRelativeModeEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean setRelativeMouseEnabled(boolean enabled) {
|
|
||||||
mRelativeModeEnabled = enabled;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public float getEventX(MotionEvent event) {
|
|
||||||
if (mRelativeModeEnabled) {
|
|
||||||
return event.getAxisValue(MotionEvent.AXIS_RELATIVE_X);
|
|
||||||
} else {
|
|
||||||
return event.getX(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public float getEventY(MotionEvent event) {
|
|
||||||
if (mRelativeModeEnabled) {
|
|
||||||
return event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y);
|
|
||||||
} else {
|
|
||||||
return event.getY(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SDLGenericMotionListener_API26 extends SDLGenericMotionListener_API24 {
|
|
||||||
// Generic Motion (mouse hover, joystick...) events go here
|
|
||||||
private boolean mRelativeModeEnabled;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onGenericMotion(View v, MotionEvent event) {
|
|
||||||
float x, y;
|
|
||||||
int action;
|
|
||||||
|
|
||||||
switch ( event.getSource() ) {
|
|
||||||
case InputDevice.SOURCE_JOYSTICK:
|
|
||||||
return SDLControllerManager.handleJoystickMotionEvent(event);
|
|
||||||
|
|
||||||
case InputDevice.SOURCE_MOUSE:
|
|
||||||
// DeX desktop mouse cursor is a separate non-standard input type.
|
|
||||||
case InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN:
|
|
||||||
action = event.getActionMasked();
|
|
||||||
switch (action) {
|
|
||||||
case MotionEvent.ACTION_SCROLL:
|
|
||||||
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
|
|
||||||
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
|
|
||||||
SDLActivity.onNativeMouse(0, action, x, y, false);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case MotionEvent.ACTION_HOVER_MOVE:
|
|
||||||
x = event.getX(0);
|
|
||||||
y = event.getY(0);
|
|
||||||
SDLActivity.onNativeMouse(0, action, x, y, false);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case InputDevice.SOURCE_MOUSE_RELATIVE:
|
|
||||||
action = event.getActionMasked();
|
|
||||||
switch (action) {
|
|
||||||
case MotionEvent.ACTION_SCROLL:
|
|
||||||
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
|
|
||||||
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
|
|
||||||
SDLActivity.onNativeMouse(0, action, x, y, false);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case MotionEvent.ACTION_HOVER_MOVE:
|
|
||||||
x = event.getX(0);
|
|
||||||
y = event.getY(0);
|
|
||||||
SDLActivity.onNativeMouse(0, action, x, y, true);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Event was not managed
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsRelativeMouse() {
|
|
||||||
return (!SDLActivity.isDeXMode() || Build.VERSION.SDK_INT >= 27 /* Android 8.1 (O_MR1) */);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean inRelativeMode() {
|
|
||||||
return mRelativeModeEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean setRelativeMouseEnabled(boolean enabled) {
|
|
||||||
if (!SDLActivity.isDeXMode() || Build.VERSION.SDK_INT >= 27 /* Android 8.1 (O_MR1) */) {
|
|
||||||
if (enabled) {
|
|
||||||
SDLActivity.getContentView().requestPointerCapture();
|
|
||||||
} else {
|
|
||||||
SDLActivity.getContentView().releasePointerCapture();
|
|
||||||
}
|
|
||||||
mRelativeModeEnabled = enabled;
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void reclaimRelativeMouseModeIfNeeded()
|
|
||||||
{
|
|
||||||
if (mRelativeModeEnabled && !SDLActivity.isDeXMode()) {
|
|
||||||
SDLActivity.getContentView().requestPointerCapture();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public float getEventX(MotionEvent event) {
|
|
||||||
// Relative mouse in capture mode will only have relative for X/Y
|
|
||||||
return event.getX(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public float getEventY(MotionEvent event) {
|
|
||||||
// Relative mouse in capture mode will only have relative for X/Y
|
|
||||||
return event.getY(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,405 +0,0 @@
|
|||||||
package org.libsdl.app;
|
|
||||||
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.pm.ActivityInfo;
|
|
||||||
import android.hardware.Sensor;
|
|
||||||
import android.hardware.SensorEvent;
|
|
||||||
import android.hardware.SensorEventListener;
|
|
||||||
import android.hardware.SensorManager;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.util.DisplayMetrics;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.Display;
|
|
||||||
import android.view.InputDevice;
|
|
||||||
import android.view.KeyEvent;
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
import android.view.Surface;
|
|
||||||
import android.view.SurfaceHolder;
|
|
||||||
import android.view.SurfaceView;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.WindowManager;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
SDLSurface. This is what we draw on, so we need to know when it's created
|
|
||||||
in order to do anything useful.
|
|
||||||
|
|
||||||
Because of this, that's where we set up the SDL thread
|
|
||||||
*/
|
|
||||||
public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
|
|
||||||
View.OnKeyListener, View.OnTouchListener, SensorEventListener {
|
|
||||||
|
|
||||||
// Sensors
|
|
||||||
protected SensorManager mSensorManager;
|
|
||||||
protected Display mDisplay;
|
|
||||||
|
|
||||||
// Keep track of the surface size to normalize touch events
|
|
||||||
protected float mWidth, mHeight;
|
|
||||||
|
|
||||||
// Is SurfaceView ready for rendering
|
|
||||||
public boolean mIsSurfaceReady;
|
|
||||||
|
|
||||||
// Startup
|
|
||||||
public SDLSurface(Context context) {
|
|
||||||
super(context);
|
|
||||||
getHolder().addCallback(this);
|
|
||||||
|
|
||||||
setFocusable(true);
|
|
||||||
setFocusableInTouchMode(true);
|
|
||||||
requestFocus();
|
|
||||||
setOnKeyListener(this);
|
|
||||||
setOnTouchListener(this);
|
|
||||||
|
|
||||||
mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
|
|
||||||
mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
|
|
||||||
|
|
||||||
setOnGenericMotionListener(SDLActivity.getMotionListener());
|
|
||||||
|
|
||||||
// Some arbitrary defaults to avoid a potential division by zero
|
|
||||||
mWidth = 1.0f;
|
|
||||||
mHeight = 1.0f;
|
|
||||||
|
|
||||||
mIsSurfaceReady = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void handlePause() {
|
|
||||||
enableSensor(Sensor.TYPE_ACCELEROMETER, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void handleResume() {
|
|
||||||
setFocusable(true);
|
|
||||||
setFocusableInTouchMode(true);
|
|
||||||
requestFocus();
|
|
||||||
setOnKeyListener(this);
|
|
||||||
setOnTouchListener(this);
|
|
||||||
enableSensor(Sensor.TYPE_ACCELEROMETER, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Surface getNativeSurface() {
|
|
||||||
return getHolder().getSurface();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called when we have a valid drawing surface
|
|
||||||
@Override
|
|
||||||
public void surfaceCreated(SurfaceHolder holder) {
|
|
||||||
Log.v("SDL", "surfaceCreated()");
|
|
||||||
SDLActivity.onNativeSurfaceCreated();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called when we lose the surface
|
|
||||||
@Override
|
|
||||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
|
||||||
Log.v("SDL", "surfaceDestroyed()");
|
|
||||||
|
|
||||||
// Transition to pause, if needed
|
|
||||||
SDLActivity.mNextNativeState = SDLActivity.NativeState.PAUSED;
|
|
||||||
SDLActivity.handleNativeState();
|
|
||||||
|
|
||||||
mIsSurfaceReady = false;
|
|
||||||
SDLActivity.onNativeSurfaceDestroyed();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called when the surface is resized
|
|
||||||
@Override
|
|
||||||
public void surfaceChanged(SurfaceHolder holder,
|
|
||||||
int format, int width, int height) {
|
|
||||||
Log.v("SDL", "surfaceChanged()");
|
|
||||||
|
|
||||||
if (SDLActivity.mSingleton == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mWidth = width;
|
|
||||||
mHeight = height;
|
|
||||||
int nDeviceWidth = width;
|
|
||||||
int nDeviceHeight = height;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (Build.VERSION.SDK_INT >= 17 /* Android 4.2 (JELLY_BEAN_MR1) */) {
|
|
||||||
DisplayMetrics realMetrics = new DisplayMetrics();
|
|
||||||
mDisplay.getRealMetrics( realMetrics );
|
|
||||||
nDeviceWidth = realMetrics.widthPixels;
|
|
||||||
nDeviceHeight = realMetrics.heightPixels;
|
|
||||||
}
|
|
||||||
} catch(Exception ignored) {
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized(SDLActivity.getContext()) {
|
|
||||||
// In case we're waiting on a size change after going fullscreen, send a notification.
|
|
||||||
SDLActivity.getContext().notifyAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.v("SDL", "Window size: " + width + "x" + height);
|
|
||||||
Log.v("SDL", "Device size: " + nDeviceWidth + "x" + nDeviceHeight);
|
|
||||||
SDLActivity.nativeSetScreenResolution(width, height, nDeviceWidth, nDeviceHeight, mDisplay.getRefreshRate());
|
|
||||||
SDLActivity.onNativeResize();
|
|
||||||
|
|
||||||
// Prevent a screen distortion glitch,
|
|
||||||
// for instance when the device is in Landscape and a Portrait App is resumed.
|
|
||||||
boolean skip = false;
|
|
||||||
int requestedOrientation = SDLActivity.mSingleton.getRequestedOrientation();
|
|
||||||
|
|
||||||
if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) {
|
|
||||||
if (mWidth > mHeight) {
|
|
||||||
skip = true;
|
|
||||||
}
|
|
||||||
} else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) {
|
|
||||||
if (mWidth < mHeight) {
|
|
||||||
skip = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Special Patch for Square Resolution: Black Berry Passport
|
|
||||||
if (skip) {
|
|
||||||
double min = Math.min(mWidth, mHeight);
|
|
||||||
double max = Math.max(mWidth, mHeight);
|
|
||||||
|
|
||||||
if (max / min < 1.20) {
|
|
||||||
Log.v("SDL", "Don't skip on such aspect-ratio. Could be a square resolution.");
|
|
||||||
skip = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't skip in MultiWindow.
|
|
||||||
if (skip) {
|
|
||||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
|
||||||
if (SDLActivity.mSingleton.isInMultiWindowMode()) {
|
|
||||||
Log.v("SDL", "Don't skip in Multi-Window");
|
|
||||||
skip = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (skip) {
|
|
||||||
Log.v("SDL", "Skip .. Surface is not ready.");
|
|
||||||
mIsSurfaceReady = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */
|
|
||||||
SDLActivity.onNativeSurfaceChanged();
|
|
||||||
|
|
||||||
/* Surface is ready */
|
|
||||||
mIsSurfaceReady = true;
|
|
||||||
|
|
||||||
SDLActivity.mNextNativeState = SDLActivity.NativeState.RESUMED;
|
|
||||||
SDLActivity.handleNativeState();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Key events
|
|
||||||
@Override
|
|
||||||
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
|
||||||
return SDLActivity.handleKeyEvent(v, keyCode, event, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Touch events
|
|
||||||
@Override
|
|
||||||
public boolean onTouch(View v, MotionEvent event) {
|
|
||||||
/* Ref: http://developer.android.com/training/gestures/multi.html */
|
|
||||||
int touchDevId = event.getDeviceId();
|
|
||||||
final int pointerCount = event.getPointerCount();
|
|
||||||
int action = event.getActionMasked();
|
|
||||||
int pointerFingerId;
|
|
||||||
int i = -1;
|
|
||||||
float x,y,p;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Prevent id to be -1, since it's used in SDL internal for synthetic events
|
|
||||||
* Appears when using Android emulator, eg:
|
|
||||||
* adb shell input mouse tap 100 100
|
|
||||||
* adb shell input touchscreen tap 100 100
|
|
||||||
*/
|
|
||||||
if (touchDevId < 0) {
|
|
||||||
touchDevId -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 12290 = Samsung DeX mode desktop mouse
|
|
||||||
// 12290 = 0x3002 = 0x2002 | 0x1002 = SOURCE_MOUSE | SOURCE_TOUCHSCREEN
|
|
||||||
// 0x2 = SOURCE_CLASS_POINTER
|
|
||||||
if (event.getSource() == InputDevice.SOURCE_MOUSE || event.getSource() == (InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN)) {
|
|
||||||
int mouseButton = 1;
|
|
||||||
try {
|
|
||||||
Object object = event.getClass().getMethod("getButtonState").invoke(event);
|
|
||||||
if (object != null) {
|
|
||||||
mouseButton = (Integer) object;
|
|
||||||
}
|
|
||||||
} catch(Exception ignored) {
|
|
||||||
}
|
|
||||||
|
|
||||||
// We need to check if we're in relative mouse mode and get the axis offset rather than the x/y values
|
|
||||||
// if we are. We'll leverage our existing mouse motion listener
|
|
||||||
SDLGenericMotionListener_API12 motionListener = SDLActivity.getMotionListener();
|
|
||||||
x = motionListener.getEventX(event);
|
|
||||||
y = motionListener.getEventY(event);
|
|
||||||
|
|
||||||
SDLActivity.onNativeMouse(mouseButton, action, x, y, motionListener.inRelativeMode());
|
|
||||||
} else {
|
|
||||||
switch(action) {
|
|
||||||
case MotionEvent.ACTION_MOVE:
|
|
||||||
for (i = 0; i < pointerCount; i++) {
|
|
||||||
pointerFingerId = event.getPointerId(i);
|
|
||||||
x = event.getX(i) / mWidth;
|
|
||||||
y = event.getY(i) / mHeight;
|
|
||||||
p = event.getPressure(i);
|
|
||||||
if (p > 1.0f) {
|
|
||||||
// may be larger than 1.0f on some devices
|
|
||||||
// see the documentation of getPressure(i)
|
|
||||||
p = 1.0f;
|
|
||||||
}
|
|
||||||
SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MotionEvent.ACTION_UP:
|
|
||||||
case MotionEvent.ACTION_DOWN:
|
|
||||||
// Primary pointer up/down, the index is always zero
|
|
||||||
i = 0;
|
|
||||||
/* fallthrough */
|
|
||||||
case MotionEvent.ACTION_POINTER_UP:
|
|
||||||
case MotionEvent.ACTION_POINTER_DOWN:
|
|
||||||
// Non primary pointer up/down
|
|
||||||
if (i == -1) {
|
|
||||||
i = event.getActionIndex();
|
|
||||||
}
|
|
||||||
|
|
||||||
pointerFingerId = event.getPointerId(i);
|
|
||||||
x = event.getX(i) / mWidth;
|
|
||||||
y = event.getY(i) / mHeight;
|
|
||||||
p = event.getPressure(i);
|
|
||||||
if (p > 1.0f) {
|
|
||||||
// may be larger than 1.0f on some devices
|
|
||||||
// see the documentation of getPressure(i)
|
|
||||||
p = 1.0f;
|
|
||||||
}
|
|
||||||
SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MotionEvent.ACTION_CANCEL:
|
|
||||||
for (i = 0; i < pointerCount; i++) {
|
|
||||||
pointerFingerId = event.getPointerId(i);
|
|
||||||
x = event.getX(i) / mWidth;
|
|
||||||
y = event.getY(i) / mHeight;
|
|
||||||
p = event.getPressure(i);
|
|
||||||
if (p > 1.0f) {
|
|
||||||
// may be larger than 1.0f on some devices
|
|
||||||
// see the documentation of getPressure(i)
|
|
||||||
p = 1.0f;
|
|
||||||
}
|
|
||||||
SDLActivity.onNativeTouch(touchDevId, pointerFingerId, MotionEvent.ACTION_UP, x, y, p);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sensor events
|
|
||||||
public void enableSensor(int sensortype, boolean enabled) {
|
|
||||||
// TODO: This uses getDefaultSensor - what if we have >1 accels?
|
|
||||||
if (enabled) {
|
|
||||||
mSensorManager.registerListener(this,
|
|
||||||
mSensorManager.getDefaultSensor(sensortype),
|
|
||||||
SensorManager.SENSOR_DELAY_GAME, null);
|
|
||||||
} else {
|
|
||||||
mSensorManager.unregisterListener(this,
|
|
||||||
mSensorManager.getDefaultSensor(sensortype));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAccuracyChanged(Sensor sensor, int accuracy) {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSensorChanged(SensorEvent event) {
|
|
||||||
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
|
|
||||||
|
|
||||||
// Since we may have an orientation set, we won't receive onConfigurationChanged events.
|
|
||||||
// We thus should check here.
|
|
||||||
int newOrientation;
|
|
||||||
|
|
||||||
float x, y;
|
|
||||||
switch (mDisplay.getRotation()) {
|
|
||||||
case Surface.ROTATION_90:
|
|
||||||
x = -event.values[1];
|
|
||||||
y = event.values[0];
|
|
||||||
newOrientation = SDLActivity.SDL_ORIENTATION_LANDSCAPE;
|
|
||||||
break;
|
|
||||||
case Surface.ROTATION_270:
|
|
||||||
x = event.values[1];
|
|
||||||
y = -event.values[0];
|
|
||||||
newOrientation = SDLActivity.SDL_ORIENTATION_LANDSCAPE_FLIPPED;
|
|
||||||
break;
|
|
||||||
case Surface.ROTATION_180:
|
|
||||||
x = -event.values[0];
|
|
||||||
y = -event.values[1];
|
|
||||||
newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT_FLIPPED;
|
|
||||||
break;
|
|
||||||
case Surface.ROTATION_0:
|
|
||||||
default:
|
|
||||||
x = event.values[0];
|
|
||||||
y = event.values[1];
|
|
||||||
newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newOrientation != SDLActivity.mCurrentOrientation) {
|
|
||||||
SDLActivity.mCurrentOrientation = newOrientation;
|
|
||||||
SDLActivity.onNativeOrientationChanged(newOrientation);
|
|
||||||
}
|
|
||||||
|
|
||||||
SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH,
|
|
||||||
y / SensorManager.GRAVITY_EARTH,
|
|
||||||
event.values[2] / SensorManager.GRAVITY_EARTH);
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Captured pointer events for API 26.
|
|
||||||
public boolean onCapturedPointerEvent(MotionEvent event)
|
|
||||||
{
|
|
||||||
int action = event.getActionMasked();
|
|
||||||
|
|
||||||
float x, y;
|
|
||||||
switch (action) {
|
|
||||||
case MotionEvent.ACTION_SCROLL:
|
|
||||||
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
|
|
||||||
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
|
|
||||||
SDLActivity.onNativeMouse(0, action, x, y, false);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case MotionEvent.ACTION_HOVER_MOVE:
|
|
||||||
case MotionEvent.ACTION_MOVE:
|
|
||||||
x = event.getX(0);
|
|
||||||
y = event.getY(0);
|
|
||||||
SDLActivity.onNativeMouse(0, action, x, y, true);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case MotionEvent.ACTION_BUTTON_PRESS:
|
|
||||||
case MotionEvent.ACTION_BUTTON_RELEASE:
|
|
||||||
|
|
||||||
// Change our action value to what SDL's code expects.
|
|
||||||
if (action == MotionEvent.ACTION_BUTTON_PRESS) {
|
|
||||||
action = MotionEvent.ACTION_DOWN;
|
|
||||||
} else { /* MotionEvent.ACTION_BUTTON_RELEASE */
|
|
||||||
action = MotionEvent.ACTION_UP;
|
|
||||||
}
|
|
||||||
|
|
||||||
x = event.getX(0);
|
|
||||||
y = event.getY(0);
|
|
||||||
int button = event.getButtonState();
|
|
||||||
|
|
||||||
SDLActivity.onNativeMouse(button, action, x, y, true);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BIN
proj-android/app/src/main/res/mipmap-hdpi/ic_launcher.png
(Stored with Git LFS)
BIN
proj-android/app/src/main/res/mipmap-hdpi/ic_launcher.png
(Stored with Git LFS)
Binary file not shown.
BIN
proj-android/app/src/main/res/mipmap-mdpi/ic_launcher.png
(Stored with Git LFS)
BIN
proj-android/app/src/main/res/mipmap-mdpi/ic_launcher.png
(Stored with Git LFS)
Binary file not shown.
BIN
proj-android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
(Stored with Git LFS)
BIN
proj-android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
(Stored with Git LFS)
Binary file not shown.
BIN
proj-android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
(Stored with Git LFS)
BIN
proj-android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
(Stored with Git LFS)
Binary file not shown.
BIN
proj-android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
(Stored with Git LFS)
BIN
proj-android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
(Stored with Git LFS)
Binary file not shown.
@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<color name="colorPrimary">#3F51B5</color>
|
|
||||||
<color name="colorPrimaryDark">#303F9F</color>
|
|
||||||
<color name="colorAccent">#FF4081</color>
|
|
||||||
</resources>
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
<resources>
|
|
||||||
<string name="app_name">Game</string>
|
|
||||||
</resources>
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<!-- Base application theme. -->
|
|
||||||
<style name="AppTheme" parent="android:Theme.NoTitleBar.Fullscreen">
|
|
||||||
<!-- Customize your theme here. -->
|
|
||||||
</style>
|
|
||||||
</resources>
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
|
||||||
|
|
||||||
buildscript {
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
google()
|
|
||||||
}
|
|
||||||
dependencies {
|
|
||||||
classpath 'com.android.tools.build:gradle:8.1.1'
|
|
||||||
|
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
|
||||||
// in the individual module build.gradle files
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
allprojects {
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
google()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
task clean(type: Delete) {
|
|
||||||
delete rootProject.buildDir
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
# Project-wide Gradle settings.
|
|
||||||
|
|
||||||
# IDE (e.g. Android Studio) users:
|
|
||||||
# Gradle settings configured through the IDE *will override*
|
|
||||||
# any settings specified in this file.
|
|
||||||
|
|
||||||
# For more details on how to configure your build environment visit
|
|
||||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
|
||||||
|
|
||||||
# Specifies the JVM arguments used for the daemon process.
|
|
||||||
# The setting is particularly useful for tweaking memory settings.
|
|
||||||
org.gradle.jvmargs=-Xmx1536m
|
|
||||||
|
|
||||||
# When configured, Gradle will run in incubating parallel mode.
|
|
||||||
# This option should only be used with decoupled projects. More details, visit
|
|
||||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
|
||||||
# org.gradle.parallel=true
|
|
||||||
BIN
proj-android/gradle/wrapper/gradle-wrapper.jar
vendored
BIN
proj-android/gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
@ -1,6 +0,0 @@
|
|||||||
#Sat Jan 10 10:31:05 MSK 2026
|
|
||||||
distributionBase=GRADLE_USER_HOME
|
|
||||||
distributionPath=wrapper/dists
|
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
|
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
|
||||||
zipStorePath=wrapper/dists
|
|
||||||
160
proj-android/gradlew
vendored
160
proj-android/gradlew
vendored
@ -1,160 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
##############################################################################
|
|
||||||
##
|
|
||||||
## Gradle start up script for UN*X
|
|
||||||
##
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
|
||||||
DEFAULT_JVM_OPTS=""
|
|
||||||
|
|
||||||
APP_NAME="Gradle"
|
|
||||||
APP_BASE_NAME=`basename "$0"`
|
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
|
||||||
MAX_FD="maximum"
|
|
||||||
|
|
||||||
warn ( ) {
|
|
||||||
echo "$*"
|
|
||||||
}
|
|
||||||
|
|
||||||
die ( ) {
|
|
||||||
echo
|
|
||||||
echo "$*"
|
|
||||||
echo
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# OS specific support (must be 'true' or 'false').
|
|
||||||
cygwin=false
|
|
||||||
msys=false
|
|
||||||
darwin=false
|
|
||||||
case "`uname`" in
|
|
||||||
CYGWIN* )
|
|
||||||
cygwin=true
|
|
||||||
;;
|
|
||||||
Darwin* )
|
|
||||||
darwin=true
|
|
||||||
;;
|
|
||||||
MINGW* )
|
|
||||||
msys=true
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# Attempt to set APP_HOME
|
|
||||||
# Resolve links: $0 may be a link
|
|
||||||
PRG="$0"
|
|
||||||
# Need this for relative symlinks.
|
|
||||||
while [ -h "$PRG" ] ; do
|
|
||||||
ls=`ls -ld "$PRG"`
|
|
||||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
|
||||||
if expr "$link" : '/.*' > /dev/null; then
|
|
||||||
PRG="$link"
|
|
||||||
else
|
|
||||||
PRG=`dirname "$PRG"`"/$link"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
SAVED="`pwd`"
|
|
||||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
|
||||||
APP_HOME="`pwd -P`"
|
|
||||||
cd "$SAVED" >/dev/null
|
|
||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
|
||||||
|
|
||||||
# Determine the Java command to use to start the JVM.
|
|
||||||
if [ -n "$JAVA_HOME" ] ; then
|
|
||||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
|
||||||
# IBM's JDK on AIX uses strange locations for the executables
|
|
||||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
|
||||||
else
|
|
||||||
JAVACMD="$JAVA_HOME/bin/java"
|
|
||||||
fi
|
|
||||||
if [ ! -x "$JAVACMD" ] ; then
|
|
||||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
|
||||||
|
|
||||||
Please set the JAVA_HOME variable in your environment to match the
|
|
||||||
location of your Java installation."
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
JAVACMD="java"
|
|
||||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
|
||||||
|
|
||||||
Please set the JAVA_HOME variable in your environment to match the
|
|
||||||
location of your Java installation."
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
|
||||||
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
|
||||||
MAX_FD_LIMIT=`ulimit -H -n`
|
|
||||||
if [ $? -eq 0 ] ; then
|
|
||||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
|
||||||
MAX_FD="$MAX_FD_LIMIT"
|
|
||||||
fi
|
|
||||||
ulimit -n $MAX_FD
|
|
||||||
if [ $? -ne 0 ] ; then
|
|
||||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# For Darwin, add options to specify how the application appears in the dock
|
|
||||||
if $darwin; then
|
|
||||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
|
||||||
fi
|
|
||||||
|
|
||||||
# For Cygwin, switch paths to Windows format before running java
|
|
||||||
if $cygwin ; then
|
|
||||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
|
||||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
|
||||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
|
||||||
|
|
||||||
# We build the pattern for arguments to be converted via cygpath
|
|
||||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
|
||||||
SEP=""
|
|
||||||
for dir in $ROOTDIRSRAW ; do
|
|
||||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
|
||||||
SEP="|"
|
|
||||||
done
|
|
||||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
|
||||||
# Add a user-defined pattern to the cygpath arguments
|
|
||||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
|
||||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
|
||||||
fi
|
|
||||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
|
||||||
i=0
|
|
||||||
for arg in "$@" ; do
|
|
||||||
CHECK=`echo "$arg"|grep -E -c "$OURCYGPATTERN" -`
|
|
||||||
CHECK2=`echo "$arg"|grep -E -c "^-"` ### Determine if an option
|
|
||||||
|
|
||||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
|
||||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
|
||||||
else
|
|
||||||
eval `echo args$i`="\"$arg\""
|
|
||||||
fi
|
|
||||||
i=$((i+1))
|
|
||||||
done
|
|
||||||
case $i in
|
|
||||||
(0) set -- ;;
|
|
||||||
(1) set -- "$args0" ;;
|
|
||||||
(2) set -- "$args0" "$args1" ;;
|
|
||||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
|
||||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
|
||||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
|
||||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
|
||||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
|
||||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
|
||||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
|
||||||
function splitJvmOpts() {
|
|
||||||
JVM_OPTS=("$@")
|
|
||||||
}
|
|
||||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
|
||||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
|
||||||
|
|
||||||
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
|
||||||
90
proj-android/gradlew.bat
vendored
90
proj-android/gradlew.bat
vendored
@ -1,90 +0,0 @@
|
|||||||
@if "%DEBUG%" == "" @echo off
|
|
||||||
@rem ##########################################################################
|
|
||||||
@rem
|
|
||||||
@rem Gradle startup script for Windows
|
|
||||||
@rem
|
|
||||||
@rem ##########################################################################
|
|
||||||
|
|
||||||
@rem Set local scope for the variables with windows NT shell
|
|
||||||
if "%OS%"=="Windows_NT" setlocal
|
|
||||||
|
|
||||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
|
||||||
set DEFAULT_JVM_OPTS=
|
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
|
||||||
if "%DIRNAME%" == "" set DIRNAME=.
|
|
||||||
set APP_BASE_NAME=%~n0
|
|
||||||
set APP_HOME=%DIRNAME%
|
|
||||||
|
|
||||||
@rem Find java.exe
|
|
||||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
|
||||||
|
|
||||||
set JAVA_EXE=java.exe
|
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
|
||||||
if "%ERRORLEVEL%" == "0" goto init
|
|
||||||
|
|
||||||
echo.
|
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
|
||||||
echo.
|
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
|
||||||
echo location of your Java installation.
|
|
||||||
|
|
||||||
goto fail
|
|
||||||
|
|
||||||
:findJavaFromJavaHome
|
|
||||||
set JAVA_HOME=%JAVA_HOME:"=%
|
|
||||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
|
||||||
|
|
||||||
if exist "%JAVA_EXE%" goto init
|
|
||||||
|
|
||||||
echo.
|
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
|
||||||
echo.
|
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
|
||||||
echo location of your Java installation.
|
|
||||||
|
|
||||||
goto fail
|
|
||||||
|
|
||||||
:init
|
|
||||||
@rem Get command-line arguments, handling Windowz variants
|
|
||||||
|
|
||||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
|
||||||
if "%@eval[2+2]" == "4" goto 4NT_args
|
|
||||||
|
|
||||||
:win9xME_args
|
|
||||||
@rem Slurp the command line arguments.
|
|
||||||
set CMD_LINE_ARGS=
|
|
||||||
set _SKIP=2
|
|
||||||
|
|
||||||
:win9xME_args_slurp
|
|
||||||
if "x%~1" == "x" goto execute
|
|
||||||
|
|
||||||
set CMD_LINE_ARGS=%*
|
|
||||||
goto execute
|
|
||||||
|
|
||||||
:4NT_args
|
|
||||||
@rem Get arguments from the 4NT Shell from JP Software
|
|
||||||
set CMD_LINE_ARGS=%$
|
|
||||||
|
|
||||||
:execute
|
|
||||||
@rem Setup the command line
|
|
||||||
|
|
||||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
|
||||||
|
|
||||||
@rem Execute Gradle
|
|
||||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
|
||||||
|
|
||||||
:end
|
|
||||||
@rem End local scope for the variables with windows NT shell
|
|
||||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
|
||||||
|
|
||||||
:fail
|
|
||||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
|
||||||
rem the _cmd.exe /c_ return code!
|
|
||||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
|
||||||
exit /b 1
|
|
||||||
|
|
||||||
:mainEnd
|
|
||||||
if "%OS%"=="Windows_NT" endlocal
|
|
||||||
|
|
||||||
:omega
|
|
||||||
@ -1 +0,0 @@
|
|||||||
include ':app'
|
|
||||||
@ -1,141 +0,0 @@
|
|||||||
cmake_minimum_required(VERSION 3.15)
|
|
||||||
|
|
||||||
# Фикс для Ninja на Windows
|
|
||||||
if(NOT CMAKE_MAKE_PROGRAM AND WIN32)
|
|
||||||
set(POTENTIAL_NINJA "${CMAKE_CURRENT_SOURCE_DIR}/ninja/ninja.exe")
|
|
||||||
if(EXISTS "${POTENTIAL_NINJA}")
|
|
||||||
set(CMAKE_MAKE_PROGRAM "${POTENTIAL_NINJA}" CACHE FILEPATH "Path to ninja" FORCE)
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
project(space-game001 CXX)
|
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
||||||
|
|
||||||
# --- АВТО-ЗАГРУЗКА ЗАВИСИМОСТЕЙ ---
|
|
||||||
include("${CMAKE_CURRENT_SOURCE_DIR}/../cmake/FetchDependencies.cmake")
|
|
||||||
# Теперь гарантированно есть папка ../thirdparty со всеми исходниками
|
|
||||||
|
|
||||||
# Список исходных файлов (без изменений)
|
|
||||||
set(SOURCES
|
|
||||||
../src/main.cpp
|
|
||||||
../src/Game.cpp
|
|
||||||
../src/Environment.cpp
|
|
||||||
../src/BoneAnimatedModel.cpp
|
|
||||||
../src/TextModel.cpp
|
|
||||||
../src/Projectile.cpp
|
|
||||||
../src/SparkEmitter.cpp
|
|
||||||
../src/UiManager.cpp
|
|
||||||
../src/render/Renderer.cpp
|
|
||||||
../src/render/ShaderManager.cpp
|
|
||||||
../src/render/TextureManager.cpp
|
|
||||||
../src/render/FrameBuffer.cpp
|
|
||||||
../src/render/OpenGlExtensions.cpp
|
|
||||||
../src/utils/Utils.cpp
|
|
||||||
../src/utils/TaskManager.cpp
|
|
||||||
../src/utils/Perlin.cpp
|
|
||||||
../src/planet/PlanetData.cpp
|
|
||||||
../src/planet/PlanetObject.cpp
|
|
||||||
../src/planet/StoneObject.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
add_executable(space-game001 ${SOURCES})
|
|
||||||
|
|
||||||
# Настройка путей к инклудам (используем скачанные исходники)
|
|
||||||
target_include_directories(space-game001 PRIVATE
|
|
||||||
../src
|
|
||||||
../thirdparty/eigen-5.0.0
|
|
||||||
../thirdparty/boost_1_90_0
|
|
||||||
)
|
|
||||||
|
|
||||||
# Сборка libzip через add_subdirectory (Emscripten соберет её из исходников)
|
|
||||||
# Опции для либзипа, чтобы он не искал лишнего в системе
|
|
||||||
set(ENABLE_GNUTLS OFF CACHE BOOL "" FORCE)
|
|
||||||
set(ENABLE_OPENSSL OFF CACHE BOOL "" FORCE)
|
|
||||||
set(ENABLE_WINDOWS_CRYPTO OFF CACHE BOOL "" FORCE)
|
|
||||||
set(ENABLE_COMMONCRYPTO OFF CACHE BOOL "" FORCE)
|
|
||||||
|
|
||||||
add_subdirectory("../thirdparty/libzip-1.11.4" libzip-build)
|
|
||||||
|
|
||||||
# Линковка:
|
|
||||||
# 'zip' берется из add_subdirectory
|
|
||||||
# 'z' - это системный zlib Emscripten-а (флаг -sUSE_ZLIB=1 добавим ниже)
|
|
||||||
target_link_libraries(space-game001 PRIVATE zip z)
|
|
||||||
|
|
||||||
# Эмскриптен-флаги
|
|
||||||
set(EMSCRIPTEN_FLAGS
|
|
||||||
"-sUSE_SDL=2"
|
|
||||||
"-sUSE_SDL_IMAGE=2"
|
|
||||||
"-sUSE_LIBPNG=1"
|
|
||||||
"-sUSE_ZLIB=1" # Добавили zlib порт
|
|
||||||
"-pthread"
|
|
||||||
"-sUSE_PTHREADS=1"
|
|
||||||
"-fexceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
target_compile_options(space-game001 PRIVATE ${EMSCRIPTEN_FLAGS} "-O2")
|
|
||||||
|
|
||||||
set(EMSCRIPTEN_LINK_FLAGS
|
|
||||||
${EMSCRIPTEN_FLAGS}
|
|
||||||
"-O2"
|
|
||||||
"-sPTHREAD_POOL_SIZE=4"
|
|
||||||
"-sALLOW_MEMORY_GROWTH=1"
|
|
||||||
"--preload-file resources.zip"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Применяем настройки линковки
|
|
||||||
target_link_options(space-game001 PRIVATE ${EMSCRIPTEN_LINK_FLAGS})
|
|
||||||
|
|
||||||
# Для совместимости со старыми версиями CMake, если target_link_options недостаточно
|
|
||||||
string(REPLACE ";" " " EMSCRIPTEN_LINK_FLAGS_STR "${EMSCRIPTEN_LINK_FLAGS}")
|
|
||||||
set_target_properties(space-game001 PROPERTIES
|
|
||||||
LINK_FLAGS "${EMSCRIPTEN_LINK_FLAGS_STR}"
|
|
||||||
SUFFIX ".html"
|
|
||||||
)
|
|
||||||
|
|
||||||
# --- Ресурсы и Деплой (без изменений) ---
|
|
||||||
set(RESOURCES_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../resources")
|
|
||||||
set(RESOURCES_ZIP "${CMAKE_CURRENT_BINARY_DIR}/resources.zip")
|
|
||||||
get_filename_component(RESOURCES_PARENT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/.." ABSOLUTE)
|
|
||||||
|
|
||||||
add_custom_command(
|
|
||||||
OUTPUT "${RESOURCES_ZIP}"
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E tar "cf" "${RESOURCES_ZIP}" --format=zip "resources"
|
|
||||||
WORKING_DIRECTORY "${RESOURCES_PARENT_DIR}"
|
|
||||||
DEPENDS "${RESOURCES_PARENT_DIR}/resources"
|
|
||||||
)
|
|
||||||
|
|
||||||
add_custom_target(pack_resources DEPENDS "${RESOURCES_ZIP}")
|
|
||||||
add_dependencies(space-game001 pack_resources)
|
|
||||||
|
|
||||||
|
|
||||||
# Определяем путь к директории установки (относительно папки билда)
|
|
||||||
set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/public")
|
|
||||||
|
|
||||||
# 1. Устанавливаем основной HTML файл
|
|
||||||
install(TARGETS space-game001
|
|
||||||
RUNTIME DESTINATION .
|
|
||||||
)
|
|
||||||
|
|
||||||
# 2. Устанавливаем сопутствующие файлы (JS, WASM и сгенерированный архив данных)
|
|
||||||
# Emscripten создает их в той же папке, что и таргет
|
|
||||||
install(FILES
|
|
||||||
"${CMAKE_CURRENT_BINARY_DIR}/space-game001.js"
|
|
||||||
"${CMAKE_CURRENT_BINARY_DIR}/space-game001.wasm"
|
|
||||||
"${CMAKE_CURRENT_BINARY_DIR}/space-game001.data"
|
|
||||||
DESTINATION .
|
|
||||||
)
|
|
||||||
|
|
||||||
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/space-game001plain.html"
|
|
||||||
DESTINATION .
|
|
||||||
)
|
|
||||||
|
|
||||||
# Если вам все еще нужен сам resources.zip отдельно в папке public:
|
|
||||||
#install(FILES "${RESOURCES_ZIP}" DESTINATION .)
|
|
||||||
|
|
||||||
add_custom_command(TARGET space-game001 POST_BUILD
|
|
||||||
COMMAND ${CMAKE_COMMAND} --install .
|
|
||||||
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
|
|
||||||
COMMENT "Automatically deploying to public directory..."
|
|
||||||
)
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
# how to build
|
|
||||||
|
|
||||||
|
|
||||||
Activate the environment:
|
|
||||||
```
|
|
||||||
C:\Work\Projects\emsdk\emsdk.bat activate latest
|
|
||||||
C:\Work\Projects\emsdk\emsdk_env.bat
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
Optionally clear cache:
|
|
||||||
```
|
|
||||||
emcc --clear-cache
|
|
||||||
embuilder build sdl2 sdl2_ttf sdl2_image sdl2_image_jpg sdl2_image_png
|
|
||||||
```
|
|
||||||
|
|
||||||
Build:
|
|
||||||
```
|
|
||||||
mkdir build
|
|
||||||
cd build
|
|
||||||
emcmake cmake -G Ninja ..
|
|
||||||
cmake --build .
|
|
||||||
```
|
|
||||||
|
|
||||||
Optionally install:
|
|
||||||
```
|
|
||||||
cmake --install .
|
|
||||||
```
|
|
||||||
|
|
||||||
Run:
|
|
||||||
```
|
|
||||||
emrun --no_browser --port 8080 public
|
|
||||||
```
|
|
||||||
Binary file not shown.
@ -1,168 +0,0 @@
|
|||||||
cmake_minimum_required(VERSION 3.16)
|
|
||||||
|
|
||||||
project(space-game001 LANGUAGES CXX)
|
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
||||||
|
|
||||||
include(${CMAKE_CURRENT_SOURCE_DIR}/../cmake/ThirdParty.cmake)
|
|
||||||
|
|
||||||
# ===========================================
|
|
||||||
# Основной проект space-game001
|
|
||||||
# ===========================================
|
|
||||||
add_executable(space-game001
|
|
||||||
../src/main.cpp
|
|
||||||
../src/Game.cpp
|
|
||||||
../src/Game.h
|
|
||||||
../src/Environment.cpp
|
|
||||||
../src/Environment.h
|
|
||||||
../src/render/Renderer.cpp
|
|
||||||
../src/render/Renderer.h
|
|
||||||
../src/render/ShaderManager.cpp
|
|
||||||
../src/render/ShaderManager.h
|
|
||||||
../src/render/TextureManager.cpp
|
|
||||||
../src/render/TextureManager.h
|
|
||||||
../src/TextModel.cpp
|
|
||||||
../src/TextModel.h
|
|
||||||
../src/AudioPlayerAsync.cpp
|
|
||||||
../src/AudioPlayerAsync.h
|
|
||||||
../src/BoneAnimatedModel.cpp
|
|
||||||
../src/BoneAnimatedModel.h
|
|
||||||
../src/render/OpenGlExtensions.cpp
|
|
||||||
../src/render/OpenGlExtensions.h
|
|
||||||
../src/utils/Utils.cpp
|
|
||||||
../src/utils/Utils.h
|
|
||||||
../src/SparkEmitter.cpp
|
|
||||||
../src/SparkEmitter.h
|
|
||||||
../src/planet/PlanetObject.cpp
|
|
||||||
../src/planet/PlanetObject.h
|
|
||||||
../src/planet/PlanetData.cpp
|
|
||||||
../src/planet/PlanetData.h
|
|
||||||
../src/utils/Perlin.cpp
|
|
||||||
../src/utils/Perlin.h
|
|
||||||
../src/utils/TaskManager.cpp
|
|
||||||
../src/utils/TaskManager.h
|
|
||||||
../src/planet/StoneObject.cpp
|
|
||||||
../src/planet/StoneObject.h
|
|
||||||
../src/render/FrameBuffer.cpp
|
|
||||||
../src/render/FrameBuffer.h
|
|
||||||
../src/UiManager.cpp
|
|
||||||
../src/UiManager.h
|
|
||||||
../src/Projectile.h
|
|
||||||
../src/Projectile.cpp
|
|
||||||
../src/network/NetworkInterface.h
|
|
||||||
../src/network/LocalClient.h
|
|
||||||
../src/network/LocalClient.cpp
|
|
||||||
../src/network/ClientState.h
|
|
||||||
../src/network/ClientState.cpp
|
|
||||||
../src/network/WebSocketClient.h
|
|
||||||
../src/network/WebSocketClient.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
# Установка проекта по умолчанию для Visual Studio
|
|
||||||
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT space-game001)
|
|
||||||
|
|
||||||
# include-пути проекта
|
|
||||||
target_include_directories(space-game001 PRIVATE
|
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/../src"
|
|
||||||
)
|
|
||||||
|
|
||||||
set_target_properties(space-game001 PROPERTIES
|
|
||||||
OUTPUT_NAME "space-game001"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Определения препроцессора:
|
|
||||||
# PNG_ENABLED – включает код PNG в TextureManager
|
|
||||||
# SDL_MAIN_HANDLED – отключает переопределение main -> SDL_main
|
|
||||||
target_compile_definitions(space-game001 PRIVATE
|
|
||||||
WIN32_LEAN_AND_MEAN
|
|
||||||
PNG_ENABLED
|
|
||||||
SDL_MAIN_HANDLED
|
|
||||||
# NETWORK
|
|
||||||
# SIMPLIFIED
|
|
||||||
)
|
|
||||||
|
|
||||||
# Линкуем с SDL2main, если он вообще установлен
|
|
||||||
target_link_libraries(space-game001 PRIVATE SDL2main_external_lib)
|
|
||||||
|
|
||||||
# Линкуем сторонние библиотеки
|
|
||||||
target_link_libraries(space-game001 PRIVATE
|
|
||||||
SDL2_external_lib
|
|
||||||
libpng_external_lib
|
|
||||||
zlib_external_lib
|
|
||||||
libzip_external_lib
|
|
||||||
freetype_external_lib
|
|
||||||
SDL2_ttf_external_lib
|
|
||||||
eigen_external_lib
|
|
||||||
boost_external_lib
|
|
||||||
)
|
|
||||||
|
|
||||||
# Линкуем OpenGL (Windows)
|
|
||||||
if(WIN32)
|
|
||||||
target_link_libraries(space-game001 PRIVATE opengl32)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# ===========================================
|
|
||||||
# Копирование SDL2d.dll и zlibd.dll рядом с exe
|
|
||||||
# ===========================================
|
|
||||||
if (WIN32)
|
|
||||||
|
|
||||||
# SDL2: в Debug - SDL2d.dll, в Release - SDL2.dll
|
|
||||||
set(SDL2_DLL_SRC "$<IF:$<CONFIG:Debug>,${SDL2_INSTALL_DIR}/bin/SDL2d.dll,${SDL2_INSTALL_DIR}/bin/SDL2.dll>")
|
|
||||||
set(SDL2_DLL_DST "$<IF:$<CONFIG:Debug>,$<TARGET_FILE_DIR:space-game001>/SDL2d.dll,$<TARGET_FILE_DIR:space-game001>/SDL2.dll>")
|
|
||||||
|
|
||||||
|
|
||||||
set(LIBZIP_DLL_SRC "$<IF:$<CONFIG:Debug>,${LIBZIP_BASE_DIR}-Debug/bin/zip.dll,${LIBZIP_BASE_DIR}-Release/bin/zip.dll>")
|
|
||||||
|
|
||||||
set(ZLIB_DLL_SRC "$<IF:$<CONFIG:Debug>,${ZLIB_INSTALL_DIR}/bin/zlibd.dll,${ZLIB_INSTALL_DIR}/bin/zlib.dll>")
|
|
||||||
set(ZLIB_DLL_DST "$<IF:$<CONFIG:Debug>,$<TARGET_FILE_DIR:space-game001>/zlibd.dll,$<TARGET_FILE_DIR:space-game001>/zlib.dll>")
|
|
||||||
|
|
||||||
set(SDL2TTF_DLL_SRC "$<IF:$<CONFIG:Debug>,${SDL2TTF_BASE_DIR}-Debug/bin/SDL2_ttfd.dll,${SDL2TTF_BASE_DIR}-Release/bin/SDL2_ttf.dll>")
|
|
||||||
|
|
||||||
|
|
||||||
add_custom_command(TARGET space-game001 POST_BUILD
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E echo "Copying DLLs to output folder..."
|
|
||||||
|
|
||||||
# Копируем SDL2 (целевое имя всегда SDL2.dll)
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
|
||||||
"${SDL2_DLL_SRC}"
|
|
||||||
"${SDL2_DLL_DST}"
|
|
||||||
|
|
||||||
# Копируем LIBZIP (целевое имя всегда zip.dll)
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
|
||||||
"${LIBZIP_DLL_SRC}"
|
|
||||||
"$<TARGET_FILE_DIR:space-game001>/zip.dll"
|
|
||||||
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
|
||||||
"${ZLIB_DLL_SRC}"
|
|
||||||
"${ZLIB_DLL_DST}"
|
|
||||||
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
|
||||||
"${SDL2TTF_DLL_SRC}"
|
|
||||||
"$<TARGET_FILE_DIR:space-game001>"
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# ===========================================
|
|
||||||
# Копирование ресурсов после сборки
|
|
||||||
# ===========================================
|
|
||||||
|
|
||||||
# Какие папки с ресурсами нужно копировать
|
|
||||||
set(RUNTIME_RESOURCE_DIRS
|
|
||||||
"resources"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Копируем ресурсы и шейдеры в папку exe и в корень build/
|
|
||||||
foreach(resdir IN LISTS RUNTIME_RESOURCE_DIRS)
|
|
||||||
add_custom_command(TARGET space-game001 POST_BUILD
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E echo "Copying ${resdir} to runtime folders..."
|
|
||||||
# 1) туда, где лежит exe (build/Debug, build/Release и т.п.)
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
|
||||||
"${CMAKE_SOURCE_DIR}/../${resdir}"
|
|
||||||
"$<TARGET_FILE_DIR:space-game001>/${resdir}"
|
|
||||||
# 2) в корень build, если захочешь запускать из этой папки
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
|
||||||
"${CMAKE_SOURCE_DIR}/../${resdir}"
|
|
||||||
"${CMAKE_BINARY_DIR}/${resdir}"
|
|
||||||
)
|
|
||||||
endforeach()
|
|
||||||
BIN
resources/DefaultMaterial_BaseColor.png
(Stored with Git LFS)
Normal file
BIN
resources/DefaultMaterial_BaseColor.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
resources/DefaultMaterial_BaseColor_shine.png
(Stored with Git LFS)
BIN
resources/DefaultMaterial_BaseColor_shine.png
(Stored with Git LFS)
Binary file not shown.
BIN
resources/MainCharacter_Base_color_sRGB.png
(Stored with Git LFS)
BIN
resources/MainCharacter_Base_color_sRGB.png
(Stored with Git LFS)
Binary file not shown.
BIN
resources/box/box.png
(Stored with Git LFS)
BIN
resources/box/box.png
(Stored with Git LFS)
Binary file not shown.
BIN
resources/button.png
(Stored with Git LFS)
Normal file
BIN
resources/button.png
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"emissionPoints": [
|
|
||||||
{ "position": [0.0, 0.0, 0.0] }
|
|
||||||
],
|
|
||||||
"texture": "resources/spark_white.png",
|
|
||||||
"speedRange": [10.0, 30.0],
|
|
||||||
"zSpeedRange": [-1.0, 1.0],
|
|
||||||
"scaleRange": [0.5, 1.0],
|
|
||||||
"lifeTimeRange": [200.0, 800.0],
|
|
||||||
"emissionRate": 50.0,
|
|
||||||
"maxParticles": 5,
|
|
||||||
"particleSize": 2,
|
|
||||||
"biasX": 0.1,
|
|
||||||
"shaderProgramName": "default"
|
|
||||||
}
|
|
||||||
@ -1,55 +0,0 @@
|
|||||||
{
|
|
||||||
"root": {
|
|
||||||
"type": "LinearLayout",
|
|
||||||
"orientation": "vertical",
|
|
||||||
"align": "center",
|
|
||||||
"x": 0,
|
|
||||||
"y": 0,
|
|
||||||
"width": 1920,
|
|
||||||
"height": 1080,
|
|
||||||
"background": {
|
|
||||||
"color": [0, 0, 0, 0.7]
|
|
||||||
},
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"type": "Button",
|
|
||||||
"name": "gameOverText",
|
|
||||||
"x": 350,
|
|
||||||
"y": 400,
|
|
||||||
"width": 600,
|
|
||||||
"height": 150,
|
|
||||||
"textures": {
|
|
||||||
"normal": "resources/gameover.png",
|
|
||||||
"hover": "resources/gameover.png",
|
|
||||||
"pressed": "resources/gameover.png"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "Button",
|
|
||||||
"name": "restartButton",
|
|
||||||
"x": 350,
|
|
||||||
"y": 300,
|
|
||||||
"width": 300,
|
|
||||||
"height": 80,
|
|
||||||
"textures": {
|
|
||||||
"normal": "resources/shoot_normal.png",
|
|
||||||
"hover": "resources/shoot_normal.png",
|
|
||||||
"pressed": "resources/shoot_normal.png"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "Button",
|
|
||||||
"name": "gameOverExitButton",
|
|
||||||
"x": 650,
|
|
||||||
"y": 300,
|
|
||||||
"width": 300,
|
|
||||||
"height": 80,
|
|
||||||
"textures": {
|
|
||||||
"normal": "resources/sand2.png",
|
|
||||||
"hover": "resources/sand2.png",
|
|
||||||
"pressed": "resources/sand2.png"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,72 +0,0 @@
|
|||||||
{
|
|
||||||
"root": {
|
|
||||||
"type": "FrameLayout",
|
|
||||||
"x": 0,
|
|
||||||
"y": 0,
|
|
||||||
"width": 1280,
|
|
||||||
"height": 720,
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"type": "FrameLayout",
|
|
||||||
"name": "centerPanel",
|
|
||||||
"x": 480,
|
|
||||||
"y": 160,
|
|
||||||
"width": 320,
|
|
||||||
"height": 400,
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"type": "LinearLayout",
|
|
||||||
"name": "settingsButtons",
|
|
||||||
"orientation": "vertical",
|
|
||||||
"spacing": 10,
|
|
||||||
"x": 0,
|
|
||||||
"y": 0,
|
|
||||||
"width": 300,
|
|
||||||
"height": 300,
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"type": "Button",
|
|
||||||
"name": "Opt1",
|
|
||||||
"x": 100,
|
|
||||||
"y": 300,
|
|
||||||
"width": 200,
|
|
||||||
"height": 50,
|
|
||||||
"textures": {
|
|
||||||
"normal": "resources/sand2.png",
|
|
||||||
"hover": "resources/sand2.png",
|
|
||||||
"pressed": "resources/sand2.png"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "Button",
|
|
||||||
"name": "Opt2",
|
|
||||||
"x": 100,
|
|
||||||
"y": 200,
|
|
||||||
"width": 200,
|
|
||||||
"height": 50,
|
|
||||||
"textures": {
|
|
||||||
"normal": "resources/sand2.png",
|
|
||||||
"hover": "resources/sand2.png",
|
|
||||||
"pressed": "resources/sand2.png"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "Button",
|
|
||||||
"name": "backButton",
|
|
||||||
"x": 100,
|
|
||||||
"y": 100,
|
|
||||||
"width": 200,
|
|
||||||
"height": 50,
|
|
||||||
"textures": {
|
|
||||||
"normal": "resources/sand2.png",
|
|
||||||
"hover": "resources/sand2.png",
|
|
||||||
"pressed": "resources/sand2.png"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"emissionRate": 100.0,
|
|
||||||
"maxParticles": 200,
|
|
||||||
"particleSize": 0.3,
|
|
||||||
"biasX": 0.3,
|
|
||||||
"emissionPoints": [
|
|
||||||
{
|
|
||||||
"position": [-1.3, 0, 0.0]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"position": [1.3, 0.0, 0.0]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"speedRange": [0.5, 2.0],
|
|
||||||
"zSpeedRange": [1.0, 3.0],
|
|
||||||
"scaleRange": [0.8, 1.2],
|
|
||||||
"lifeTimeRange": [600.0, 1400.0],
|
|
||||||
"texture": "resources/spark.png",
|
|
||||||
"shaderProgramName": "default",
|
|
||||||
"vertexShader": "resources/shaders/spark.vertex",
|
|
||||||
"fragmentShader": "resources/shaders/spark.fragment"
|
|
||||||
}
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"emissionPoints": [
|
|
||||||
{ "position": [0.0, 0.0, 0.0] }
|
|
||||||
],
|
|
||||||
"texture": "resources/spark_white.png",
|
|
||||||
"speedRange": [10.0, 30.0],
|
|
||||||
"zSpeedRange": [-1.0, 1.0],
|
|
||||||
"scaleRange": [0.5, 1.0],
|
|
||||||
"lifeTimeRange": [200.0, 800.0],
|
|
||||||
"emissionRate": 50.0,
|
|
||||||
"maxParticles": 10,
|
|
||||||
"particleSize": 0.09,
|
|
||||||
"biasX": 0.1,
|
|
||||||
"shaderProgramName": "default"
|
|
||||||
}
|
|
||||||
@ -1,169 +0,0 @@
|
|||||||
{
|
|
||||||
"root": {
|
|
||||||
"type": "FrameLayout",
|
|
||||||
"x": 0,
|
|
||||||
"y": 0,
|
|
||||||
"width": 1280,
|
|
||||||
"height": 720,
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"type": "FrameLayout",
|
|
||||||
"name": "leftPanel",
|
|
||||||
"x": 100,
|
|
||||||
"y": 100,
|
|
||||||
"width": 320,
|
|
||||||
"height": 400,
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"type": "LinearLayout",
|
|
||||||
"name": "mainButtons",
|
|
||||||
"orientation": "vertical",
|
|
||||||
"spacing": 10,
|
|
||||||
"x": 0,
|
|
||||||
"y": 0,
|
|
||||||
"width": 300,
|
|
||||||
"height": 300,
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"type": "Button",
|
|
||||||
"name": "playButton",
|
|
||||||
"x": -1000,
|
|
||||||
"y": 500,
|
|
||||||
"width": 200,
|
|
||||||
"height": 50,
|
|
||||||
"animations": {
|
|
||||||
"buttonsExit": {
|
|
||||||
"repeat": false,
|
|
||||||
"steps": [
|
|
||||||
{
|
|
||||||
"type": "move",
|
|
||||||
"to": [
|
|
||||||
-400,
|
|
||||||
0
|
|
||||||
],
|
|
||||||
"duration": 1.0,
|
|
||||||
"easing": "easein"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"textures": {
|
|
||||||
"normal": "./resources/sand2.png",
|
|
||||||
"hover": "./resources/sand2.png",
|
|
||||||
"pressed": "./resources/sand2.png"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "Button",
|
|
||||||
"name": "settingsButton",
|
|
||||||
"x": -1000,
|
|
||||||
"y": 400,
|
|
||||||
"width": 200,
|
|
||||||
"height": 50,
|
|
||||||
"animations": {
|
|
||||||
"buttonsExit": {
|
|
||||||
"repeat": false,
|
|
||||||
"steps": [
|
|
||||||
{
|
|
||||||
"type": "wait",
|
|
||||||
"duration": 0.5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "move",
|
|
||||||
"to": [
|
|
||||||
-400,
|
|
||||||
0
|
|
||||||
],
|
|
||||||
"duration": 1.0,
|
|
||||||
"easing": "easein"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"textures": {
|
|
||||||
"normal": "./resources/sand2.png",
|
|
||||||
"hover": "./resources/sand2.png",
|
|
||||||
"pressed": "./resources/sand2.png"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "Button",
|
|
||||||
"name": "exitButton",
|
|
||||||
"x": -1000,
|
|
||||||
"y": 300,
|
|
||||||
"width": 200,
|
|
||||||
"height": 50,
|
|
||||||
"animations": {
|
|
||||||
"buttonsExit": {
|
|
||||||
"repeat": false,
|
|
||||||
"steps": [
|
|
||||||
{
|
|
||||||
"type": "wait",
|
|
||||||
"duration": 1.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "move",
|
|
||||||
"to": [
|
|
||||||
-400,
|
|
||||||
0
|
|
||||||
],
|
|
||||||
"duration": 1.0,
|
|
||||||
"easing": "easein"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"bgScroll": {
|
|
||||||
"repeat": true,
|
|
||||||
"steps": [
|
|
||||||
{
|
|
||||||
"type": "move",
|
|
||||||
"to": [
|
|
||||||
1280,
|
|
||||||
0
|
|
||||||
],
|
|
||||||
"duration": 5.0,
|
|
||||||
"easing": "linear"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"textures": {
|
|
||||||
"normal": "./resources/sand2.png",
|
|
||||||
"hover": "./resources/sand2.png",
|
|
||||||
"pressed": "./resources/sand2.png"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "Slider",
|
|
||||||
"name": "velocitySlider",
|
|
||||||
"x": 1140,
|
|
||||||
"y": 100,
|
|
||||||
"width": 50,
|
|
||||||
"height": 500,
|
|
||||||
"value": 0.0,
|
|
||||||
"orientation": "vertical",
|
|
||||||
"textures": {
|
|
||||||
"track": "resources/velocitySliderTexture.png",
|
|
||||||
"knob": "resources/velocitySliderButton.png"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "Button",
|
|
||||||
"name": "shootButton",
|
|
||||||
"x": 100,
|
|
||||||
"y": 100,
|
|
||||||
"width": 100,
|
|
||||||
"height": 100,
|
|
||||||
"textures": {
|
|
||||||
"normal": "resources/shoot_normal.png",
|
|
||||||
"hover": "resources/shoot_hover.png",
|
|
||||||
"pressed": "resources/shoot_pressed.png"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BIN
resources/gameover.png
(Stored with Git LFS)
BIN
resources/gameover.png
(Stored with Git LFS)
Binary file not shown.
BIN
resources/musicVolumeBarButton.png
(Stored with Git LFS)
Normal file
BIN
resources/musicVolumeBarButton.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
resources/musicVolumeBarTexture.png
(Stored with Git LFS)
Normal file
BIN
resources/musicVolumeBarTexture.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
resources/rock.png
(Stored with Git LFS)
Normal file
BIN
resources/rock.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
resources/rockdark3.png
(Stored with Git LFS)
BIN
resources/rockdark3.png
(Stored with Git LFS)
Binary file not shown.
BIN
resources/rockx.png
(Stored with Git LFS)
Normal file
BIN
resources/rockx.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
resources/sand.png
(Stored with Git LFS)
Normal file
BIN
resources/sand.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
resources/sand2.png
(Stored with Git LFS)
BIN
resources/sand2.png
(Stored with Git LFS)
Binary file not shown.
BIN
resources/sandx.png
(Stored with Git LFS)
Normal file
BIN
resources/sandx.png
(Stored with Git LFS)
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user