kopia lustrzana https://github.com/jameshball/osci-render
commit
20b367b466
|
@ -107,12 +107,13 @@ MainComponent::~MainComponent() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainComponent::updateFileLabel() {
|
void MainComponent::updateFileLabel() {
|
||||||
if (audioProcessor.getCurrentFileIndex() == -1) {
|
if (audioProcessor.objectServerRendering) {
|
||||||
|
fileLabel.setText("Rendering from Blender", juce::dontSendNotification);
|
||||||
|
} else if (audioProcessor.getCurrentFileIndex() == -1) {
|
||||||
fileLabel.setText("No file open", juce::dontSendNotification);
|
fileLabel.setText("No file open", juce::dontSendNotification);
|
||||||
return;
|
} else {
|
||||||
|
fileLabel.setText(audioProcessor.getCurrentFileName(), juce::dontSendNotification);
|
||||||
}
|
}
|
||||||
|
|
||||||
fileLabel.setText(audioProcessor.getCurrentFileName(), juce::dontSendNotification);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainComponent::resized() {
|
void MainComponent::resized() {
|
||||||
|
|
|
@ -42,6 +42,7 @@ OscirenderAudioProcessorEditor::OscirenderAudioProcessorEditor(OscirenderAudioPr
|
||||||
|
|
||||||
{
|
{
|
||||||
juce::MessageManagerLock lock;
|
juce::MessageManagerLock lock;
|
||||||
|
audioProcessor.fileChangeBroadcaster.addChangeListener(this);
|
||||||
audioProcessor.broadcaster.addChangeListener(this);
|
audioProcessor.broadcaster.addChangeListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,6 +66,7 @@ OscirenderAudioProcessorEditor::~OscirenderAudioProcessorEditor() {
|
||||||
juce::Desktop::getInstance().setDefaultLookAndFeel(nullptr);
|
juce::Desktop::getInstance().setDefaultLookAndFeel(nullptr);
|
||||||
juce::MessageManagerLock lock;
|
juce::MessageManagerLock lock;
|
||||||
audioProcessor.broadcaster.removeChangeListener(this);
|
audioProcessor.broadcaster.removeChangeListener(this);
|
||||||
|
audioProcessor.fileChangeBroadcaster.removeChangeListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// parsersLock must be held
|
// parsersLock must be held
|
||||||
|
@ -206,8 +208,13 @@ void OscirenderAudioProcessorEditor::handleAsyncUpdate() {
|
||||||
|
|
||||||
void OscirenderAudioProcessorEditor::changeListenerCallback(juce::ChangeBroadcaster* source) {
|
void OscirenderAudioProcessorEditor::changeListenerCallback(juce::ChangeBroadcaster* source) {
|
||||||
juce::SpinLock::ScopedLockType lock(audioProcessor.parsersLock);
|
juce::SpinLock::ScopedLockType lock(audioProcessor.parsersLock);
|
||||||
initialiseCodeEditors();
|
if (source == &audioProcessor.broadcaster) {
|
||||||
settings.update();
|
initialiseCodeEditors();
|
||||||
|
settings.update();
|
||||||
|
} else if (source == &audioProcessor.fileChangeBroadcaster) {
|
||||||
|
// triggered when the audioProcessor changes the current file (e.g. to Blender)
|
||||||
|
settings.fileUpdated(audioProcessor.getCurrentFileName());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void OscirenderAudioProcessorEditor::editPerspectiveFunction(bool enable) {
|
void OscirenderAudioProcessorEditor::editPerspectiveFunction(bool enable) {
|
||||||
|
|
|
@ -388,11 +388,13 @@ void OscirenderAudioProcessor::changeCurrentFile(int index) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void OscirenderAudioProcessor::changeSound(ShapeSound::Ptr sound) {
|
void OscirenderAudioProcessor::changeSound(ShapeSound::Ptr sound) {
|
||||||
synth.clearSounds();
|
if (!objectServerRendering || sound == objectServerSound) {
|
||||||
synth.addSound(sound);
|
synth.clearSounds();
|
||||||
for (int i = 0; i < synth.getNumVoices(); i++) {
|
synth.addSound(sound);
|
||||||
auto voice = dynamic_cast<ShapeVoice*>(synth.getVoice(i));
|
for (int i = 0; i < synth.getNumVoices(); i++) {
|
||||||
voice->updateSound(sound.get());
|
auto voice = dynamic_cast<ShapeVoice*>(synth.getVoice(i));
|
||||||
|
voice->updateSound(sound.get());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -405,7 +407,7 @@ std::shared_ptr<FileParser> OscirenderAudioProcessor::getCurrentFileParser() {
|
||||||
}
|
}
|
||||||
|
|
||||||
juce::String OscirenderAudioProcessor::getCurrentFileName() {
|
juce::String OscirenderAudioProcessor::getCurrentFileName() {
|
||||||
if (currentFile == -1) {
|
if (objectServerRendering || currentFile == -1) {
|
||||||
return "";
|
return "";
|
||||||
} else {
|
} else {
|
||||||
return fileNames[currentFile];
|
return fileNames[currentFile];
|
||||||
|
@ -420,6 +422,24 @@ std::shared_ptr<juce::MemoryBlock> OscirenderAudioProcessor::getFileBlock(int in
|
||||||
return fileBlocks[index];
|
return fileBlocks[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OscirenderAudioProcessor::setObjectServerRendering(bool enabled) {
|
||||||
|
{
|
||||||
|
juce::SpinLock::ScopedLockType lock1(parsersLock);
|
||||||
|
|
||||||
|
objectServerRendering = enabled;
|
||||||
|
if (enabled) {
|
||||||
|
changeSound(objectServerSound);
|
||||||
|
} else {
|
||||||
|
changeCurrentFile(currentFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
juce::MessageManagerLock lock;
|
||||||
|
fileChangeBroadcaster.sendChangeMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void OscirenderAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages) {
|
void OscirenderAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages) {
|
||||||
juce::ScopedNoDenormals noDenormals;
|
juce::ScopedNoDenormals noDenormals;
|
||||||
auto totalNumInputChannels = getTotalNumInputChannels();
|
auto totalNumInputChannels = getTotalNumInputChannels();
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
#include "audio/PitchDetector.h"
|
#include "audio/PitchDetector.h"
|
||||||
#include "audio/WobbleEffect.h"
|
#include "audio/WobbleEffect.h"
|
||||||
#include "audio/PerspectiveEffect.h"
|
#include "audio/PerspectiveEffect.h"
|
||||||
|
#include "obj/ObjectServer.h"
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
/**
|
/**
|
||||||
|
@ -187,6 +188,8 @@ public:
|
||||||
std::atomic<int> currentFile = -1;
|
std::atomic<int> currentFile = -1;
|
||||||
|
|
||||||
juce::ChangeBroadcaster broadcaster;
|
juce::ChangeBroadcaster broadcaster;
|
||||||
|
std::atomic<bool> objectServerRendering = false;
|
||||||
|
juce::ChangeBroadcaster fileChangeBroadcaster;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
juce::SpinLock consumerLock;
|
juce::SpinLock consumerLock;
|
||||||
|
@ -203,6 +206,8 @@ public:
|
||||||
juce::SpinLock fontLock;
|
juce::SpinLock fontLock;
|
||||||
juce::Font font = juce::Font(juce::Font::getDefaultSansSerifFontName(), 1.0f, juce::Font::plain);
|
juce::Font font = juce::Font(juce::Font::getDefaultSansSerifFontName(), 1.0f, juce::Font::plain);
|
||||||
|
|
||||||
|
ShapeSound::Ptr objectServerSound = new ShapeSound();
|
||||||
|
|
||||||
void addLuaSlider();
|
void addLuaSlider();
|
||||||
void updateEffectPrecedence();
|
void updateEffectPrecedence();
|
||||||
void updateFileBlock(int index, std::shared_ptr<juce::MemoryBlock> block);
|
void updateFileBlock(int index, std::shared_ptr<juce::MemoryBlock> block);
|
||||||
|
@ -218,6 +223,7 @@ public:
|
||||||
juce::String getCurrentFileName();
|
juce::String getCurrentFileName();
|
||||||
juce::String getFileName(int index);
|
juce::String getFileName(int index);
|
||||||
std::shared_ptr<juce::MemoryBlock> getFileBlock(int index);
|
std::shared_ptr<juce::MemoryBlock> getFileBlock(int index);
|
||||||
|
void setObjectServerRendering(bool enabled);
|
||||||
private:
|
private:
|
||||||
std::atomic<double> volume = 1.0;
|
std::atomic<double> volume = 1.0;
|
||||||
std::atomic<double> threshold = 1.0;
|
std::atomic<double> threshold = 1.0;
|
||||||
|
@ -232,6 +238,7 @@ private:
|
||||||
juce::Synthesiser synth;
|
juce::Synthesiser synth;
|
||||||
|
|
||||||
AudioWebSocketServer softwareOscilloscopeServer{*this};
|
AudioWebSocketServer softwareOscilloscopeServer{*this};
|
||||||
|
ObjectServer objectServer{*this};
|
||||||
|
|
||||||
void updateLuaValues();
|
void updateLuaValues();
|
||||||
void updateObjValues();
|
void updateObjValues();
|
||||||
|
|
|
@ -47,7 +47,7 @@ void SettingsComponent::fileUpdated(juce::String fileName) {
|
||||||
lua.setVisible(false);
|
lua.setVisible(false);
|
||||||
obj.setVisible(false);
|
obj.setVisible(false);
|
||||||
txt.setVisible(false);
|
txt.setVisible(false);
|
||||||
if (fileName.isEmpty()) {
|
if (fileName.isEmpty() || audioProcessor.objectServerRendering) {
|
||||||
// do nothing
|
// do nothing
|
||||||
} else if (extension == ".lua") {
|
} else if (extension == ".lua") {
|
||||||
lua.setVisible(true);
|
lua.setVisible(true);
|
||||||
|
|
|
@ -9,9 +9,13 @@ ShapeSound::ShapeSound(std::shared_ptr<FileParser> parser) : parser(parser) {
|
||||||
producer->startThread();
|
producer->startThread();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ShapeSound::ShapeSound() {}
|
||||||
|
|
||||||
ShapeSound::~ShapeSound() {
|
ShapeSound::~ShapeSound() {
|
||||||
frames.kill();
|
frames.kill();
|
||||||
producer->stopThread(1000);
|
if (producer != nullptr) {
|
||||||
|
producer->stopThread(1000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ShapeSound::appliesToNote(int note) {
|
bool ShapeSound::appliesToNote(int note) {
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
class ShapeSound : public juce::SynthesiserSound, public FrameConsumer {
|
class ShapeSound : public juce::SynthesiserSound, public FrameConsumer {
|
||||||
public:
|
public:
|
||||||
ShapeSound(std::shared_ptr<FileParser> parser);
|
ShapeSound(std::shared_ptr<FileParser> parser);
|
||||||
|
ShapeSound();
|
||||||
~ShapeSound() override;
|
~ShapeSound() override;
|
||||||
|
|
||||||
bool appliesToNote(int note) override;
|
bool appliesToNote(int note) override;
|
||||||
|
|
|
@ -88,10 +88,11 @@ void ShapeVoice::renderNextBlock(juce::AudioSampleBuffer& outputBuffer, int star
|
||||||
bool renderingSample = true;
|
bool renderingSample = true;
|
||||||
|
|
||||||
if (sound.load() != nullptr) {
|
if (sound.load() != nullptr) {
|
||||||
renderingSample = sound.load()->parser->isSample();
|
auto parser = sound.load()->parser;
|
||||||
|
renderingSample = parser != nullptr && parser->isSample();
|
||||||
|
|
||||||
if (renderingSample) {
|
if (renderingSample) {
|
||||||
channels = sound.load()->parser->nextSample();
|
channels = parser->nextSample();
|
||||||
} else if (currentShape < frame.size()) {
|
} else if (currentShape < frame.size()) {
|
||||||
auto& shape = frame[currentShape];
|
auto& shape = frame[currentShape];
|
||||||
double length = shape->length();
|
double length = shape->length();
|
||||||
|
|
|
@ -0,0 +1,188 @@
|
||||||
|
#include "ObjectServer.h"
|
||||||
|
#include "../PluginProcessor.h"
|
||||||
|
#include "../shape/Line.h"
|
||||||
|
|
||||||
|
ObjectServer::ObjectServer(OscirenderAudioProcessor& p) : audioProcessor(p), juce::Thread("Object Server") {
|
||||||
|
startThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectServer::~ObjectServer() {
|
||||||
|
stopThread(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ObjectServer::run() {
|
||||||
|
if (socket.createListener(51677, "127.0.0.1")) {
|
||||||
|
// preallocating a large buffer to avoid allocations in the loop
|
||||||
|
std::unique_ptr<char[]> message{ new char[10 * 1024 * 1024] };
|
||||||
|
|
||||||
|
while (!threadShouldExit()) {
|
||||||
|
if (socket.waitUntilReady(true, 200)) {
|
||||||
|
std::unique_ptr<juce::StreamingSocket> connection(socket.waitForNextConnection());
|
||||||
|
|
||||||
|
if (connection != nullptr) {
|
||||||
|
audioProcessor.setObjectServerRendering(true);
|
||||||
|
|
||||||
|
while (!threadShouldExit()) {
|
||||||
|
if (connection->waitUntilReady(true, 200) == 1) {
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
// read until we get a newline
|
||||||
|
while (!threadShouldExit()) {
|
||||||
|
char buffer[1024];
|
||||||
|
int bytesRead = connection->read(buffer, sizeof(buffer), false);
|
||||||
|
|
||||||
|
if (bytesRead <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::memcpy(message.get() + i, buffer, bytesRead);
|
||||||
|
i += bytesRead;
|
||||||
|
|
||||||
|
if (message[i] == '\n') {
|
||||||
|
message[i] = '\0';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strncmp(message.get(), "CLOSE", 5) == 0) {
|
||||||
|
connection->close();
|
||||||
|
audioProcessor.setObjectServerRendering(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// format of json is:
|
||||||
|
// {
|
||||||
|
// "objects": [
|
||||||
|
// {
|
||||||
|
// "name": "Line Art",
|
||||||
|
// "vertices": [
|
||||||
|
// [
|
||||||
|
// {
|
||||||
|
// "x": double value,
|
||||||
|
// "y": double value,
|
||||||
|
// "z": double value
|
||||||
|
// },
|
||||||
|
// ...
|
||||||
|
// ],
|
||||||
|
// ...
|
||||||
|
// ],
|
||||||
|
// "matrix": [
|
||||||
|
// 16 double values
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// ],
|
||||||
|
// "focalLength": double value
|
||||||
|
// }
|
||||||
|
|
||||||
|
auto json = juce::JSON::parse(message.get());
|
||||||
|
|
||||||
|
auto objects = *json.getProperty("objects", juce::Array<juce::var>()).getArray();
|
||||||
|
std::vector<std::vector<double>> allMatrices;
|
||||||
|
std::vector<std::vector<std::vector<Vector3D>>> allVertices;
|
||||||
|
|
||||||
|
double focalLength = json.getProperty("focalLength", 1);
|
||||||
|
|
||||||
|
for (int i = 0; i < objects.size(); i++) {
|
||||||
|
auto verticesArray = *objects[i].getProperty("vertices", juce::Array<juce::var>()).getArray();
|
||||||
|
std::vector<std::vector<Vector3D>> vertices;
|
||||||
|
|
||||||
|
for (auto& vertexArrayVar : verticesArray) {
|
||||||
|
vertices.push_back(std::vector<Vector3D>());
|
||||||
|
auto& vertexArray = *vertexArrayVar.getArray();
|
||||||
|
for (auto& vertex : vertexArray) {
|
||||||
|
double x = vertex.getProperty("x", 0);
|
||||||
|
double y = vertex.getProperty("y", 0);
|
||||||
|
double z = vertex.getProperty("z", 0);
|
||||||
|
vertices[vertices.size() - 1].push_back(Vector3D(x, y, z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto matrix = *objects[i].getProperty("matrix", juce::Array<juce::var>()).getArray();
|
||||||
|
|
||||||
|
allMatrices.push_back(std::vector<double>());
|
||||||
|
for (auto& value : matrix) {
|
||||||
|
allMatrices[i].push_back(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::vector<Vector3D>> reorderedVertices;
|
||||||
|
|
||||||
|
if (vertices.size() > 0 && matrix.size() == 16) {
|
||||||
|
std::vector<bool> visited = std::vector<bool>(vertices.size(), false);
|
||||||
|
std::vector<int> order = std::vector<int>(vertices.size(), 0);
|
||||||
|
visited[0] = true;
|
||||||
|
|
||||||
|
auto endPoint = vertices[0].back();
|
||||||
|
|
||||||
|
for (int i = 1; i < vertices.size(); i++) {
|
||||||
|
int minPath = 0;
|
||||||
|
double minDistance = 9999999;
|
||||||
|
for (int j = 0; j < vertices.size(); j++) {
|
||||||
|
if (!visited[j]) {
|
||||||
|
auto startPoint = vertices[j][0];
|
||||||
|
|
||||||
|
double diffX = endPoint.x - startPoint.x;
|
||||||
|
double diffY = endPoint.y - startPoint.y;
|
||||||
|
double diffZ = endPoint.z - startPoint.z;
|
||||||
|
|
||||||
|
double distance = std::sqrt(diffX * diffX + diffY * diffY + diffZ * diffZ);
|
||||||
|
if (distance < minDistance) {
|
||||||
|
minPath = j;
|
||||||
|
minDistance = distance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
visited[minPath] = true;
|
||||||
|
order[i] = minPath;
|
||||||
|
endPoint = vertices[minPath].back();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < vertices.size(); i++) {
|
||||||
|
std::vector<Vector3D> reorderedVertex;
|
||||||
|
int index = order[i];
|
||||||
|
for (int j = 0; j < vertices[index].size(); j++) {
|
||||||
|
reorderedVertex.push_back(vertices[index][j]);
|
||||||
|
}
|
||||||
|
reorderedVertices.push_back(reorderedVertex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allVertices.push_back(reorderedVertices);
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate a frame from the vertices and matrix
|
||||||
|
std::vector<std::unique_ptr<Shape>> frame;
|
||||||
|
|
||||||
|
for (int i = 0; i < objects.size(); i++) {
|
||||||
|
for (int j = 0; j < allVertices[i].size(); j++) {
|
||||||
|
for (int k = 0; k < allVertices[i][j].size() - 1; k++) {
|
||||||
|
auto start = allVertices[i][j][k];
|
||||||
|
auto end = allVertices[i][j][k + 1];
|
||||||
|
|
||||||
|
// multiply the start and end points by the matrix
|
||||||
|
double rotatedX = start.x * allMatrices[i][0] + start.y * allMatrices[i][1] + start.z * allMatrices[i][2] + allMatrices[i][3];
|
||||||
|
double rotatedY = start.x * allMatrices[i][4] + start.y * allMatrices[i][5] + start.z * allMatrices[i][6] + allMatrices[i][7];
|
||||||
|
double rotatedZ = start.x * allMatrices[i][8] + start.y * allMatrices[i][9] + start.z * allMatrices[i][10] + allMatrices[i][11];
|
||||||
|
|
||||||
|
double rotatedX2 = end.x * allMatrices[i][0] + end.y * allMatrices[i][1] + end.z * allMatrices[i][2] + allMatrices[i][3];
|
||||||
|
double rotatedY2 = end.x * allMatrices[i][4] + end.y * allMatrices[i][5] + end.z * allMatrices[i][6] + allMatrices[i][7];
|
||||||
|
double rotatedZ2 = end.x * allMatrices[i][8] + end.y * allMatrices[i][9] + end.z * allMatrices[i][10] + allMatrices[i][11];
|
||||||
|
|
||||||
|
double x = rotatedX * focalLength / rotatedZ;
|
||||||
|
double y = rotatedY * focalLength / rotatedZ;
|
||||||
|
|
||||||
|
double x2 = rotatedX2 * focalLength / rotatedZ2;
|
||||||
|
double y2 = rotatedY2 * focalLength / rotatedZ2;
|
||||||
|
|
||||||
|
frame.push_back(std::make_unique<Line>(x, y, x2, y2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
audioProcessor.objectServerSound->addFrame(frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <JuceHeader.h>
|
||||||
|
|
||||||
|
class OscirenderAudioProcessor;
|
||||||
|
class ObjectServer : public juce::Thread {
|
||||||
|
public:
|
||||||
|
ObjectServer(OscirenderAudioProcessor& p);
|
||||||
|
~ObjectServer();
|
||||||
|
|
||||||
|
void run() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
OscirenderAudioProcessor& audioProcessor;
|
||||||
|
|
||||||
|
juce::StreamingSocket socket;
|
||||||
|
};
|
|
@ -390,6 +390,9 @@
|
||||||
<FILE id="ix12FT" name="Camera.h" compile="0" resource="0" file="Source/obj/Camera.h"/>
|
<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="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="TMrur0" name="Line3D.h" compile="0" resource="0" file="Source/obj/Line3D.h"/>
|
||||||
|
<FILE id="Yfpzzn" name="ObjectServer.cpp" compile="1" resource="0"
|
||||||
|
file="Source/obj/ObjectServer.cpp"/>
|
||||||
|
<FILE id="CqYgqM" name="ObjectServer.h" compile="0" resource="0" file="Source/obj/ObjectServer.h"/>
|
||||||
<FILE id="a4ILpa" name="tiny_obj_loader.cpp" compile="1" resource="0"
|
<FILE id="a4ILpa" name="tiny_obj_loader.cpp" compile="1" resource="0"
|
||||||
file="Source/obj/tiny_obj_loader.cpp"/>
|
file="Source/obj/tiny_obj_loader.cpp"/>
|
||||||
<FILE id="YdfYbM" name="tiny_obj_loader.h" compile="0" resource="0"
|
<FILE id="YdfYbM" name="tiny_obj_loader.h" compile="0" resource="0"
|
||||||
|
|
Ładowanie…
Reference in New Issue