mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-07-12 18:38:54 +02:00
495 lines
18 KiB
C++
495 lines
18 KiB
C++
//
|
|
// MetavoxelSystem.cpp
|
|
// interface/src
|
|
//
|
|
// Created by Andrzej Kapolka on 12/10/13.
|
|
// Copyright 2013 High Fidelity, Inc.
|
|
//
|
|
// Distributed under the Apache License, Version 2.0.
|
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
//
|
|
|
|
#include <QMutexLocker>
|
|
#include <QtDebug>
|
|
|
|
#include <glm/gtx/transform.hpp>
|
|
|
|
#include <SharedUtil.h>
|
|
|
|
#include <MetavoxelUtil.h>
|
|
#include <ScriptCache.h>
|
|
|
|
#include "Application.h"
|
|
#include "MetavoxelSystem.h"
|
|
#include "renderer/Model.h"
|
|
|
|
REGISTER_META_OBJECT(SphereRenderer)
|
|
REGISTER_META_OBJECT(StaticModelRenderer)
|
|
|
|
ProgramObject MetavoxelSystem::_program;
|
|
int MetavoxelSystem::_pointScaleLocation;
|
|
|
|
MetavoxelSystem::MetavoxelSystem() :
|
|
_simulateVisitor(_points),
|
|
_buffer(QOpenGLBuffer::VertexBuffer) {
|
|
}
|
|
|
|
void MetavoxelSystem::init() {
|
|
MetavoxelClientManager::init();
|
|
|
|
if (!_program.isLinked()) {
|
|
_program.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + "shaders/metavoxel_point.vert");
|
|
_program.link();
|
|
|
|
_pointScaleLocation = _program.uniformLocation("pointScale");
|
|
}
|
|
_buffer.setUsagePattern(QOpenGLBuffer::DynamicDraw);
|
|
_buffer.create();
|
|
_pointBufferAttribute = AttributeRegistry::getInstance()->registerAttribute(new PointBufferAttribute());
|
|
}
|
|
|
|
MetavoxelLOD MetavoxelSystem::getLOD() const {
|
|
// the LOD threshold is temporarily tied to the avatar LOD parameter
|
|
const float BASE_LOD_THRESHOLD = 0.01f;
|
|
return MetavoxelLOD(Application::getInstance()->getCamera()->getPosition(),
|
|
BASE_LOD_THRESHOLD * Menu::getInstance()->getAvatarLODDistanceMultiplier());
|
|
}
|
|
|
|
void MetavoxelSystem::simulate(float deltaTime) {
|
|
// update the clients
|
|
_points.clear();
|
|
_simulateVisitor.setDeltaTime(deltaTime);
|
|
_simulateVisitor.setOrder(-Application::getInstance()->getViewFrustum()->getDirection());
|
|
update();
|
|
|
|
_buffer.bind();
|
|
int bytes = _points.size() * sizeof(Point);
|
|
if (_buffer.size() < bytes) {
|
|
_buffer.allocate(_points.constData(), bytes);
|
|
} else {
|
|
_buffer.write(0, _points.constData(), bytes);
|
|
}
|
|
_buffer.release();
|
|
}
|
|
|
|
void MetavoxelSystem::render() {
|
|
int viewport[4];
|
|
glGetIntegerv(GL_VIEWPORT, viewport);
|
|
const int VIEWPORT_WIDTH_INDEX = 2;
|
|
const int VIEWPORT_HEIGHT_INDEX = 3;
|
|
float viewportWidth = viewport[VIEWPORT_WIDTH_INDEX];
|
|
float viewportHeight = viewport[VIEWPORT_HEIGHT_INDEX];
|
|
float viewportDiagonal = sqrtf(viewportWidth*viewportWidth + viewportHeight*viewportHeight);
|
|
float worldDiagonal = glm::distance(Application::getInstance()->getViewFrustum()->getNearBottomLeft(),
|
|
Application::getInstance()->getViewFrustum()->getNearTopRight());
|
|
|
|
_program.bind();
|
|
_program.setUniformValue(_pointScaleLocation, viewportDiagonal *
|
|
Application::getInstance()->getViewFrustum()->getNearClip() / worldDiagonal);
|
|
|
|
_buffer.bind();
|
|
|
|
Point* pt = 0;
|
|
glVertexPointer(4, GL_FLOAT, sizeof(Point), &pt->vertex);
|
|
glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(Point), &pt->color);
|
|
glNormalPointer(GL_BYTE, sizeof(Point), &pt->normal);
|
|
|
|
glEnableClientState(GL_VERTEX_ARRAY);
|
|
glEnableClientState(GL_COLOR_ARRAY);
|
|
glEnableClientState(GL_NORMAL_ARRAY);
|
|
|
|
glEnable(GL_VERTEX_PROGRAM_POINT_SIZE_ARB);
|
|
|
|
glDrawArrays(GL_POINTS, 0, _points.size());
|
|
|
|
glDisable(GL_VERTEX_PROGRAM_POINT_SIZE_ARB);
|
|
|
|
glDisableClientState(GL_VERTEX_ARRAY);
|
|
glDisableClientState(GL_COLOR_ARRAY);
|
|
glDisableClientState(GL_NORMAL_ARRAY);
|
|
|
|
_buffer.release();
|
|
|
|
_program.release();
|
|
|
|
foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
|
|
if (node->getType() == NodeType::MetavoxelServer) {
|
|
QMutexLocker locker(&node->getMutex());
|
|
MetavoxelSystemClient* client = static_cast<MetavoxelSystemClient*>(node->getLinkedData());
|
|
if (client) {
|
|
client->guide(_renderVisitor);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
MetavoxelClient* MetavoxelSystem::createClient(const SharedNodePointer& node) {
|
|
return new MetavoxelSystemClient(node, this);
|
|
}
|
|
|
|
void MetavoxelSystem::updateClient(MetavoxelClient* client) {
|
|
MetavoxelClientManager::updateClient(client);
|
|
client->guide(_simulateVisitor);
|
|
}
|
|
|
|
MetavoxelSystem::SimulateVisitor::SimulateVisitor(QVector<Point>& points) :
|
|
SpannerVisitor(QVector<AttributePointer>() << AttributeRegistry::getInstance()->getSpannersAttribute(),
|
|
QVector<AttributePointer>(), QVector<AttributePointer>() << AttributeRegistry::getInstance()->getColorAttribute() <<
|
|
AttributeRegistry::getInstance()->getNormalAttribute() <<
|
|
AttributeRegistry::getInstance()->getSpannerColorAttribute() <<
|
|
AttributeRegistry::getInstance()->getSpannerNormalAttribute()),
|
|
_points(points) {
|
|
}
|
|
|
|
bool MetavoxelSystem::SimulateVisitor::visit(Spanner* spanner, const glm::vec3& clipMinimum, float clipSize) {
|
|
spanner->getRenderer()->simulate(_deltaTime);
|
|
return true;
|
|
}
|
|
|
|
int MetavoxelSystem::SimulateVisitor::visit(MetavoxelInfo& info) {
|
|
SpannerVisitor::visit(info);
|
|
|
|
if (!info.isLeaf) {
|
|
return _order;
|
|
}
|
|
QRgb color = info.inputValues.at(0).getInlineValue<QRgb>();
|
|
QRgb normal = info.inputValues.at(1).getInlineValue<QRgb>();
|
|
quint8 alpha = qAlpha(color);
|
|
if (!info.isLODLeaf) {
|
|
if (alpha > 0) {
|
|
Point point = { glm::vec4(info.minimum + glm::vec3(info.size, info.size, info.size) * 0.5f, info.size),
|
|
{ quint8(qRed(color)), quint8(qGreen(color)), quint8(qBlue(color)), alpha },
|
|
{ quint8(qRed(normal)), quint8(qGreen(normal)), quint8(qBlue(normal)) } };
|
|
_points.append(point);
|
|
}
|
|
} else {
|
|
QRgb spannerColor = info.inputValues.at(2).getInlineValue<QRgb>();
|
|
QRgb spannerNormal = info.inputValues.at(3).getInlineValue<QRgb>();
|
|
quint8 spannerAlpha = qAlpha(spannerColor);
|
|
if (spannerAlpha > 0) {
|
|
if (alpha > 0) {
|
|
Point point = { glm::vec4(info.minimum + glm::vec3(info.size, info.size, info.size) * 0.5f, info.size),
|
|
{ quint8(qRed(spannerColor)), quint8(qGreen(spannerColor)), quint8(qBlue(spannerColor)), spannerAlpha },
|
|
{ quint8(qRed(spannerNormal)), quint8(qGreen(spannerNormal)), quint8(qBlue(spannerNormal)) } };
|
|
_points.append(point);
|
|
|
|
} else {
|
|
Point point = { glm::vec4(info.minimum + glm::vec3(info.size, info.size, info.size) * 0.5f, info.size),
|
|
{ quint8(qRed(spannerColor)), quint8(qGreen(spannerColor)), quint8(qBlue(spannerColor)), spannerAlpha },
|
|
{ quint8(qRed(spannerNormal)), quint8(qGreen(spannerNormal)), quint8(qBlue(spannerNormal)) } };
|
|
_points.append(point);
|
|
}
|
|
} else if (alpha > 0) {
|
|
Point point = { glm::vec4(info.minimum + glm::vec3(info.size, info.size, info.size) * 0.5f, info.size),
|
|
{ quint8(qRed(color)), quint8(qGreen(color)), quint8(qBlue(color)), alpha },
|
|
{ quint8(qRed(normal)), quint8(qGreen(normal)), quint8(qBlue(normal)) } };
|
|
_points.append(point);
|
|
}
|
|
}
|
|
return STOP_RECURSION;
|
|
}
|
|
|
|
MetavoxelSystem::RenderVisitor::RenderVisitor() :
|
|
SpannerVisitor(QVector<AttributePointer>() << AttributeRegistry::getInstance()->getSpannersAttribute(),
|
|
QVector<AttributePointer>() << AttributeRegistry::getInstance()->getSpannerMaskAttribute()) {
|
|
}
|
|
|
|
bool MetavoxelSystem::RenderVisitor::visit(Spanner* spanner, const glm::vec3& clipMinimum, float clipSize) {
|
|
spanner->getRenderer()->render(1.0f, SpannerRenderer::DEFAULT_MODE, clipMinimum, clipSize);
|
|
return true;
|
|
}
|
|
|
|
MetavoxelSystemClient::MetavoxelSystemClient(const SharedNodePointer& node, MetavoxelSystem* system) :
|
|
MetavoxelClient(node, system) {
|
|
}
|
|
|
|
int MetavoxelSystemClient::parseData(const QByteArray& packet) {
|
|
// process through sequencer
|
|
QMetaObject::invokeMethod(&_sequencer, "receivedDatagram", Q_ARG(const QByteArray&, packet));
|
|
Application::getInstance()->getBandwidthMeter()->inputStream(BandwidthMeter::METAVOXELS).updateValue(packet.size());
|
|
return packet.size();
|
|
}
|
|
|
|
class BufferBuilder : public MetavoxelVisitor {
|
|
public:
|
|
|
|
BufferBuilder(const MetavoxelLOD& lod);
|
|
|
|
virtual int visit(MetavoxelInfo& info);
|
|
virtual bool postVisit(MetavoxelInfo& info);
|
|
|
|
private:
|
|
|
|
QVector<BufferPointVectorPair> _depthPoints;
|
|
};
|
|
|
|
BufferBuilder::BufferBuilder(const MetavoxelLOD& lod) :
|
|
MetavoxelVisitor(QVector<AttributePointer>() << AttributeRegistry::getInstance()->getColorAttribute() <<
|
|
AttributeRegistry::getInstance()->getNormalAttribute() <<
|
|
Application::getInstance()->getMetavoxels()->getPointBufferAttribute(), QVector<AttributePointer>() <<
|
|
Application::getInstance()->getMetavoxels()->getPointBufferAttribute(), lod) {
|
|
}
|
|
|
|
const int ALPHA_RENDER_THRESHOLD = 0;
|
|
|
|
int BufferBuilder::visit(MetavoxelInfo& info) {
|
|
if (info.inputValues.at(2).getInlineValue<PointBufferPointer>()) {
|
|
info.outputValues[0] = AttributeValue(_outputs.at(0));
|
|
}
|
|
if (_depth >= _depthPoints.size()) {
|
|
_depthPoints.resize(_depth + 1);
|
|
}
|
|
QRgb color = info.inputValues.at(0).getInlineValue<QRgb>();
|
|
quint8 alpha = qAlpha(color);
|
|
if (alpha <= ALPHA_RENDER_THRESHOLD) {
|
|
return info.isLeaf ? STOP_RECURSION : DEFAULT_ORDER;
|
|
}
|
|
QRgb normal = info.inputValues.at(1).getInlineValue<QRgb>();
|
|
BufferPoint point = { glm::vec4(info.minimum + glm::vec3(info.size, info.size, info.size) * 0.5f, info.size),
|
|
{ quint8(qRed(color)), quint8(qGreen(color)), quint8(qBlue(color)) },
|
|
{ quint8(qRed(normal)), quint8(qGreen(normal)), quint8(qBlue(normal)) } };
|
|
if (info.isLeaf) {
|
|
_depthPoints[_depth].first.append(point);
|
|
return STOP_RECURSION;
|
|
}
|
|
_depthPoints[_depth].second.append(point);
|
|
return DEFAULT_ORDER;
|
|
}
|
|
|
|
const int BUFFER_LEVELS = 5;
|
|
|
|
bool BufferBuilder::postVisit(MetavoxelInfo& info) {
|
|
if (_depth % BUFFER_LEVELS != 0) {
|
|
return false;
|
|
}
|
|
QVector<int> offsets;
|
|
offsets.append(0);
|
|
int leafCount = 0;
|
|
int totalPoints = 0;
|
|
int lastDepth = qMin(_depth + BUFFER_LEVELS, _depthPoints.size());
|
|
for (int i = _depth; i < lastDepth; i++) {
|
|
const BufferPointVectorPair& pair = _depthPoints.at(i);
|
|
offsets.append(totalPoints += ((leafCount += pair.first.size()) + pair.second.size()));
|
|
}
|
|
QOpenGLBuffer buffer;
|
|
buffer.setUsagePattern(QOpenGLBuffer::StaticDraw);
|
|
buffer.create();
|
|
buffer.bind();
|
|
buffer.allocate(totalPoints * sizeof(BufferPoint));
|
|
int offset = 0;
|
|
for (int i = _depth; i < lastDepth; i++) {
|
|
// write the internal nodes from the current level
|
|
BufferPointVector& internal = _depthPoints[i].second;
|
|
int length = internal.size() * sizeof(BufferPoint);
|
|
buffer.write(offset, internal.constData(), length);
|
|
offset += length;
|
|
internal.clear();
|
|
|
|
// and the leaves from the top down
|
|
for (int j = _depth; j <= i; j++) {
|
|
const BufferPointVector& leaves = _depthPoints.at(j).first;
|
|
length = leaves.size() * sizeof(BufferPoint);
|
|
buffer.write(offset, leaves.constData(), length);
|
|
offset += length;
|
|
}
|
|
}
|
|
// clear the leaves now that we're done with them
|
|
for (int i = _depth; i < lastDepth; i++) {
|
|
_depthPoints[i].first.clear();
|
|
}
|
|
buffer.release();
|
|
info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(PointBufferPointer(
|
|
new PointBuffer(buffer, offsets, leafCount))));
|
|
return true;
|
|
}
|
|
|
|
void MetavoxelSystemClient::dataChanged(const MetavoxelData& oldData) {
|
|
BufferBuilder builder(_remoteDataLOD);
|
|
_data.guideToDifferent(oldData, builder);
|
|
}
|
|
|
|
void MetavoxelSystemClient::sendDatagram(const QByteArray& data) {
|
|
NodeList::getInstance()->writeDatagram(data, _node);
|
|
Application::getInstance()->getBandwidthMeter()->outputStream(BandwidthMeter::METAVOXELS).updateValue(data.size());
|
|
}
|
|
|
|
PointBuffer::PointBuffer(const QOpenGLBuffer& buffer, const QVector<int>& offsets, int lastLeafCount) :
|
|
_buffer(buffer),
|
|
_offsets(offsets),
|
|
_lastLeafCount(lastLeafCount) {
|
|
}
|
|
|
|
void PointBuffer::render(int level) {
|
|
_buffer.bind();
|
|
|
|
BufferPoint* point = 0;
|
|
glVertexPointer(4, GL_FLOAT, sizeof(BufferPoint), &point->vertex);
|
|
glColorPointer(3, GL_UNSIGNED_BYTE, sizeof(BufferPoint), &point->color);
|
|
glNormalPointer(GL_BYTE, sizeof(BufferPoint), &point->normal);
|
|
|
|
int nextLevel = level + 1;
|
|
if (nextLevel >= _offsets.size()) {
|
|
glDrawArrays(GL_POINTS, _offsets.last() - _lastLeafCount, _lastLeafCount);
|
|
|
|
} else {
|
|
int first = _offsets.at(level);
|
|
glDrawArrays(GL_POINTS, first, _offsets.at(nextLevel) - first);
|
|
}
|
|
_buffer.release();
|
|
}
|
|
|
|
PointBufferAttribute::PointBufferAttribute() :
|
|
InlineAttribute<PointBufferPointer>("pointBuffer") {
|
|
}
|
|
|
|
MetavoxelNode* PointBufferAttribute::createMetavoxelNode(const AttributeValue& value, const MetavoxelNode* original) const {
|
|
return new MetavoxelNode(value, original);
|
|
}
|
|
|
|
bool PointBufferAttribute::merge(void*& parent, void* children[], bool postRead) const {
|
|
for (int i = 0; i < MERGE_COUNT; i++) {
|
|
if (decodeInline<PointBufferPointer>(children[i])) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
AttributeValue PointBufferAttribute::inherit(const AttributeValue& parentValue) const {
|
|
return AttributeValue(parentValue.getAttribute());
|
|
}
|
|
|
|
static void enableClipPlane(GLenum plane, float x, float y, float z, float w) {
|
|
GLdouble coefficients[] = { x, y, z, w };
|
|
glClipPlane(plane, coefficients);
|
|
glEnable(plane);
|
|
}
|
|
|
|
void ClippedRenderer::render(float alpha, Mode mode, const glm::vec3& clipMinimum, float clipSize) {
|
|
if (clipSize == 0.0f) {
|
|
renderUnclipped(alpha, mode);
|
|
return;
|
|
}
|
|
enableClipPlane(GL_CLIP_PLANE0, -1.0f, 0.0f, 0.0f, clipMinimum.x + clipSize);
|
|
enableClipPlane(GL_CLIP_PLANE1, 1.0f, 0.0f, 0.0f, -clipMinimum.x);
|
|
enableClipPlane(GL_CLIP_PLANE2, 0.0f, -1.0f, 0.0f, clipMinimum.y + clipSize);
|
|
enableClipPlane(GL_CLIP_PLANE3, 0.0f, 1.0f, 0.0f, -clipMinimum.y);
|
|
enableClipPlane(GL_CLIP_PLANE4, 0.0f, 0.0f, -1.0f, clipMinimum.z + clipSize);
|
|
enableClipPlane(GL_CLIP_PLANE5, 0.0f, 0.0f, 1.0f, -clipMinimum.z);
|
|
|
|
renderUnclipped(alpha, mode);
|
|
|
|
glDisable(GL_CLIP_PLANE0);
|
|
glDisable(GL_CLIP_PLANE1);
|
|
glDisable(GL_CLIP_PLANE2);
|
|
glDisable(GL_CLIP_PLANE3);
|
|
glDisable(GL_CLIP_PLANE4);
|
|
glDisable(GL_CLIP_PLANE5);
|
|
}
|
|
|
|
SphereRenderer::SphereRenderer() {
|
|
}
|
|
|
|
void SphereRenderer::render(float alpha, Mode mode, const glm::vec3& clipMinimum, float clipSize) {
|
|
if (clipSize == 0.0f) {
|
|
renderUnclipped(alpha, mode);
|
|
return;
|
|
}
|
|
// slight performance optimization: don't render if clip bounds are entirely within sphere
|
|
Sphere* sphere = static_cast<Sphere*>(parent());
|
|
Box clipBox(clipMinimum, clipMinimum + glm::vec3(clipSize, clipSize, clipSize));
|
|
for (int i = 0; i < Box::VERTEX_COUNT; i++) {
|
|
const float CLIP_PROPORTION = 0.95f;
|
|
if (glm::distance(sphere->getTranslation(), clipBox.getVertex(i)) >= sphere->getScale() * CLIP_PROPORTION) {
|
|
ClippedRenderer::render(alpha, mode, clipMinimum, clipSize);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void SphereRenderer::renderUnclipped(float alpha, Mode mode) {
|
|
Sphere* sphere = static_cast<Sphere*>(parent());
|
|
const QColor& color = sphere->getColor();
|
|
glColor4f(color.redF(), color.greenF(), color.blueF(), color.alphaF() * alpha);
|
|
|
|
glPushMatrix();
|
|
const glm::vec3& translation = sphere->getTranslation();
|
|
glTranslatef(translation.x, translation.y, translation.z);
|
|
glm::quat rotation = sphere->getRotation();
|
|
glm::vec3 axis = glm::axis(rotation);
|
|
glRotatef(glm::angle(rotation), axis.x, axis.y, axis.z);
|
|
|
|
glutSolidSphere(sphere->getScale(), 10, 10);
|
|
|
|
glPopMatrix();
|
|
}
|
|
|
|
StaticModelRenderer::StaticModelRenderer() :
|
|
_model(new Model(this)) {
|
|
}
|
|
|
|
void StaticModelRenderer::init(Spanner* spanner) {
|
|
_model->init();
|
|
|
|
StaticModel* staticModel = static_cast<StaticModel*>(spanner);
|
|
applyTranslation(staticModel->getTranslation());
|
|
applyRotation(staticModel->getRotation());
|
|
applyScale(staticModel->getScale());
|
|
applyURL(staticModel->getURL());
|
|
|
|
connect(spanner, SIGNAL(translationChanged(const glm::vec3&)), SLOT(applyTranslation(const glm::vec3&)));
|
|
connect(spanner, SIGNAL(rotationChanged(const glm::quat&)), SLOT(applyRotation(const glm::quat&)));
|
|
connect(spanner, SIGNAL(scaleChanged(float)), SLOT(applyScale(float)));
|
|
connect(spanner, SIGNAL(urlChanged(const QUrl&)), SLOT(applyURL(const QUrl&)));
|
|
}
|
|
|
|
void StaticModelRenderer::simulate(float deltaTime) {
|
|
// update the bounds
|
|
Box bounds;
|
|
if (_model->isActive()) {
|
|
const Extents& extents = _model->getGeometry()->getFBXGeometry().meshExtents;
|
|
bounds = Box(extents.minimum, extents.maximum);
|
|
}
|
|
static_cast<StaticModel*>(parent())->setBounds(glm::translate(_model->getTranslation()) *
|
|
glm::mat4_cast(_model->getRotation()) * glm::scale(_model->getScale()) * bounds);
|
|
_model->simulate(deltaTime);
|
|
}
|
|
|
|
void StaticModelRenderer::renderUnclipped(float alpha, Mode mode) {
|
|
switch (mode) {
|
|
case DIFFUSE_MODE:
|
|
_model->render(alpha, Model::DIFFUSE_RENDER_MODE);
|
|
break;
|
|
|
|
case NORMAL_MODE:
|
|
_model->render(alpha, Model::NORMAL_RENDER_MODE);
|
|
break;
|
|
|
|
default:
|
|
_model->render(alpha);
|
|
break;
|
|
}
|
|
_model->render(alpha);
|
|
}
|
|
|
|
bool StaticModelRenderer::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
|
const glm::vec3& clipMinimum, float clipSize, float& distance) const {
|
|
return _model->findRayIntersection(origin, direction, distance);
|
|
}
|
|
|
|
void StaticModelRenderer::applyTranslation(const glm::vec3& translation) {
|
|
_model->setTranslation(translation);
|
|
}
|
|
|
|
void StaticModelRenderer::applyRotation(const glm::quat& rotation) {
|
|
_model->setRotation(rotation);
|
|
}
|
|
|
|
void StaticModelRenderer::applyScale(float scale) {
|
|
const float SCALE_MULTIPLIER = 0.0006f;
|
|
_model->setScale(glm::vec3(scale, scale, scale) * SCALE_MULTIPLIER);
|
|
}
|
|
|
|
void StaticModelRenderer::applyURL(const QUrl& url) {
|
|
_model->setURL(url);
|
|
}
|