Add support for .obj files (unoptimised)

pull/170/head
James Ball 2023-01-15 22:34:02 +00:00
rodzic 6162a7f203
commit 0efc108e76
20 zmienionych plików z 303 dodań i 53 usunięć

3
.gitignore vendored
Wyświetl plik

@ -31,7 +31,6 @@ x86/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
@ -74,8 +73,8 @@ StyleCopReport.xml
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.obj
*.pch
*.pdb
*.ipdb

Wyświetl plik

@ -16,15 +16,7 @@ MainComponent::MainComponent(OscirenderAudioProcessor& p) : audioProcessor(p) {
chooser->launchAsync(flags, [this](const juce::FileChooser& chooser) {
if (chooser.getURLResult().isLocalFile()) {
auto file = chooser.getResult();
if (file.getFileExtension() == ".obj") {
audioProcessor.parser.parse();
} else if (file.getFileExtension() == ".svg") {
} else if (file.getFileExtension() == ".lua") {
} else if (file.getFileExtension() == ".txt") {
}
audioProcessor.parser.parse(file.getFileExtension(), file.createInputStream());
}
});
};

Wyświetl plik

@ -139,20 +139,20 @@ void OscirenderAudioProcessor::updateAngleDelta() {
void OscirenderAudioProcessor::addFrame(std::vector<std::unique_ptr<Shape>> frame) {
const auto scope = frameFifo.write(1);
if (scope.blockSize1 > 0) {
frameBuffer[scope.startIndex1].clear();
for (auto& shape : frame) {
frameBuffer[scope.startIndex1].push_back(std::move(shape));
}
}
if (scope.blockSize2 > 0) {
frameBuffer[scope.startIndex2].clear();
for (auto& shape : frame) {
frameBuffer[scope.startIndex2].push_back(std::move(shape));
}
}
if (scope.blockSize1 > 0) {
frameBuffer[scope.startIndex1].clear();
for (auto& shape : frame) {
frameBuffer[scope.startIndex1].push_back(std::move(shape));
}
}
if (scope.blockSize2 > 0) {
frameBuffer[scope.startIndex2].clear();
for (auto& shape : frame) {
frameBuffer[scope.startIndex2].push_back(std::move(shape));
}
}
}
void OscirenderAudioProcessor::updateFrame() {
@ -165,21 +165,13 @@ void OscirenderAudioProcessor::updateFrame() {
const auto scope = frameFifo.read(1);
if (scope.blockSize1 > 0) {
frame.clear();
for (auto& shape : frameBuffer[scope.startIndex1]) {
frame.push_back(std::move(shape));
}
frame.swap(frameBuffer[scope.startIndex1]);
} else if (scope.blockSize2 > 0) {
frame.swap(frameBuffer[scope.startIndex2]);
}
if (scope.blockSize2 > 0) {
frame.clear();
for (auto& shape : frameBuffer[scope.startIndex2]) {
frame.push_back(std::move(shape));
}
}
frameLength = Shape::totalLength(frame);
}
frameLength = Shape::totalLength(frame);
}
}
@ -230,8 +222,10 @@ void OscirenderAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, j
channelData[0][sample] = x;
}
frameDrawn += lengthIncrement;
shapeDrawn += lengthIncrement;
// hard cap on how many times it can be over the length to
// prevent audio stuttering
frameDrawn += std::min(lengthIncrement, 20 * length);
shapeDrawn += std::min(lengthIncrement, 20 * length);
// Need to skip all shapes that the lengthIncrement draws over.
// This is especially an issue when there are lots of small lines being
@ -243,7 +237,9 @@ void OscirenderAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, j
currentShape = 0;
break;
}
length = frame[currentShape]->length();
// POTENTIAL TODO: Think of a way to make this more efficient when iterating
// this loop many times
length = frame[currentShape]->len;
}
if (frameDrawn > frameLength) {

Wyświetl plik

@ -80,7 +80,7 @@ private:
std::vector<std::unique_ptr<Shape>> frameBuffer[10];
int currentShape = 0;
std::vector<std::unique_ptr<Shape>> frame;
std::vector<std::unique_ptr<Shape>> frame;
double frameLength;
double shapeDrawn = 0.0;
double frameDrawn = 0.0;

Wyświetl plik

@ -0,0 +1,55 @@
#include "Camera.h"
#include "../shape/Line.h"
Camera::Camera(double focalLength, double x, double y, double z) : focalLength(focalLength), x(x), y(y), z(z) {}
std::vector<std::unique_ptr<Shape>> Camera::draw(WorldObject& object)
{
std::vector<std::unique_ptr<Shape>> shapes;
for (auto& edge : object.edges) {
// rotate around x-axis
double cosValue = std::cos(object.rotateX);
double sinValue = std::sin(object.rotateX);
double y2 = cosValue * edge.y1 - sinValue * edge.z1;
double z2 = sinValue * edge.y1 + cosValue * edge.z1;
// rotate around y-axis
cosValue = std::cos(object.rotateY);
sinValue = std::sin(object.rotateY);
double x2 = cosValue * edge.x1 + sinValue * z2;
double z3 = -sinValue * edge.x1 + cosValue * z2;
// rotate around z-axis
cosValue = cos(object.rotateZ);
sinValue = sin(object.rotateZ);
double x3 = cosValue * x2 - sinValue * y2;
double y3 = sinValue * x2 + cosValue * y2;
double startX = x3 * focalLength / (z3 - z) + x;
double startY = y3 * focalLength / (z3 - z) + y;
// rotate around x-axis
cosValue = std::cos(object.rotateX);
sinValue = std::sin(object.rotateX);
y2 = cosValue * edge.y2 - sinValue * edge.z2;
z2 = sinValue * edge.y2 + cosValue * edge.z2;
// rotate around y-axis
cosValue = std::cos(object.rotateY);
sinValue = std::sin(object.rotateY);
x2 = cosValue * edge.x2 + sinValue * z2;
z3 = -sinValue * edge.x2 + cosValue * z2;
// rotate around z-axis
cosValue = cos(object.rotateZ);
sinValue = sin(object.rotateZ);
x3 = cosValue * x2 - sinValue * y2;
y3 = sinValue * x2 + cosValue * y2;
double endX = x3 * focalLength / (z3 - z) + x;
double endY = y3 * focalLength / (z3 - z) + y;
shapes.push_back(std::make_unique<Line>(startX, startY, endX, endY));
}
return shapes;
}

Wyświetl plik

@ -0,0 +1,15 @@
#pragma once
#include <vector>
#include <memory>
#include "WorldObject.h"
#include "../shape/Shape.h"
class Camera {
public:
Camera(double focalLength, double x, double y, double z);
std::vector<std::unique_ptr<Shape>> draw(WorldObject& object);
private:
double focalLength;
double x, y, z;
};

Wyświetl plik

@ -0,0 +1,3 @@
#include "Line3D.h"
Line3D::Line3D(double x1, double y1, double z1, double x2, double y2, double z2) : x1(x1), y1(y1), z1(z1), x2(x2), y2(y2), z2(z2) {}

Wyświetl plik

@ -0,0 +1,10 @@
#pragma once
#include <JuceHeader.h>
class Line3D {
public:
Line3D(double, double, double, double, double, double);
double x1, y1, z1, x2, y2, z2;
};

Wyświetl plik

@ -0,0 +1,100 @@
#include "WorldObject.h"
WorldObject::WorldObject(juce::InputStream& stream) {
std::string key;
while (!stream.isExhausted()) {
auto line = stream.readNextLine();
key = "";
std::stringstream stringstream(line.toStdString());
stringstream >> key >> std::ws;
if (key == "v") { // vertex
vertex v; float x;
while (!stringstream.eof()) {
stringstream >> x >> std::ws;
v.v.push_back(x);
}
vertices.push_back(v);
}
else if (key == "vp") { // parameter
vertex v; float x;
while (!stringstream.eof()) {
stringstream >> x >> std::ws;
v.v.push_back(x);
}
parameters.push_back(v);
}
else if (key == "vt") { // texture coordinate
vertex v; float x;
while (!stringstream.eof()) {
stringstream >> x >> std::ws;
v.v.push_back(x);
}
texcoords.push_back(v);
}
else if (key == "vn") { // normal
vertex v; float x;
while (!stringstream.eof()) {
stringstream >> x >> std::ws;
v.v.push_back(x);
}
v.normalize();
normals.push_back(v);
}
else if (key == "f") { // face
face f; int v, t, n;
while (!stringstream.eof()) {
stringstream >> v >> std::ws;
f.vertex.push_back(v - 1);
if (stringstream.peek() == '/') {
stringstream.get();
if (stringstream.peek() == '/') {
stringstream.get();
stringstream >> n >> std::ws;
f.normal.push_back(n - 1);
}
else {
stringstream >> t >> std::ws;
f.texture.push_back(t - 1);
if (stringstream.peek() == '/') {
stringstream.get();
stringstream >> n >> std::ws;
f.normal.push_back(n - 1);
}
}
}
}
faces.push_back(f);
}
}
double x = 0.0, y = 0.0, z = 0.0;
double max = 0.0;
for (auto& v : vertices) {
x += v.v[0];
y += v.v[1];
z += v.v[2];
if (std::abs(v.v[0]) > max) max = std::abs(v.v[0]);
if (std::abs(v.v[1]) > max) max = std::abs(v.v[1]);
if (std::abs(v.v[2]) > max) max = std::abs(v.v[2]);
}
x /= vertices.size();
y /= vertices.size();
z /= vertices.size();
// IMPORTANT TODO: get rid of duplicate edges here using an unordered_set
// as there are wayyy too many edges being rendered causing poor performance
for (auto& f : faces) {
for (int i = 0; i < f.vertex.size(); i++) {
double x1 = (vertices[f.vertex[i]].v[0] - x) / max;
double y1 = (vertices[f.vertex[i]].v[1] - y) / max;
double z1 = (vertices[f.vertex[i]].v[2] - z) / max;
double x2 = (vertices[f.vertex[(i + 1) % f.vertex.size()]].v[0] - x) / max;
double y2 = (vertices[f.vertex[(i + 1) % f.vertex.size()]].v[1] - y) / max;
double z2 = (vertices[f.vertex[(i + 1) % f.vertex.size()]].v[2] - z) / max;
edges.push_back(Line3D(x1, y1, z1, x2, y2, z2));
}
}
}

Wyświetl plik

@ -0,0 +1,65 @@
#pragma once
#include <JuceHeader.h>
#include "Line3D.h"
// from https://www.keithlantz.net/2011/10/a-preliminary-wavefront-obj-loader-in-c/
struct vertex {
std::vector<float> v;
void normalize() {
float magnitude = 0.0f;
for (int i = 0; i < v.size(); i++)
magnitude += pow(v[i], 2.0f);
magnitude = sqrt(magnitude);
for (int i = 0; i < v.size(); i++)
v[i] /= magnitude;
}
vertex operator-(vertex v2) {
vertex v3;
if (v.size() != v2.v.size()) {
v3.v.push_back(0.0f);
v3.v.push_back(0.0f);
v3.v.push_back(0.0f);
}
else {
for (int i = 0; i < v.size(); i++)
v3.v.push_back(v[i] - v2.v[i]);
}
return v3;
}
vertex cross(vertex v2) {
vertex v3;
if (v.size() != 3 || v2.v.size() != 3) {
v3.v.push_back(0.0f);
v3.v.push_back(0.0f);
v3.v.push_back(0.0f);
}
else {
v3.v.push_back(v[1] * v2.v[2] - v[2] * v2.v[1]);
v3.v.push_back(v[2] * v2.v[0] - v[0] * v2.v[2]);
v3.v.push_back(v[0] * v2.v[1] - v[1] * v2.v[0]);
}
return v3;
}
};
struct face {
std::vector<int> vertex;
std::vector<int> texture;
std::vector<int> normal;
};
class WorldObject {
public:
WorldObject(juce::InputStream&);
double rotateX = 0.0, rotateY = 0.0, rotateZ = 0.0;
std::vector<Line3D> edges;
private:
std::vector<vertex> vertices;
std::vector<vertex> texcoords;
std::vector<vertex> normals;
std::vector<vertex> parameters;
std::vector<face> faces;
};

Wyświetl plik

@ -3,10 +3,17 @@
FileParser::FileParser() {}
void FileParser::parse() {
void FileParser::parse(juce::String extension, std::unique_ptr<juce::InputStream> stream) {
if (extension == ".obj") {
object = std::make_unique<WorldObject>(*stream);
camera = std::make_unique<Camera>(1.0, 0, 0, -1.0);
}
}
std::vector<std::unique_ptr<Shape>> FileParser::next() {
if (object != nullptr && camera != nullptr) {
return camera->draw(*object);
}
auto shapes = std::vector<std::unique_ptr<Shape>>();
shapes.push_back(std::make_unique<Line>(0.0, 0.0, 1.0, 1.0));
return shapes;

Wyświetl plik

@ -1,14 +1,15 @@
#pragma once
#include <JuceHeader.h>
#include "FrameSource.h"
#include "../shape/Shape.h"
#include "../obj/WorldObject.h"
#include "../obj/Camera.h"
class FileParser : public FrameSource {
public:
FileParser();
void parse() override;
void parse(juce::String extension, std::unique_ptr<juce::InputStream>) override;
std::vector<std::unique_ptr<Shape>> next() override;
bool isActive() override;
void disable() override;
@ -16,4 +17,7 @@ public:
private:
bool active = true;
std::unique_ptr<WorldObject> object;
std::unique_ptr<Camera> camera;
};

Wyświetl plik

@ -8,7 +8,7 @@ FrameProducer::~FrameProducer() {
}
void FrameProducer::run() {
while (frameSource.isActive() && !threadShouldExit()) {
while (!threadShouldExit() && frameSource.isActive()) {
frameConsumer.addFrame(frameSource.next());
}
}

Wyświetl plik

@ -1,12 +1,13 @@
#pragma once
#include <JuceHeader.h>
#include <vector>
#include <memory>
#include "../shape/Shape.h"
class FrameSource {
public:
virtual void parse() = 0;
virtual void parse(juce::String extension, std::unique_ptr<juce::InputStream>) = 0;
virtual std::vector<std::unique_ptr<Shape>> next() = 0;
virtual bool isActive() = 0;
virtual void disable() = 0;

Wyświetl plik

@ -60,7 +60,7 @@ void CubicBezierCurve::translate(double x, double y) {
}
double CubicBezierCurve::length() {
if (len == INVALID_LENGTH) {
if (len < 0) {
// Euclidean distance approximation based on octagonal boundary
double dx = std::abs(x4 - x1);
double dy = std::abs(y4 - y1);

Wyświetl plik

@ -14,6 +14,5 @@ public:
double length() override;
double x1, y1, x2, y2, x3, y3, x4, y4;
double len = INVALID_LENGTH;
};

Wyświetl plik

@ -38,7 +38,7 @@ void Line::translate(double x, double y) {
y2 += y;
}
double Line::length() {
double inline Line::length() {
if (len < 0) {
// Euclidean distance approximation based on octagonal boundary
double dx = std::abs(x2 - x1);

Wyświetl plik

@ -14,6 +14,5 @@ public:
double length() override;
double x1, y1, x2, y2;
double len;
};

Wyświetl plik

@ -17,4 +17,6 @@ public:
static double totalLength(std::vector<std::unique_ptr<Shape>>&);
const double INVALID_LENGTH = -1.0;
double len = INVALID_LENGTH;
};

Wyświetl plik

@ -33,9 +33,12 @@
<FILE id="hCrVUD" name="FrameSource.h" compile="0" resource="0" file="Source/parser/FrameSource.h"/>
</GROUP>
<GROUP id="{E6ED85A9-3843-825F-EF48-BCF81E38F8AD}" name="obj">
<FILE id="aUh7fp" name="LICENSE" compile="0" resource="1" file="Source/obj/LICENSE"/>
<FILE id="npNAJ1" name="obj.c" compile="1" resource="0" file="Source/obj/obj.c"/>
<FILE id="iVQVY6" name="obj.h" compile="0" resource="0" file="Source/obj/obj.h"/>
<FILE id="Tyz6WY" name="Camera.cpp" compile="1" resource="0" file="Source/obj/Camera.cpp"/>
<FILE id="ix12FT" name="Camera.h" compile="0" resource="0" file="Source/obj/Camera.h"/>
<FILE id="JJTNO9" name="Line3D.cpp" compile="1" resource="0" file="Source/obj/Line3D.cpp"/>
<FILE id="TMrur0" name="Line3D.h" compile="0" resource="0" file="Source/obj/Line3D.h"/>
<FILE id="YNsbe9" name="WorldObject.cpp" compile="1" resource="0" file="Source/obj/WorldObject.cpp"/>
<FILE id="SZBVI9" name="WorldObject.h" compile="0" resource="0" file="Source/obj/WorldObject.h"/>
</GROUP>
<FILE id="GKBQ8j" name="MainComponent.cpp" compile="1" resource="0"
file="Source/MainComponent.cpp"/>