mirror of
https://github.com/overte-org/overte.git
synced 2025-04-15 08:26:36 +02:00
Merge pull request #14439 from AndrewMeadows/shaped-zones
Case 15518: shaped zones
This commit is contained in:
commit
ed4f2d9c10
20 changed files with 200 additions and 172 deletions
|
@ -14,6 +14,7 @@ link_hifi_libraries(
|
||||||
audio avatars octree gpu graphics shaders fbx hfm entities
|
audio avatars octree gpu graphics shaders fbx hfm entities
|
||||||
networking animation recording shared script-engine embedded-webserver
|
networking animation recording shared script-engine embedded-webserver
|
||||||
controllers physics plugins midi image
|
controllers physics plugins midi image
|
||||||
|
model-networking ktx shaders
|
||||||
)
|
)
|
||||||
|
|
||||||
add_dependencies(${TARGET_NAME} oven)
|
add_dependencies(${TARGET_NAME} oven)
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
#include <EntityEditFilters.h>
|
#include <EntityEditFilters.h>
|
||||||
#include <NetworkingConstants.h>
|
#include <NetworkingConstants.h>
|
||||||
#include <AddressManager.h>
|
#include <AddressManager.h>
|
||||||
|
#include <hfm/ModelFormatRegistry.h>
|
||||||
|
|
||||||
#include "../AssignmentDynamicFactory.h"
|
#include "../AssignmentDynamicFactory.h"
|
||||||
#include "AssignmentParentFinder.h"
|
#include "AssignmentParentFinder.h"
|
||||||
|
@ -45,6 +46,8 @@ EntityServer::EntityServer(ReceivedMessage& message) :
|
||||||
|
|
||||||
DependencyManager::registerInheritance<EntityDynamicFactoryInterface, AssignmentDynamicFactory>();
|
DependencyManager::registerInheritance<EntityDynamicFactoryInterface, AssignmentDynamicFactory>();
|
||||||
DependencyManager::set<AssignmentDynamicFactory>();
|
DependencyManager::set<AssignmentDynamicFactory>();
|
||||||
|
DependencyManager::set<ModelFormatRegistry>(); // ModelFormatRegistry must be defined before ModelCache. See the ModelCache ctor
|
||||||
|
DependencyManager::set<ModelCache>();
|
||||||
|
|
||||||
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
|
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
|
||||||
packetReceiver.registerListenerForTypes({ PacketType::EntityAdd,
|
packetReceiver.registerListenerForTypes({ PacketType::EntityAdd,
|
||||||
|
|
|
@ -279,7 +279,7 @@ bool RenderableModelEntityItem::findDetailedParabolaIntersection(const glm::vec3
|
||||||
face, surfaceNormal, extraInfo, precisionPicking, false);
|
face, surfaceNormal, extraInfo, precisionPicking, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderableModelEntityItem::getCollisionGeometryResource() {
|
void RenderableModelEntityItem::fetchCollisionGeometryResource() {
|
||||||
QUrl hullURL(getCollisionShapeURL());
|
QUrl hullURL(getCollisionShapeURL());
|
||||||
QUrlQuery queryArgs(hullURL);
|
QUrlQuery queryArgs(hullURL);
|
||||||
queryArgs.addQueryItem("collision-hull", "");
|
queryArgs.addQueryItem("collision-hull", "");
|
||||||
|
@ -289,7 +289,7 @@ void RenderableModelEntityItem::getCollisionGeometryResource() {
|
||||||
|
|
||||||
bool RenderableModelEntityItem::computeShapeFailedToLoad() {
|
bool RenderableModelEntityItem::computeShapeFailedToLoad() {
|
||||||
if (!_compoundShapeResource) {
|
if (!_compoundShapeResource) {
|
||||||
getCollisionGeometryResource();
|
fetchCollisionGeometryResource();
|
||||||
}
|
}
|
||||||
|
|
||||||
return (_compoundShapeResource && _compoundShapeResource->isFailed());
|
return (_compoundShapeResource && _compoundShapeResource->isFailed());
|
||||||
|
@ -300,7 +300,7 @@ void RenderableModelEntityItem::setShapeType(ShapeType type) {
|
||||||
auto shapeType = getShapeType();
|
auto shapeType = getShapeType();
|
||||||
if (shapeType == SHAPE_TYPE_COMPOUND || shapeType == SHAPE_TYPE_SIMPLE_COMPOUND) {
|
if (shapeType == SHAPE_TYPE_COMPOUND || shapeType == SHAPE_TYPE_SIMPLE_COMPOUND) {
|
||||||
if (!_compoundShapeResource && !getCollisionShapeURL().isEmpty()) {
|
if (!_compoundShapeResource && !getCollisionShapeURL().isEmpty()) {
|
||||||
getCollisionGeometryResource();
|
fetchCollisionGeometryResource();
|
||||||
}
|
}
|
||||||
} else if (_compoundShapeResource && !getCompoundShapeURL().isEmpty()) {
|
} else if (_compoundShapeResource && !getCompoundShapeURL().isEmpty()) {
|
||||||
// the compoundURL has been set but the shapeType does not agree
|
// the compoundURL has been set but the shapeType does not agree
|
||||||
|
@ -317,7 +317,7 @@ void RenderableModelEntityItem::setCompoundShapeURL(const QString& url) {
|
||||||
ModelEntityItem::setCompoundShapeURL(url);
|
ModelEntityItem::setCompoundShapeURL(url);
|
||||||
if (getCompoundShapeURL() != currentCompoundShapeURL || !getModel()) {
|
if (getCompoundShapeURL() != currentCompoundShapeURL || !getModel()) {
|
||||||
if (getShapeType() == SHAPE_TYPE_COMPOUND) {
|
if (getShapeType() == SHAPE_TYPE_COMPOUND) {
|
||||||
getCollisionGeometryResource();
|
fetchCollisionGeometryResource();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -340,7 +340,7 @@ bool RenderableModelEntityItem::isReadyToComputeShape() const {
|
||||||
|
|
||||||
if (model->isLoaded()) {
|
if (model->isLoaded()) {
|
||||||
if (!shapeURL.isEmpty() && !_compoundShapeResource) {
|
if (!shapeURL.isEmpty() && !_compoundShapeResource) {
|
||||||
const_cast<RenderableModelEntityItem*>(this)->getCollisionGeometryResource();
|
const_cast<RenderableModelEntityItem*>(this)->fetchCollisionGeometryResource();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_compoundShapeResource && _compoundShapeResource->isLoaded()) {
|
if (_compoundShapeResource && _compoundShapeResource->isLoaded()) {
|
||||||
|
@ -367,8 +367,6 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) {
|
||||||
const uint32_t QUAD_STRIDE = 4;
|
const uint32_t QUAD_STRIDE = 4;
|
||||||
|
|
||||||
ShapeType type = getShapeType();
|
ShapeType type = getShapeType();
|
||||||
glm::vec3 dimensions = getScaledDimensions();
|
|
||||||
auto model = getModel();
|
|
||||||
if (type == SHAPE_TYPE_COMPOUND) {
|
if (type == SHAPE_TYPE_COMPOUND) {
|
||||||
updateModelBounds();
|
updateModelBounds();
|
||||||
|
|
||||||
|
@ -450,6 +448,11 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) {
|
||||||
// to the visual model and apply them to the collision model (without regard for the
|
// to the visual model and apply them to the collision model (without regard for the
|
||||||
// collision model's extents).
|
// collision model's extents).
|
||||||
|
|
||||||
|
auto model = getModel();
|
||||||
|
// assert we never fall in here when model not fully loaded
|
||||||
|
assert(model && model->isLoaded());
|
||||||
|
|
||||||
|
glm::vec3 dimensions = getScaledDimensions();
|
||||||
glm::vec3 scaleToFit = dimensions / model->getHFMModel().getUnscaledMeshExtents().size();
|
glm::vec3 scaleToFit = dimensions / model->getHFMModel().getUnscaledMeshExtents().size();
|
||||||
// multiply each point by scale before handing the point-set off to the physics engine.
|
// multiply each point by scale before handing the point-set off to the physics engine.
|
||||||
// also determine the extents of the collision model.
|
// also determine the extents of the collision model.
|
||||||
|
@ -461,11 +464,12 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
shapeInfo.setParams(type, dimensions, getCompoundShapeURL());
|
shapeInfo.setParams(type, dimensions, getCompoundShapeURL());
|
||||||
|
adjustShapeInfoByRegistration(shapeInfo);
|
||||||
} else if (type >= SHAPE_TYPE_SIMPLE_HULL && type <= SHAPE_TYPE_STATIC_MESH) {
|
} else if (type >= SHAPE_TYPE_SIMPLE_HULL && type <= SHAPE_TYPE_STATIC_MESH) {
|
||||||
// TODO: assert we never fall in here when model not fully loaded
|
|
||||||
// assert(_model && _model->isLoaded());
|
|
||||||
|
|
||||||
updateModelBounds();
|
updateModelBounds();
|
||||||
|
auto model = getModel();
|
||||||
|
// assert we never fall in here when model not fully loaded
|
||||||
|
assert(model && model->isLoaded());
|
||||||
model->updateGeometry();
|
model->updateGeometry();
|
||||||
|
|
||||||
// compute meshPart local transforms
|
// compute meshPart local transforms
|
||||||
|
@ -473,6 +477,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) {
|
||||||
const HFMModel& hfmModel = model->getHFMModel();
|
const HFMModel& hfmModel = model->getHFMModel();
|
||||||
int numHFMMeshes = hfmModel.meshes.size();
|
int numHFMMeshes = hfmModel.meshes.size();
|
||||||
int totalNumVertices = 0;
|
int totalNumVertices = 0;
|
||||||
|
glm::vec3 dimensions = getScaledDimensions();
|
||||||
glm::mat4 invRegistraionOffset = glm::translate(dimensions * (getRegistrationPoint() - ENTITY_ITEM_DEFAULT_REGISTRATION_POINT));
|
glm::mat4 invRegistraionOffset = glm::translate(dimensions * (getRegistrationPoint() - ENTITY_ITEM_DEFAULT_REGISTRATION_POINT));
|
||||||
for (int i = 0; i < numHFMMeshes; i++) {
|
for (int i = 0; i < numHFMMeshes; i++) {
|
||||||
const HFMMesh& mesh = hfmModel.meshes.at(i);
|
const HFMMesh& mesh = hfmModel.meshes.at(i);
|
||||||
|
@ -695,12 +700,10 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) {
|
||||||
}
|
}
|
||||||
|
|
||||||
shapeInfo.setParams(type, 0.5f * dimensions, getModelURL());
|
shapeInfo.setParams(type, 0.5f * dimensions, getModelURL());
|
||||||
|
adjustShapeInfoByRegistration(shapeInfo);
|
||||||
} else {
|
} else {
|
||||||
ModelEntityItem::computeShapeInfo(shapeInfo);
|
EntityItem::computeShapeInfo(shapeInfo);
|
||||||
shapeInfo.setParams(type, 0.5f * dimensions);
|
|
||||||
}
|
}
|
||||||
// finally apply the registration offset to the shapeInfo
|
|
||||||
adjustShapeInfoByRegistration(shapeInfo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderableModelEntityItem::setJointMap(std::vector<int> jointMap) {
|
void RenderableModelEntityItem::setJointMap(std::vector<int> jointMap) {
|
||||||
|
@ -726,7 +729,9 @@ int RenderableModelEntityItem::avatarJointIndex(int modelJointIndex) {
|
||||||
bool RenderableModelEntityItem::contains(const glm::vec3& point) const {
|
bool RenderableModelEntityItem::contains(const glm::vec3& point) const {
|
||||||
auto model = getModel();
|
auto model = getModel();
|
||||||
if (EntityItem::contains(point) && model && _compoundShapeResource && _compoundShapeResource->isLoaded()) {
|
if (EntityItem::contains(point) && model && _compoundShapeResource && _compoundShapeResource->isLoaded()) {
|
||||||
return _compoundShapeResource->getHFMModel().convexHullContains(worldToEntity(point));
|
glm::mat4 worldToHFMMatrix = model->getWorldToHFMMatrix();
|
||||||
|
glm::vec3 hfmPoint = worldToHFMMatrix * glm::vec4(point, 1.0f);
|
||||||
|
return _compoundShapeResource->getHFMModel().convexHullContains(hfmPoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -122,7 +122,7 @@ private:
|
||||||
void autoResizeJointArrays();
|
void autoResizeJointArrays();
|
||||||
void copyAnimationJointDataToModel();
|
void copyAnimationJointDataToModel();
|
||||||
bool readyToAnimate() const;
|
bool readyToAnimate() const;
|
||||||
void getCollisionGeometryResource();
|
void fetchCollisionGeometryResource();
|
||||||
|
|
||||||
GeometryResource::Pointer _compoundShapeResource;
|
GeometryResource::Pointer _compoundShapeResource;
|
||||||
std::vector<int> _jointMap;
|
std::vector<int> _jointMap;
|
||||||
|
@ -179,7 +179,6 @@ private:
|
||||||
|
|
||||||
bool _hasModel { false };
|
bool _hasModel { false };
|
||||||
ModelPointer _model;
|
ModelPointer _model;
|
||||||
GeometryResource::Pointer _compoundShapeResource;
|
|
||||||
QString _lastTextures;
|
QString _lastTextures;
|
||||||
bool _texturesLoaded { false };
|
bool _texturesLoaded { false };
|
||||||
int _lastKnownCurrentFrame { -1 };
|
int _lastKnownCurrentFrame { -1 };
|
||||||
|
|
|
@ -546,22 +546,3 @@ void ZoneEntityRenderer::setProceduralUserData(const QString& userData) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if 0
|
|
||||||
bool RenderableZoneEntityItem::contains(const glm::vec3& point) const {
|
|
||||||
if (getShapeType() != SHAPE_TYPE_COMPOUND) {
|
|
||||||
return EntityItem::contains(point);
|
|
||||||
}
|
|
||||||
const_cast<RenderableZoneEntityItem*>(this)->updateGeometry();
|
|
||||||
|
|
||||||
if (_model && _model->isActive() && EntityItem::contains(point)) {
|
|
||||||
return _model->convexHullContains(point);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderableZoneEntityItem::notifyBoundChanged() {
|
|
||||||
notifyChangedRenderItem();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
|
@ -6,4 +6,4 @@ include_hifi_library_headers(fbx)
|
||||||
include_hifi_library_headers(gpu)
|
include_hifi_library_headers(gpu)
|
||||||
include_hifi_library_headers(image)
|
include_hifi_library_headers(image)
|
||||||
include_hifi_library_headers(ktx)
|
include_hifi_library_headers(ktx)
|
||||||
link_hifi_libraries(shared shaders networking octree avatars graphics model-networking)
|
link_hifi_libraries(shared shaders networking octree avatars graphics model-networking)
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
#include <QtNetwork/QNetworkRequest>
|
#include <QtNetwork/QNetworkRequest>
|
||||||
|
|
||||||
#include <glm/gtx/transform.hpp>
|
#include <glm/gtx/transform.hpp>
|
||||||
|
#include <glm/gtx/component_wise.hpp>
|
||||||
|
|
||||||
#include <BufferParser.h>
|
#include <BufferParser.h>
|
||||||
#include <ByteCountCoding.h>
|
#include <ByteCountCoding.h>
|
||||||
|
@ -1679,15 +1680,57 @@ void EntityItem::adjustShapeInfoByRegistration(ShapeInfo& info) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EntityItem::contains(const glm::vec3& point) const {
|
bool EntityItem::contains(const glm::vec3& point) const {
|
||||||
if (getShapeType() == SHAPE_TYPE_COMPOUND) {
|
ShapeType shapeType = getShapeType();
|
||||||
bool success;
|
|
||||||
bool result = getAABox(success).contains(point);
|
if (shapeType == SHAPE_TYPE_SPHERE) {
|
||||||
return result && success;
|
// SPHERE case is special:
|
||||||
} else {
|
// anything with shapeType == SPHERE must collide as a bounding sphere in the world-frame regardless of dimensions
|
||||||
ShapeInfo info;
|
// therefore we must do math using an unscaled localPoint relative to sphere center
|
||||||
info.setParams(getShapeType(), glm::vec3(0.5f));
|
glm::vec3 dimensions = getScaledDimensions();
|
||||||
adjustShapeInfoByRegistration(info);
|
glm::vec3 localPoint = point - (getWorldPosition() + getWorldOrientation() * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint())));
|
||||||
return info.contains(worldToEntity(point));
|
const float HALF_SQUARED = 0.25f;
|
||||||
|
return glm::length2(localPoint) < HALF_SQUARED * glm::length2(dimensions);
|
||||||
|
}
|
||||||
|
|
||||||
|
// we transform into the "normalized entity-frame" where the bounding box is centered on the origin
|
||||||
|
// and has dimensions <1,1,1>
|
||||||
|
glm::vec3 localPoint = glm::vec3(glm::inverse(getEntityToWorldMatrix()) * glm::vec4(point, 1.0f));
|
||||||
|
|
||||||
|
const float NORMALIZED_HALF_SIDE = 0.5f;
|
||||||
|
const float NORMALIZED_RADIUS_SQUARED = NORMALIZED_HALF_SIDE * NORMALIZED_HALF_SIDE;
|
||||||
|
switch(shapeType) {
|
||||||
|
case SHAPE_TYPE_NONE:
|
||||||
|
return false;
|
||||||
|
case SHAPE_TYPE_CAPSULE_X:
|
||||||
|
case SHAPE_TYPE_CAPSULE_Y:
|
||||||
|
case SHAPE_TYPE_CAPSULE_Z:
|
||||||
|
case SHAPE_TYPE_HULL:
|
||||||
|
case SHAPE_TYPE_PLANE:
|
||||||
|
case SHAPE_TYPE_COMPOUND:
|
||||||
|
case SHAPE_TYPE_SIMPLE_HULL:
|
||||||
|
case SHAPE_TYPE_SIMPLE_COMPOUND:
|
||||||
|
case SHAPE_TYPE_STATIC_MESH:
|
||||||
|
case SHAPE_TYPE_CIRCLE:
|
||||||
|
// the above cases not yet supported --> fall through to BOX case
|
||||||
|
case SHAPE_TYPE_BOX: {
|
||||||
|
localPoint = glm::abs(localPoint);
|
||||||
|
return glm::any(glm::lessThanEqual(localPoint, glm::vec3(NORMALIZED_HALF_SIDE)));
|
||||||
|
}
|
||||||
|
case SHAPE_TYPE_ELLIPSOID: {
|
||||||
|
// since we've transformed into the normalized space this is just a sphere-point intersection test
|
||||||
|
return glm::length2(localPoint) <= NORMALIZED_RADIUS_SQUARED;
|
||||||
|
}
|
||||||
|
case SHAPE_TYPE_CYLINDER_X:
|
||||||
|
return fabsf(localPoint.x) <= NORMALIZED_HALF_SIDE &&
|
||||||
|
localPoint.y * localPoint.y + localPoint.z * localPoint.z <= NORMALIZED_RADIUS_SQUARED;
|
||||||
|
case SHAPE_TYPE_CYLINDER_Y:
|
||||||
|
return fabsf(localPoint.y) <= NORMALIZED_HALF_SIDE &&
|
||||||
|
localPoint.z * localPoint.z + localPoint.x * localPoint.x <= NORMALIZED_RADIUS_SQUARED;
|
||||||
|
case SHAPE_TYPE_CYLINDER_Z:
|
||||||
|
return fabsf(localPoint.z) <= NORMALIZED_HALF_SIDE &&
|
||||||
|
localPoint.x * localPoint.x + localPoint.y * localPoint.y <= NORMALIZED_RADIUS_SQUARED;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -113,6 +113,7 @@ void buildStringToShapeTypeLookup() {
|
||||||
addShapeType(SHAPE_TYPE_SIMPLE_HULL);
|
addShapeType(SHAPE_TYPE_SIMPLE_HULL);
|
||||||
addShapeType(SHAPE_TYPE_SIMPLE_COMPOUND);
|
addShapeType(SHAPE_TYPE_SIMPLE_COMPOUND);
|
||||||
addShapeType(SHAPE_TYPE_STATIC_MESH);
|
addShapeType(SHAPE_TYPE_STATIC_MESH);
|
||||||
|
addShapeType(SHAPE_TYPE_ELLIPSOID);
|
||||||
}
|
}
|
||||||
|
|
||||||
QHash<QString, MaterialMappingMode> stringToMaterialMappingModeLookup;
|
QHash<QString, MaterialMappingMode> stringToMaterialMappingModeLookup;
|
||||||
|
|
|
@ -11,7 +11,9 @@
|
||||||
|
|
||||||
#include "ZoneEntityItem.h"
|
#include "ZoneEntityItem.h"
|
||||||
|
|
||||||
|
#include <glm/gtx/transform.hpp>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <QUrlQuery>
|
||||||
|
|
||||||
#include <ByteCountCoding.h>
|
#include <ByteCountCoding.h>
|
||||||
|
|
||||||
|
@ -275,22 +277,52 @@ void ZoneEntityItem::debugDump() const {
|
||||||
_bloomProperties.debugDump();
|
_bloomProperties.debugDump();
|
||||||
}
|
}
|
||||||
|
|
||||||
ShapeType ZoneEntityItem::getShapeType() const {
|
void ZoneEntityItem::setShapeType(ShapeType type) {
|
||||||
// Zones are not allowed to have a SHAPE_TYPE_NONE... they are always at least a SHAPE_TYPE_BOX
|
ShapeType oldShapeType = _shapeType;
|
||||||
if (_shapeType == SHAPE_TYPE_COMPOUND) {
|
switch(type) {
|
||||||
return hasCompoundShapeURL() ? SHAPE_TYPE_COMPOUND : DEFAULT_SHAPE_TYPE;
|
case SHAPE_TYPE_NONE:
|
||||||
|
case SHAPE_TYPE_CAPSULE_X:
|
||||||
|
case SHAPE_TYPE_CAPSULE_Y:
|
||||||
|
case SHAPE_TYPE_CAPSULE_Z:
|
||||||
|
case SHAPE_TYPE_HULL:
|
||||||
|
case SHAPE_TYPE_PLANE:
|
||||||
|
case SHAPE_TYPE_SIMPLE_HULL:
|
||||||
|
case SHAPE_TYPE_SIMPLE_COMPOUND:
|
||||||
|
case SHAPE_TYPE_STATIC_MESH:
|
||||||
|
case SHAPE_TYPE_CIRCLE:
|
||||||
|
// these types are unsupported for ZoneEntity
|
||||||
|
type = DEFAULT_SHAPE_TYPE;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_shapeType = type;
|
||||||
|
|
||||||
|
if (type == SHAPE_TYPE_COMPOUND) {
|
||||||
|
if (type != oldShapeType) {
|
||||||
|
fetchCollisionGeometryResource();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return _shapeType == SHAPE_TYPE_NONE ? DEFAULT_SHAPE_TYPE : _shapeType;
|
_shapeResource.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ShapeType ZoneEntityItem::getShapeType() const {
|
||||||
|
return _shapeType;
|
||||||
|
}
|
||||||
|
|
||||||
void ZoneEntityItem::setCompoundShapeURL(const QString& url) {
|
void ZoneEntityItem::setCompoundShapeURL(const QString& url) {
|
||||||
|
QString oldCompoundShapeURL = _compoundShapeURL;
|
||||||
withWriteLock([&] {
|
withWriteLock([&] {
|
||||||
_compoundShapeURL = url;
|
_compoundShapeURL = url;
|
||||||
if (_compoundShapeURL.isEmpty() && _shapeType == SHAPE_TYPE_COMPOUND) {
|
|
||||||
_shapeType = DEFAULT_SHAPE_TYPE;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
if (oldCompoundShapeURL != url) {
|
||||||
|
if (_shapeType == SHAPE_TYPE_COMPOUND) {
|
||||||
|
fetchCollisionGeometryResource();
|
||||||
|
} else {
|
||||||
|
_shapeResource.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ZoneEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
bool ZoneEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||||
|
@ -307,6 +339,27 @@ bool ZoneEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, c
|
||||||
return _zonesArePickable;
|
return _zonesArePickable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ZoneEntityItem::contains(const glm::vec3& point) const {
|
||||||
|
GeometryResource::Pointer resource = _shapeResource;
|
||||||
|
if (_shapeType == SHAPE_TYPE_COMPOUND && resource) {
|
||||||
|
if (resource->isLoaded()) {
|
||||||
|
const HFMModel& hfmModel = resource->getHFMModel();
|
||||||
|
|
||||||
|
Extents meshExtents = hfmModel.getMeshExtents();
|
||||||
|
glm::vec3 meshExtentsDiagonal = meshExtents.maximum - meshExtents.minimum;
|
||||||
|
glm::vec3 offset = -meshExtents.minimum- (meshExtentsDiagonal * getRegistrationPoint());
|
||||||
|
glm::vec3 scale(getScaledDimensions() / meshExtentsDiagonal);
|
||||||
|
|
||||||
|
glm::mat4 hfmToEntityMatrix = glm::scale(scale) * glm::translate(offset);
|
||||||
|
glm::mat4 entityToWorldMatrix = getTransform().getMatrix();
|
||||||
|
glm::mat4 worldToHFMMatrix = glm::inverse(entityToWorldMatrix * hfmToEntityMatrix);
|
||||||
|
|
||||||
|
return hfmModel.convexHullContains(glm::vec3(worldToHFMMatrix * glm::vec4(point, 1.0f)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return EntityItem::contains(point);
|
||||||
|
}
|
||||||
|
|
||||||
void ZoneEntityItem::setFilterURL(QString url) {
|
void ZoneEntityItem::setFilterURL(QString url) {
|
||||||
withWriteLock([&] {
|
withWriteLock([&] {
|
||||||
_filterURL = url;
|
_filterURL = url;
|
||||||
|
@ -326,10 +379,6 @@ QString ZoneEntityItem::getFilterURL() const {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ZoneEntityItem::hasCompoundShapeURL() const {
|
|
||||||
return !getCompoundShapeURL().isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString ZoneEntityItem::getCompoundShapeURL() const {
|
QString ZoneEntityItem::getCompoundShapeURL() const {
|
||||||
QString result;
|
QString result;
|
||||||
withReadLock([&] {
|
withReadLock([&] {
|
||||||
|
@ -403,3 +452,15 @@ void ZoneEntityItem::setSkyboxMode(const uint32_t value) {
|
||||||
uint32_t ZoneEntityItem::getSkyboxMode() const {
|
uint32_t ZoneEntityItem::getSkyboxMode() const {
|
||||||
return _skyboxMode;
|
return _skyboxMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ZoneEntityItem::fetchCollisionGeometryResource() {
|
||||||
|
QUrl hullURL(getCompoundShapeURL());
|
||||||
|
if (hullURL.isEmpty()) {
|
||||||
|
_shapeResource.reset();
|
||||||
|
} else {
|
||||||
|
QUrlQuery queryArgs(hullURL);
|
||||||
|
queryArgs.addQueryItem("collision-hull", "");
|
||||||
|
hullURL.setQuery(queryArgs);
|
||||||
|
_shapeResource = DependencyManager::get<ModelCache>()->getCollisionGeometryResource(hullURL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -12,6 +12,9 @@
|
||||||
#ifndef hifi_ZoneEntityItem_h
|
#ifndef hifi_ZoneEntityItem_h
|
||||||
#define hifi_ZoneEntityItem_h
|
#define hifi_ZoneEntityItem_h
|
||||||
|
|
||||||
|
#include <ComponentMode.h>
|
||||||
|
#include <model-networking/ModelCache.h>
|
||||||
|
|
||||||
#include "KeyLightPropertyGroup.h"
|
#include "KeyLightPropertyGroup.h"
|
||||||
#include "AmbientLightPropertyGroup.h"
|
#include "AmbientLightPropertyGroup.h"
|
||||||
#include "EntityItem.h"
|
#include "EntityItem.h"
|
||||||
|
@ -19,7 +22,6 @@
|
||||||
#include "SkyboxPropertyGroup.h"
|
#include "SkyboxPropertyGroup.h"
|
||||||
#include "HazePropertyGroup.h"
|
#include "HazePropertyGroup.h"
|
||||||
#include "BloomPropertyGroup.h"
|
#include "BloomPropertyGroup.h"
|
||||||
#include <ComponentMode.h>
|
|
||||||
|
|
||||||
class ZoneEntityItem : public EntityItem {
|
class ZoneEntityItem : public EntityItem {
|
||||||
public:
|
public:
|
||||||
|
@ -58,10 +60,9 @@ public:
|
||||||
static void setDrawZoneBoundaries(bool value) { _drawZoneBoundaries = value; }
|
static void setDrawZoneBoundaries(bool value) { _drawZoneBoundaries = value; }
|
||||||
|
|
||||||
virtual bool isReadyToComputeShape() const override { return false; }
|
virtual bool isReadyToComputeShape() const override { return false; }
|
||||||
void setShapeType(ShapeType type) override { withWriteLock([&] { _shapeType = type; }); }
|
virtual void setShapeType(ShapeType type) override;
|
||||||
virtual ShapeType getShapeType() const override;
|
virtual ShapeType getShapeType() const override;
|
||||||
|
|
||||||
virtual bool hasCompoundShapeURL() const;
|
|
||||||
QString getCompoundShapeURL() const;
|
QString getCompoundShapeURL() const;
|
||||||
virtual void setCompoundShapeURL(const QString& url);
|
virtual void setCompoundShapeURL(const QString& url);
|
||||||
|
|
||||||
|
@ -115,6 +116,8 @@ public:
|
||||||
BoxFace& face, glm::vec3& surfaceNormal,
|
BoxFace& face, glm::vec3& surfaceNormal,
|
||||||
QVariantMap& extraInfo, bool precisionPicking) const override;
|
QVariantMap& extraInfo, bool precisionPicking) const override;
|
||||||
|
|
||||||
|
bool contains(const glm::vec3& point) const override;
|
||||||
|
|
||||||
virtual void debugDump() const override;
|
virtual void debugDump() const override;
|
||||||
|
|
||||||
static const ShapeType DEFAULT_SHAPE_TYPE;
|
static const ShapeType DEFAULT_SHAPE_TYPE;
|
||||||
|
@ -156,6 +159,10 @@ protected:
|
||||||
|
|
||||||
static bool _drawZoneBoundaries;
|
static bool _drawZoneBoundaries;
|
||||||
static bool _zonesArePickable;
|
static bool _zonesArePickable;
|
||||||
|
|
||||||
|
void fetchCollisionGeometryResource();
|
||||||
|
GeometryResource::Pointer _shapeResource;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_ZoneEntityItem_h
|
#endif // hifi_ZoneEntityItem_h
|
||||||
|
|
|
@ -189,20 +189,17 @@ bool HFMModel::hasBlendedMeshes() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
Extents HFMModel::getUnscaledMeshExtents() const {
|
Extents HFMModel::getUnscaledMeshExtents() const {
|
||||||
const Extents& extents = meshExtents;
|
|
||||||
|
|
||||||
// even though our caller asked for "unscaled" we need to include any fst scaling, translation, and rotation, which
|
// even though our caller asked for "unscaled" we need to include any fst scaling, translation, and rotation, which
|
||||||
// is captured in the offset matrix
|
// is captured in the offset matrix
|
||||||
glm::vec3 minimum = glm::vec3(offset * glm::vec4(extents.minimum, 1.0f));
|
glm::vec3 minimum = glm::vec3(offset * glm::vec4(meshExtents.minimum, 1.0f));
|
||||||
glm::vec3 maximum = glm::vec3(offset * glm::vec4(extents.maximum, 1.0f));
|
glm::vec3 maximum = glm::vec3(offset * glm::vec4(meshExtents.maximum, 1.0f));
|
||||||
Extents scaledExtents = { minimum, maximum };
|
Extents scaledExtents = { minimum, maximum };
|
||||||
|
|
||||||
return scaledExtents;
|
return scaledExtents;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Move to graphics::Mesh when Sam's ready
|
// TODO: Move to graphics::Mesh when Sam's ready
|
||||||
bool HFMModel::convexHullContains(const glm::vec3& point) const {
|
bool HFMModel::convexHullContains(const glm::vec3& point) const {
|
||||||
if (!getUnscaledMeshExtents().containsPoint(point)) {
|
if (!meshExtents.containsPoint(point)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -310,6 +310,7 @@ public:
|
||||||
|
|
||||||
/// Returns the unscaled extents of the model's mesh
|
/// Returns the unscaled extents of the model's mesh
|
||||||
Extents getUnscaledMeshExtents() const;
|
Extents getUnscaledMeshExtents() const;
|
||||||
|
const Extents& getMeshExtents() const { return meshExtents; }
|
||||||
|
|
||||||
bool convexHullContains(const glm::vec3& point) const;
|
bool convexHullContains(const glm::vec3& point) const;
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
set(TARGET_NAME physics)
|
set(TARGET_NAME physics)
|
||||||
setup_hifi_library()
|
setup_hifi_library()
|
||||||
link_hifi_libraries(shared task workload fbx entities graphics)
|
link_hifi_libraries(shared task workload fbx entities graphics shaders)
|
||||||
include_hifi_library_headers(networking)
|
include_hifi_library_headers(networking)
|
||||||
include_hifi_library_headers(gpu)
|
include_hifi_library_headers(gpu)
|
||||||
include_hifi_library_headers(avatars)
|
include_hifi_library_headers(avatars)
|
||||||
include_hifi_library_headers(audio)
|
include_hifi_library_headers(audio)
|
||||||
include_hifi_library_headers(octree)
|
include_hifi_library_headers(octree)
|
||||||
include_hifi_library_headers(animation)
|
include_hifi_library_headers(animation)
|
||||||
|
include_hifi_library_headers(model-networking)
|
||||||
|
include_hifi_library_headers(image)
|
||||||
|
include_hifi_library_headers(ktx)
|
||||||
|
include_hifi_library_headers(gpu)
|
||||||
|
include_hifi_library_headers(hfm)
|
||||||
|
|
||||||
target_bullet()
|
target_bullet()
|
||||||
|
|
|
@ -91,7 +91,6 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer
|
||||||
_serverRotation = localTransform.getRotation();
|
_serverRotation = localTransform.getRotation();
|
||||||
_serverAcceleration = _entity->getAcceleration();
|
_serverAcceleration = _entity->getAcceleration();
|
||||||
_serverActionData = _entity->getDynamicData();
|
_serverActionData = _entity->getDynamicData();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
EntityMotionState::~EntityMotionState() {
|
EntityMotionState::~EntityMotionState() {
|
||||||
|
|
|
@ -614,58 +614,11 @@ bool Model::findParabolaIntersectionAgainstSubMeshes(const glm::vec3& origin, co
|
||||||
return intersectedSomething;
|
return intersectedSomething;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Model::convexHullContains(glm::vec3 point) {
|
glm::mat4 Model::getWorldToHFMMatrix() const {
|
||||||
// if we aren't active, we can't compute that yet...
|
glm::mat4 hfmToModelMatrix = glm::scale(_scale) * glm::translate(_offset);
|
||||||
if (!isActive()) {
|
glm::mat4 modelToWorldMatrix = createMatFromQuatAndPos(_rotation, _translation);
|
||||||
return false;
|
glm::mat4 worldToHFMMatrix = glm::inverse(modelToWorldMatrix * hfmToModelMatrix);
|
||||||
}
|
return worldToHFMMatrix;
|
||||||
|
|
||||||
// extents is the entity relative, scaled, centered extents of the entity
|
|
||||||
glm::vec3 position = _translation;
|
|
||||||
glm::mat4 rotation = glm::mat4_cast(_rotation);
|
|
||||||
glm::mat4 translation = glm::translate(position);
|
|
||||||
glm::mat4 modelToWorldMatrix = translation * rotation;
|
|
||||||
glm::mat4 worldToModelMatrix = glm::inverse(modelToWorldMatrix);
|
|
||||||
|
|
||||||
Extents modelExtents = getMeshExtents(); // NOTE: unrotated
|
|
||||||
|
|
||||||
glm::vec3 dimensions = modelExtents.maximum - modelExtents.minimum;
|
|
||||||
glm::vec3 corner = -(dimensions * _registrationPoint);
|
|
||||||
AABox modelFrameBox(corner, dimensions);
|
|
||||||
|
|
||||||
glm::vec3 modelFramePoint = glm::vec3(worldToModelMatrix * glm::vec4(point, 1.0f));
|
|
||||||
|
|
||||||
// we can use the AABox's contains() by mapping our point into the model frame
|
|
||||||
// and testing there.
|
|
||||||
if (modelFrameBox.contains(modelFramePoint)){
|
|
||||||
QMutexLocker locker(&_mutex);
|
|
||||||
|
|
||||||
if (!_triangleSetsValid) {
|
|
||||||
calculateTriangleSets(getHFMModel());
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we are inside the models box, then consider the submeshes...
|
|
||||||
glm::mat4 meshToModelMatrix = glm::scale(_scale) * glm::translate(_offset);
|
|
||||||
glm::mat4 meshToWorldMatrix = createMatFromQuatAndPos(_rotation, _translation) * meshToModelMatrix;
|
|
||||||
glm::mat4 worldToMeshMatrix = glm::inverse(meshToWorldMatrix);
|
|
||||||
glm::vec3 meshFramePoint = glm::vec3(worldToMeshMatrix * glm::vec4(point, 1.0f));
|
|
||||||
|
|
||||||
for (auto& meshTriangleSets : _modelSpaceMeshTriangleSets) {
|
|
||||||
for (auto &partTriangleSet : meshTriangleSets) {
|
|
||||||
const AABox& box = partTriangleSet.getBounds();
|
|
||||||
if (box.contains(meshFramePoint)) {
|
|
||||||
if (partTriangleSet.convexHullContains(meshFramePoint)) {
|
|
||||||
// It's inside this mesh, return true.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
// It wasn't in any mesh, return false.
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: deprecate and remove
|
// TODO: deprecate and remove
|
||||||
|
|
|
@ -192,7 +192,7 @@ public:
|
||||||
bool didVisualGeometryRequestFail() const { return _visualGeometryRequestFailed; }
|
bool didVisualGeometryRequestFail() const { return _visualGeometryRequestFailed; }
|
||||||
bool didCollisionGeometryRequestFail() const { return _collisionGeometryRequestFailed; }
|
bool didCollisionGeometryRequestFail() const { return _collisionGeometryRequestFailed; }
|
||||||
|
|
||||||
bool convexHullContains(glm::vec3 point);
|
glm::mat4 getWorldToHFMMatrix() const;
|
||||||
|
|
||||||
QStringList getJointNames() const;
|
QStringList getJointNames() const;
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,8 @@ const char* shapeTypeNames[] = {
|
||||||
"compound",
|
"compound",
|
||||||
"simple-hull",
|
"simple-hull",
|
||||||
"simple-compound",
|
"simple-compound",
|
||||||
"static-mesh"
|
"static-mesh",
|
||||||
|
"ellipsoid"
|
||||||
};
|
};
|
||||||
|
|
||||||
static const size_t SHAPETYPE_NAME_COUNT = (sizeof(shapeTypeNames) / sizeof((shapeTypeNames)[0]));
|
static const size_t SHAPETYPE_NAME_COUNT = (sizeof(shapeTypeNames) / sizeof((shapeTypeNames)[0]));
|
||||||
|
@ -250,50 +251,6 @@ float ShapeInfo::computeVolume() const {
|
||||||
return volume;
|
return volume;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ShapeInfo::contains(const glm::vec3& point) const {
|
|
||||||
switch(_type) {
|
|
||||||
case SHAPE_TYPE_SPHERE:
|
|
||||||
return glm::length(point) <= _halfExtents.x;
|
|
||||||
case SHAPE_TYPE_CYLINDER_X:
|
|
||||||
return glm::length(glm::vec2(point.y, point.z)) <= _halfExtents.z;
|
|
||||||
case SHAPE_TYPE_CYLINDER_Y:
|
|
||||||
return glm::length(glm::vec2(point.x, point.z)) <= _halfExtents.x;
|
|
||||||
case SHAPE_TYPE_CYLINDER_Z:
|
|
||||||
return glm::length(glm::vec2(point.x, point.y)) <= _halfExtents.y;
|
|
||||||
case SHAPE_TYPE_CAPSULE_X: {
|
|
||||||
if (glm::abs(point.x) <= _halfExtents.x - _halfExtents.y) {
|
|
||||||
return glm::length(glm::vec2(point.y, point.z)) <= _halfExtents.y;
|
|
||||||
} else {
|
|
||||||
glm::vec3 absPoint = glm::abs(point) - glm::vec3(_halfExtents.x, 0.0f, 0.0f);
|
|
||||||
return glm::length(absPoint) <= _halfExtents.y;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case SHAPE_TYPE_CAPSULE_Y: {
|
|
||||||
if (glm::abs(point.y) <= _halfExtents.y - _halfExtents.z) {
|
|
||||||
return glm::length(glm::vec2(point.x, point.z)) <= _halfExtents.z;
|
|
||||||
} else {
|
|
||||||
glm::vec3 absPoint = glm::abs(point) - glm::vec3(0.0f, _halfExtents.y, 0.0f);
|
|
||||||
return glm::length(absPoint) <= _halfExtents.z;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case SHAPE_TYPE_CAPSULE_Z: {
|
|
||||||
if (glm::abs(point.z) <= _halfExtents.z - _halfExtents.x) {
|
|
||||||
return glm::length(glm::vec2(point.x, point.y)) <= _halfExtents.x;
|
|
||||||
} else {
|
|
||||||
glm::vec3 absPoint = glm::abs(point) - glm::vec3(0.0f, 0.0f, _halfExtents.z);
|
|
||||||
return glm::length(absPoint) <= _halfExtents.x;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case SHAPE_TYPE_BOX:
|
|
||||||
default: {
|
|
||||||
glm::vec3 absPoint = glm::abs(point);
|
|
||||||
return absPoint.x <= _halfExtents.x
|
|
||||||
&& absPoint.y <= _halfExtents.y
|
|
||||||
&& absPoint.z <= _halfExtents.z;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const HashKey& ShapeInfo::getHash() const {
|
const HashKey& ShapeInfo::getHash() const {
|
||||||
// NOTE: we cache the key so we only ever need to compute it once for any valid ShapeInfo instance.
|
// NOTE: we cache the key so we only ever need to compute it once for any valid ShapeInfo instance.
|
||||||
if (_hashKey.isNull() && _type != SHAPE_TYPE_NONE) {
|
if (_hashKey.isNull() && _type != SHAPE_TYPE_NONE) {
|
||||||
|
|
|
@ -86,10 +86,6 @@ public:
|
||||||
|
|
||||||
float computeVolume() const;
|
float computeVolume() const;
|
||||||
|
|
||||||
/// Returns whether point is inside the shape
|
|
||||||
/// For compound shapes it will only return whether it is inside the bounding box
|
|
||||||
bool contains(const glm::vec3& point) const;
|
|
||||||
|
|
||||||
const HashKey& getHash() const;
|
const HashKey& getHash() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
|
@ -372,6 +372,7 @@ const DEFAULT_ENTITY_PROPERTIES = {
|
||||||
blue: 179
|
blue: 179
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
shapeType: "box",
|
||||||
bloomMode: "inherit"
|
bloomMode: "inherit"
|
||||||
},
|
},
|
||||||
Model: {
|
Model: {
|
||||||
|
|
|
@ -1321,6 +1321,24 @@ const GROUPS = [
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "zone_shape",
|
||||||
|
label: "ZONE SHAPE",
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
label: "Shape Type",
|
||||||
|
type: "dropdown",
|
||||||
|
options: { "box": "Box", "sphere": "Sphere", "ellipsoid": "Ellipsoid",
|
||||||
|
"cylinder-y": "Cylinder", "compound": "Use Compound Shape URL" },
|
||||||
|
propertyID: "shapeType",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Compound Shape URL",
|
||||||
|
type: "string",
|
||||||
|
propertyID: "compoundShapeURL",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "physics",
|
id: "physics",
|
||||||
label: "PHYSICS",
|
label: "PHYSICS",
|
||||||
|
@ -1417,7 +1435,7 @@ const GROUPS_PER_TYPE = {
|
||||||
None: [ 'base', 'spatial', 'behavior', 'collision', 'physics' ],
|
None: [ 'base', 'spatial', 'behavior', 'collision', 'physics' ],
|
||||||
Shape: [ 'base', 'shape', 'spatial', 'behavior', 'collision', 'physics' ],
|
Shape: [ 'base', 'shape', 'spatial', 'behavior', 'collision', 'physics' ],
|
||||||
Text: [ 'base', 'text', 'spatial', 'behavior', 'collision', 'physics' ],
|
Text: [ 'base', 'text', 'spatial', 'behavior', 'collision', 'physics' ],
|
||||||
Zone: [ 'base', 'zone', 'spatial', 'behavior', 'collision', 'physics' ],
|
Zone: [ 'base', 'zone', 'spatial', 'behavior', 'zone_shape', 'physics' ],
|
||||||
Model: [ 'base', 'model', 'spatial', 'behavior', 'collision', 'physics' ],
|
Model: [ 'base', 'model', 'spatial', 'behavior', 'collision', 'physics' ],
|
||||||
Image: [ 'base', 'image', 'spatial', 'behavior', 'collision', 'physics' ],
|
Image: [ 'base', 'image', 'spatial', 'behavior', 'collision', 'physics' ],
|
||||||
Web: [ 'base', 'web', 'spatial', 'behavior', 'collision', 'physics' ],
|
Web: [ 'base', 'web', 'spatial', 'behavior', 'collision', 'physics' ],
|
||||||
|
|
Loading…
Reference in a new issue