mirror of
https://github.com/overte-org/overte.git
synced 2025-08-10 07:23:00 +02:00
fold kinematic hold into normal hold (disabled by default, an argument enables)
This commit is contained in:
parent
0964180f2c
commit
359a318568
7 changed files with 90 additions and 247 deletions
|
@ -12,7 +12,6 @@
|
||||||
|
|
||||||
|
|
||||||
#include <avatar/AvatarActionHold.h>
|
#include <avatar/AvatarActionHold.h>
|
||||||
#include <avatar/AvatarActionKinematicHold.h>
|
|
||||||
#include <ObjectActionOffset.h>
|
#include <ObjectActionOffset.h>
|
||||||
#include <ObjectActionSpring.h>
|
#include <ObjectActionSpring.h>
|
||||||
|
|
||||||
|
@ -29,8 +28,6 @@ EntityActionPointer interfaceActionFactory(EntityActionType type, const QUuid& i
|
||||||
return (EntityActionPointer) new ObjectActionSpring(id, ownerEntity);
|
return (EntityActionPointer) new ObjectActionSpring(id, ownerEntity);
|
||||||
case ACTION_TYPE_HOLD:
|
case ACTION_TYPE_HOLD:
|
||||||
return (EntityActionPointer) new AvatarActionHold(id, ownerEntity);
|
return (EntityActionPointer) new AvatarActionHold(id, ownerEntity);
|
||||||
case ACTION_TYPE_KINEMATIC_HOLD:
|
|
||||||
return (EntityActionPointer) new AvatarActionKinematicHold(id, ownerEntity);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(false);
|
assert(false);
|
||||||
|
|
|
@ -75,12 +75,54 @@ void AvatarActionHold::updateActionWorker(float deltaTimeStep) {
|
||||||
_active = true;
|
_active = true;
|
||||||
});
|
});
|
||||||
if (gotLock) {
|
if (gotLock) {
|
||||||
ObjectActionSpring::updateActionWorker(deltaTimeStep);
|
if (_kinematic) {
|
||||||
|
doKinematicUpdate(deltaTimeStep);
|
||||||
|
} else {
|
||||||
|
ObjectActionSpring::updateActionWorker(deltaTimeStep);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AvatarActionHold::doKinematicUpdate(float deltaTimeStep) {
|
||||||
|
auto ownerEntity = _ownerEntity.lock();
|
||||||
|
if (!ownerEntity) {
|
||||||
|
qDebug() << "AvatarActionHold::doKinematicUpdate -- no owning entity";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
void* physicsInfo = ownerEntity->getPhysicsInfo();
|
||||||
|
if (!physicsInfo) {
|
||||||
|
qDebug() << "AvatarActionHold::doKinematicUpdate -- no owning physics info";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ObjectMotionState* motionState = static_cast<ObjectMotionState*>(physicsInfo);
|
||||||
|
btRigidBody* rigidBody = motionState ? motionState->getRigidBody() : nullptr;
|
||||||
|
if (!rigidBody) {
|
||||||
|
qDebug() << "AvatarActionHold::doKinematicUpdate -- no rigidBody";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
withWriteLock([&]{
|
||||||
|
if (_kinematicSetVelocity) {
|
||||||
|
if (_previousSet) {
|
||||||
|
glm::vec3 positionalVelocity = (_positionalTarget - _previousPositionalTarget) / deltaTimeStep;
|
||||||
|
rigidBody->setLinearVelocity(glmToBullet(positionalVelocity));
|
||||||
|
// back up along velocity a bit in order to smooth out a "vibrating" appearance
|
||||||
|
_positionalTarget -= positionalVelocity * deltaTimeStep / 2.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
btTransform worldTrans = rigidBody->getWorldTransform();
|
||||||
|
worldTrans.setOrigin(glmToBullet(_positionalTarget));
|
||||||
|
worldTrans.setRotation(glmToBullet(_rotationalTarget));
|
||||||
|
rigidBody->setWorldTransform(worldTrans);
|
||||||
|
|
||||||
|
_previousPositionalTarget = _positionalTarget;
|
||||||
|
_previousRotationalTarget = _rotationalTarget;
|
||||||
|
_previousSet = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
bool AvatarActionHold::updateArguments(QVariantMap arguments) {
|
bool AvatarActionHold::updateArguments(QVariantMap arguments) {
|
||||||
glm::vec3 relativePosition;
|
glm::vec3 relativePosition;
|
||||||
|
@ -88,6 +130,8 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) {
|
||||||
float timeScale;
|
float timeScale;
|
||||||
QString hand;
|
QString hand;
|
||||||
QUuid holderID;
|
QUuid holderID;
|
||||||
|
bool kinematic;
|
||||||
|
bool kinematicSetVelocity;
|
||||||
bool needUpdate = false;
|
bool needUpdate = false;
|
||||||
|
|
||||||
bool somethingChanged = ObjectAction::updateArguments(arguments);
|
bool somethingChanged = ObjectAction::updateArguments(arguments);
|
||||||
|
@ -120,12 +164,27 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) {
|
||||||
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||||
holderID = myAvatar->getSessionUUID();
|
holderID = myAvatar->getSessionUUID();
|
||||||
|
|
||||||
|
ok = true;
|
||||||
|
kinematic = EntityActionInterface::extractBooleanArgument("hold", arguments, "kinematic", ok, false);
|
||||||
|
if (!ok) {
|
||||||
|
_kinematic = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ok = true;
|
||||||
|
kinematicSetVelocity = EntityActionInterface::extractBooleanArgument("hold", arguments,
|
||||||
|
"kinematic-set-velocity", ok, false);
|
||||||
|
if (!ok) {
|
||||||
|
_kinematicSetVelocity = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (somethingChanged ||
|
if (somethingChanged ||
|
||||||
relativePosition != _relativePosition ||
|
relativePosition != _relativePosition ||
|
||||||
relativeRotation != _relativeRotation ||
|
relativeRotation != _relativeRotation ||
|
||||||
timeScale != _linearTimeScale ||
|
timeScale != _linearTimeScale ||
|
||||||
hand != _hand ||
|
hand != _hand ||
|
||||||
holderID != _holderID) {
|
holderID != _holderID ||
|
||||||
|
kinematic != _kinematic ||
|
||||||
|
kinematicSetVelocity != _kinematicSetVelocity) {
|
||||||
needUpdate = true;
|
needUpdate = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -139,6 +198,8 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) {
|
||||||
_angularTimeScale = _linearTimeScale;
|
_angularTimeScale = _linearTimeScale;
|
||||||
_hand = hand;
|
_hand = hand;
|
||||||
_holderID = holderID;
|
_holderID = holderID;
|
||||||
|
_kinematic = kinematic;
|
||||||
|
_kinematicSetVelocity = kinematicSetVelocity;
|
||||||
_active = true;
|
_active = true;
|
||||||
|
|
||||||
auto ownerEntity = _ownerEntity.lock();
|
auto ownerEntity = _ownerEntity.lock();
|
||||||
|
|
|
@ -39,6 +39,13 @@ private:
|
||||||
glm::quat _relativeRotation;
|
glm::quat _relativeRotation;
|
||||||
QString _hand;
|
QString _hand;
|
||||||
QUuid _holderID;
|
QUuid _holderID;
|
||||||
|
|
||||||
|
void doKinematicUpdate(float deltaTimeStep);
|
||||||
|
bool _kinematic = false;
|
||||||
|
bool _kinematicSetVelocity = false;
|
||||||
|
bool _previousSet = false;
|
||||||
|
glm::vec3 _previousPositionalTarget;
|
||||||
|
glm::quat _previousRotationalTarget;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_AvatarActionHold_h
|
#endif // hifi_AvatarActionHold_h
|
||||||
|
|
|
@ -1,188 +0,0 @@
|
||||||
//
|
|
||||||
// AvatarActionKinematicHold.cpp
|
|
||||||
// interface/src/avatar/
|
|
||||||
//
|
|
||||||
// Created by Seth Alves 2015-6-9
|
|
||||||
// Copyright 2015 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 "QVariantGLM.h"
|
|
||||||
#include "avatar/MyAvatar.h"
|
|
||||||
#include "avatar/AvatarManager.h"
|
|
||||||
|
|
||||||
#include "AvatarActionKinematicHold.h"
|
|
||||||
|
|
||||||
const uint16_t AvatarActionKinematicHold::holdVersion = 1;
|
|
||||||
|
|
||||||
AvatarActionKinematicHold::AvatarActionKinematicHold(const QUuid& id, EntityItemPointer ownerEntity) :
|
|
||||||
ObjectActionSpring(id, ownerEntity),
|
|
||||||
_relativePosition(glm::vec3(0.0f)),
|
|
||||||
_relativeRotation(glm::quat()),
|
|
||||||
_hand("right"),
|
|
||||||
_mine(false),
|
|
||||||
_previousPositionalTarget(Vectors::ZERO),
|
|
||||||
_previousRotationalTarget(Quaternions::IDENTITY)
|
|
||||||
{
|
|
||||||
_type = ACTION_TYPE_KINEMATIC_HOLD;
|
|
||||||
#if WANT_DEBUG
|
|
||||||
qDebug() << "AvatarActionKinematicHold::AvatarActionKinematicHold";
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
AvatarActionKinematicHold::~AvatarActionKinematicHold() {
|
|
||||||
#if WANT_DEBUG
|
|
||||||
qDebug() << "AvatarActionKinematicHold::~AvatarActionKinematicHold";
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void AvatarActionKinematicHold::updateActionWorker(float deltaTimeStep) {
|
|
||||||
if (!_mine) {
|
|
||||||
// if a local script isn't updating this, then we are just getting spring-action data over the wire.
|
|
||||||
// let the super-class handle it.
|
|
||||||
ObjectActionSpring::updateActionWorker(deltaTimeStep);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(deltaTimeStep > 0.0f);
|
|
||||||
|
|
||||||
glm::quat rotation;
|
|
||||||
glm::vec3 position;
|
|
||||||
glm::vec3 offset;
|
|
||||||
bool gotLock = withTryReadLock([&]{
|
|
||||||
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
|
||||||
glm::vec3 palmPosition;
|
|
||||||
glm::quat palmRotation;
|
|
||||||
if (_hand == "right") {
|
|
||||||
palmPosition = myAvatar->getRightPalmPosition();
|
|
||||||
palmRotation = myAvatar->getRightPalmRotation();
|
|
||||||
} else {
|
|
||||||
palmPosition = myAvatar->getLeftPalmPosition();
|
|
||||||
palmRotation = myAvatar->getLeftPalmRotation();
|
|
||||||
}
|
|
||||||
|
|
||||||
rotation = palmRotation * _relativeRotation;
|
|
||||||
offset = rotation * _relativePosition;
|
|
||||||
position = palmPosition + offset;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (gotLock) {
|
|
||||||
gotLock = withTryWriteLock([&]{
|
|
||||||
if (_positionalTarget != position || _rotationalTarget != rotation) {
|
|
||||||
_positionalTarget = position;
|
|
||||||
_rotationalTarget = rotation;
|
|
||||||
auto ownerEntity = _ownerEntity.lock();
|
|
||||||
if (ownerEntity) {
|
|
||||||
ownerEntity->setActionDataDirty(true);
|
|
||||||
void* physicsInfo = ownerEntity->getPhysicsInfo();
|
|
||||||
if (physicsInfo) {
|
|
||||||
ObjectMotionState* motionState = static_cast<ObjectMotionState*>(physicsInfo);
|
|
||||||
btRigidBody* rigidBody = motionState ? motionState->getRigidBody() : nullptr;
|
|
||||||
if (!rigidBody) {
|
|
||||||
qDebug() << "ObjectActionSpring::updateActionWorker no rigidBody";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_setVelocity) {
|
|
||||||
if (_previousSet) {
|
|
||||||
glm::vec3 positionalVelocity = (_positionalTarget - _previousPositionalTarget) / deltaTimeStep;
|
|
||||||
rigidBody->setLinearVelocity(glmToBullet(positionalVelocity));
|
|
||||||
// back up along velocity a bit in order to smooth out a "vibrating" appearance
|
|
||||||
_positionalTarget -= positionalVelocity * deltaTimeStep / 2.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
btTransform worldTrans = rigidBody->getWorldTransform();
|
|
||||||
worldTrans.setOrigin(glmToBullet(_positionalTarget));
|
|
||||||
worldTrans.setRotation(glmToBullet(_rotationalTarget));
|
|
||||||
rigidBody->setWorldTransform(worldTrans);
|
|
||||||
|
|
||||||
_previousPositionalTarget = _positionalTarget;
|
|
||||||
_previousRotationalTarget = _rotationalTarget;
|
|
||||||
_previousSet = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gotLock) {
|
|
||||||
ObjectActionSpring::updateActionWorker(deltaTimeStep);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool AvatarActionKinematicHold::updateArguments(QVariantMap arguments) {
|
|
||||||
if (!ObjectAction::updateArguments(arguments)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
bool ok = true;
|
|
||||||
glm::vec3 relativePosition =
|
|
||||||
EntityActionInterface::extractVec3Argument("kinematic-hold", arguments, "relativePosition", ok, false);
|
|
||||||
if (!ok) {
|
|
||||||
relativePosition = _relativePosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
ok = true;
|
|
||||||
glm::quat relativeRotation =
|
|
||||||
EntityActionInterface::extractQuatArgument("kinematic-hold", arguments, "relativeRotation", ok, false);
|
|
||||||
if (!ok) {
|
|
||||||
relativeRotation = _relativeRotation;
|
|
||||||
}
|
|
||||||
|
|
||||||
ok = true;
|
|
||||||
QString hand =
|
|
||||||
EntityActionInterface::extractStringArgument("kinematic-hold", arguments, "hand", ok, false);
|
|
||||||
if (!ok || !(hand == "left" || hand == "right")) {
|
|
||||||
hand = _hand;
|
|
||||||
}
|
|
||||||
|
|
||||||
ok = true;
|
|
||||||
int setVelocity =
|
|
||||||
EntityActionInterface::extractIntegerArgument("kinematic-hold", arguments, "setVelocity", ok, false);
|
|
||||||
if (!ok) {
|
|
||||||
setVelocity = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (relativePosition != _relativePosition
|
|
||||||
|| relativeRotation != _relativeRotation
|
|
||||||
|| hand != _hand) {
|
|
||||||
withWriteLock([&] {
|
|
||||||
_relativePosition = relativePosition;
|
|
||||||
_relativeRotation = relativeRotation;
|
|
||||||
_hand = hand;
|
|
||||||
_setVelocity = setVelocity;
|
|
||||||
|
|
||||||
_mine = true;
|
|
||||||
_active = true;
|
|
||||||
activateBody();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
QVariantMap AvatarActionKinematicHold::getArguments() {
|
|
||||||
QVariantMap arguments = ObjectAction::getArguments();
|
|
||||||
withReadLock([&]{
|
|
||||||
if (!_mine) {
|
|
||||||
arguments = ObjectActionSpring::getArguments();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
arguments["relativePosition"] = glmToQMap(_relativePosition);
|
|
||||||
arguments["relativeRotation"] = glmToQMap(_relativeRotation);
|
|
||||||
arguments["setVelocity"] = _setVelocity;
|
|
||||||
arguments["hand"] = _hand;
|
|
||||||
});
|
|
||||||
return arguments;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void AvatarActionKinematicHold::deserialize(QByteArray serializedArguments) {
|
|
||||||
if (!_mine) {
|
|
||||||
ObjectActionSpring::deserialize(serializedArguments);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
//
|
|
||||||
// AvatarActionKinematicHold.h
|
|
||||||
// interface/src/avatar/
|
|
||||||
//
|
|
||||||
// Created by Seth Alves 2015-10-2
|
|
||||||
// Copyright 2015 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
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef hifi_AvatarActionKinematicHold_h
|
|
||||||
#define hifi_AvatarActionKinematicHold_h
|
|
||||||
|
|
||||||
#include <QUuid>
|
|
||||||
|
|
||||||
#include <EntityItem.h>
|
|
||||||
#include <ObjectActionSpring.h>
|
|
||||||
|
|
||||||
class AvatarActionKinematicHold : public ObjectActionSpring {
|
|
||||||
public:
|
|
||||||
AvatarActionKinematicHold(const QUuid& id, EntityItemPointer ownerEntity);
|
|
||||||
virtual ~AvatarActionKinematicHold();
|
|
||||||
|
|
||||||
virtual bool updateArguments(QVariantMap arguments);
|
|
||||||
virtual QVariantMap getArguments();
|
|
||||||
|
|
||||||
virtual void updateActionWorker(float deltaTimeStep);
|
|
||||||
|
|
||||||
virtual void deserialize(QByteArray serializedArguments);
|
|
||||||
|
|
||||||
private:
|
|
||||||
static const uint16_t holdVersion;
|
|
||||||
|
|
||||||
glm::vec3 _relativePosition;
|
|
||||||
glm::quat _relativeRotation;
|
|
||||||
QString _hand;
|
|
||||||
bool _mine = false;
|
|
||||||
|
|
||||||
bool _previousSet = false;
|
|
||||||
glm::vec3 _previousPositionalTarget;
|
|
||||||
glm::quat _previousRotationalTarget;
|
|
||||||
|
|
||||||
bool _setVelocity = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // hifi_AvatarActionKinematicHold_h
|
|
|
@ -100,9 +100,6 @@ EntityActionType EntityActionInterface::actionTypeFromString(QString actionTypeS
|
||||||
if (normalizedActionTypeString == "hold") {
|
if (normalizedActionTypeString == "hold") {
|
||||||
return ACTION_TYPE_HOLD;
|
return ACTION_TYPE_HOLD;
|
||||||
}
|
}
|
||||||
if (normalizedActionTypeString == "kinematichold") {
|
|
||||||
return ACTION_TYPE_KINEMATIC_HOLD;
|
|
||||||
}
|
|
||||||
|
|
||||||
qDebug() << "Warning -- EntityActionInterface::actionTypeFromString got unknown action-type name" << actionTypeString;
|
qDebug() << "Warning -- EntityActionInterface::actionTypeFromString got unknown action-type name" << actionTypeString;
|
||||||
return ACTION_TYPE_NONE;
|
return ACTION_TYPE_NONE;
|
||||||
|
@ -118,8 +115,6 @@ QString EntityActionInterface::actionTypeToString(EntityActionType actionType) {
|
||||||
return "spring";
|
return "spring";
|
||||||
case ACTION_TYPE_HOLD:
|
case ACTION_TYPE_HOLD:
|
||||||
return "hold";
|
return "hold";
|
||||||
case ACTION_TYPE_KINEMATIC_HOLD:
|
|
||||||
return "kinematic-hold";
|
|
||||||
}
|
}
|
||||||
assert(false);
|
assert(false);
|
||||||
return "none";
|
return "none";
|
||||||
|
@ -286,6 +281,23 @@ QString EntityActionInterface::extractStringArgument(QString objectName, QVarian
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool EntityActionInterface::extractBooleanArgument(QString objectName, QVariantMap arguments,
|
||||||
|
QString argumentName, bool& ok, bool required) {
|
||||||
|
if (!arguments.contains(argumentName)) {
|
||||||
|
if (required) {
|
||||||
|
qDebug() << objectName << "requires argument:" << argumentName;
|
||||||
|
}
|
||||||
|
ok = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant vV = arguments[argumentName];
|
||||||
|
bool v = vV.toBool();
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
QDataStream& operator<<(QDataStream& stream, const EntityActionType& entityActionType)
|
QDataStream& operator<<(QDataStream& stream, const EntityActionType& entityActionType)
|
||||||
{
|
{
|
||||||
return stream << (quint16)entityActionType;
|
return stream << (quint16)entityActionType;
|
||||||
|
|
|
@ -23,8 +23,7 @@ enum EntityActionType {
|
||||||
ACTION_TYPE_NONE = 0,
|
ACTION_TYPE_NONE = 0,
|
||||||
ACTION_TYPE_OFFSET = 1000,
|
ACTION_TYPE_OFFSET = 1000,
|
||||||
ACTION_TYPE_SPRING = 2000,
|
ACTION_TYPE_SPRING = 2000,
|
||||||
ACTION_TYPE_HOLD = 3000,
|
ACTION_TYPE_HOLD = 3000
|
||||||
ACTION_TYPE_KINEMATIC_HOLD = 4000
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -66,6 +65,8 @@ public:
|
||||||
QString argumentName, bool& ok, bool required = true);
|
QString argumentName, bool& ok, bool required = true);
|
||||||
static QString extractStringArgument(QString objectName, QVariantMap arguments,
|
static QString extractStringArgument(QString objectName, QVariantMap arguments,
|
||||||
QString argumentName, bool& ok, bool required = true);
|
QString argumentName, bool& ok, bool required = true);
|
||||||
|
static bool extractBooleanArgument(QString objectName, QVariantMap arguments,
|
||||||
|
QString argumentName, bool& ok, bool required = true);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual glm::vec3 getPosition() = 0;
|
virtual glm::vec3 getPosition() = 0;
|
||||||
|
|
Loading…
Reference in a new issue