diff --git a/.gitignore b/.gitignore index 30e79dc..1761600 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/Source/MainComponent.cpp b/Source/MainComponent.cpp index d143909..249e618 100644 --- a/Source/MainComponent.cpp +++ b/Source/MainComponent.cpp @@ -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()); } }); }; diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 2407fe6..b29b067 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -139,20 +139,20 @@ void OscirenderAudioProcessor::updateAngleDelta() { void OscirenderAudioProcessor::addFrame(std::vector> 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& 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& 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) { diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index cd13a3d..f7c964b 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -80,7 +80,7 @@ private: std::vector> frameBuffer[10]; int currentShape = 0; - std::vector> frame; + std::vector> frame; double frameLength; double shapeDrawn = 0.0; double frameDrawn = 0.0; diff --git a/Source/obj/Camera.cpp b/Source/obj/Camera.cpp new file mode 100644 index 0000000..220d3cc --- /dev/null +++ b/Source/obj/Camera.cpp @@ -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> Camera::draw(WorldObject& object) +{ + std::vector> 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(startX, startY, endX, endY)); + } + return shapes; +} diff --git a/Source/obj/Camera.h b/Source/obj/Camera.h new file mode 100644 index 0000000..279bca8 --- /dev/null +++ b/Source/obj/Camera.h @@ -0,0 +1,15 @@ +#pragma once +#include +#include +#include "WorldObject.h" +#include "../shape/Shape.h" + +class Camera { +public: + Camera(double focalLength, double x, double y, double z); + + std::vector> draw(WorldObject& object); +private: + double focalLength; + double x, y, z; +}; \ No newline at end of file diff --git a/Source/obj/Line3D.cpp b/Source/obj/Line3D.cpp new file mode 100644 index 0000000..3129f58 --- /dev/null +++ b/Source/obj/Line3D.cpp @@ -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) {} \ No newline at end of file diff --git a/Source/obj/Line3D.h b/Source/obj/Line3D.h new file mode 100644 index 0000000..fb63ab0 --- /dev/null +++ b/Source/obj/Line3D.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +class Line3D { +public: + Line3D(double, double, double, double, double, double); + + double x1, y1, z1, x2, y2, z2; +}; \ No newline at end of file diff --git a/Source/obj/WorldObject.cpp b/Source/obj/WorldObject.cpp new file mode 100644 index 0000000..a7bd7f8 --- /dev/null +++ b/Source/obj/WorldObject.cpp @@ -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)); + } + } +} diff --git a/Source/obj/WorldObject.h b/Source/obj/WorldObject.h new file mode 100644 index 0000000..cf597d9 --- /dev/null +++ b/Source/obj/WorldObject.h @@ -0,0 +1,65 @@ +#pragma once + +#include +#include "Line3D.h" + +// from https://www.keithlantz.net/2011/10/a-preliminary-wavefront-obj-loader-in-c/ +struct vertex { + std::vector 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 vertex; + std::vector texture; + std::vector normal; +}; + +class WorldObject { +public: + WorldObject(juce::InputStream&); + + double rotateX = 0.0, rotateY = 0.0, rotateZ = 0.0; + + std::vector edges; +private: + std::vector vertices; + std::vector texcoords; + std::vector normals; + std::vector parameters; + std::vector faces; +}; \ No newline at end of file diff --git a/Source/parser/FileParser.cpp b/Source/parser/FileParser.cpp index 36641cf..b5ff24a 100644 --- a/Source/parser/FileParser.cpp +++ b/Source/parser/FileParser.cpp @@ -3,10 +3,17 @@ FileParser::FileParser() {} -void FileParser::parse() { +void FileParser::parse(juce::String extension, std::unique_ptr stream) { + if (extension == ".obj") { + object = std::make_unique(*stream); + camera = std::make_unique(1.0, 0, 0, -1.0); + } } std::vector> FileParser::next() { + if (object != nullptr && camera != nullptr) { + return camera->draw(*object); + } auto shapes = std::vector>(); shapes.push_back(std::make_unique(0.0, 0.0, 1.0, 1.0)); return shapes; diff --git a/Source/parser/FileParser.h b/Source/parser/FileParser.h index 66ec1a7..a2f4f99 100644 --- a/Source/parser/FileParser.h +++ b/Source/parser/FileParser.h @@ -1,14 +1,15 @@ #pragma once -#include #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) override; std::vector> next() override; bool isActive() override; void disable() override; @@ -16,4 +17,7 @@ public: private: bool active = true; + + std::unique_ptr object; + std::unique_ptr camera; }; \ No newline at end of file diff --git a/Source/parser/FrameProducer.cpp b/Source/parser/FrameProducer.cpp index f84ad99..7b5de04 100644 --- a/Source/parser/FrameProducer.cpp +++ b/Source/parser/FrameProducer.cpp @@ -8,7 +8,7 @@ FrameProducer::~FrameProducer() { } void FrameProducer::run() { - while (frameSource.isActive() && !threadShouldExit()) { + while (!threadShouldExit() && frameSource.isActive()) { frameConsumer.addFrame(frameSource.next()); } } diff --git a/Source/parser/FrameSource.h b/Source/parser/FrameSource.h index 84c671f..2ebec8c 100644 --- a/Source/parser/FrameSource.h +++ b/Source/parser/FrameSource.h @@ -1,12 +1,13 @@ #pragma once +#include #include #include #include "../shape/Shape.h" class FrameSource { public: - virtual void parse() = 0; + virtual void parse(juce::String extension, std::unique_ptr) = 0; virtual std::vector> next() = 0; virtual bool isActive() = 0; virtual void disable() = 0; diff --git a/Source/shape/CubicBezierCurve.cpp b/Source/shape/CubicBezierCurve.cpp index ed2f3ea..4108130 100644 --- a/Source/shape/CubicBezierCurve.cpp +++ b/Source/shape/CubicBezierCurve.cpp @@ -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); diff --git a/Source/shape/CubicBezierCurve.h b/Source/shape/CubicBezierCurve.h index b6a0764..663fe81 100644 --- a/Source/shape/CubicBezierCurve.h +++ b/Source/shape/CubicBezierCurve.h @@ -14,6 +14,5 @@ public: double length() override; double x1, y1, x2, y2, x3, y3, x4, y4; - double len = INVALID_LENGTH; }; \ No newline at end of file diff --git a/Source/shape/Line.cpp b/Source/shape/Line.cpp index 9ebfd9c..6054a55 100644 --- a/Source/shape/Line.cpp +++ b/Source/shape/Line.cpp @@ -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); diff --git a/Source/shape/Line.h b/Source/shape/Line.h index 58560bb..feb65b7 100644 --- a/Source/shape/Line.h +++ b/Source/shape/Line.h @@ -14,6 +14,5 @@ public: double length() override; double x1, y1, x2, y2; - double len; }; \ No newline at end of file diff --git a/Source/shape/Shape.h b/Source/shape/Shape.h index 33ef5ce..5b50795 100644 --- a/Source/shape/Shape.h +++ b/Source/shape/Shape.h @@ -17,4 +17,6 @@ public: static double totalLength(std::vector>&); const double INVALID_LENGTH = -1.0; + + double len = INVALID_LENGTH; }; \ No newline at end of file diff --git a/osci-render.jucer b/osci-render.jucer index 515baef..4e3e835 100644 --- a/osci-render.jucer +++ b/osci-render.jucer @@ -33,9 +33,12 @@ - - - + + + + + +