fixes because i neglected to test the uf::graph changes on other scenes (just to realize i really need to optimize the internal physics system...)
This commit is contained in:
parent
3b5db2c32c
commit
0ce6b57867
6
Makefile
6
Makefile
@ -66,7 +66,7 @@ ifneq (,$(findstring win64,$(ARCH)))
|
||||
REQ_DEPS += meshoptimizer toml xatlas curl ffx:fsr dc:texconv # vall_e cpptrace # openvr # ncurses draco discord bullet ultralight-ux
|
||||
FLAGS += -march=native -g # -flto # -g
|
||||
endif
|
||||
REQ_DEPS += $(RENDERER) json:nlohmann zlib luajit simd ctti gltf imgui fmt freetype openal ogg wav # reactphysics
|
||||
REQ_DEPS += $(RENDERER) json:nlohmann zlib luajit reactphysics simd ctti gltf imgui fmt freetype openal ogg wav
|
||||
FLAGS += -DUF_ENV_WINDOWS -DUF_ENV_WIN64 -DWIN32_LEAN_AND_MEAN
|
||||
DEPS += -lgdi32 -ldwmapi
|
||||
LINKS += #-Wl,-subsystem,windows
|
||||
@ -76,13 +76,13 @@ else ifneq (,$(findstring linux,$(ARCH)))
|
||||
REQ_DEPS += toml xatlas curl dc:texconv # meshoptimizer ffx:fsr cpptrace vall_e # ncurses openvr draco discord bullet ultralight-ux
|
||||
FLAGS += -march=native -g # -flto # -g
|
||||
endif
|
||||
REQ_DEPS += $(RENDERER) json:nlohmann zlib luajit simd ctti gltf imgui fmt freetype openal ogg wav # reactphysics
|
||||
REQ_DEPS += $(RENDERER) json:nlohmann zlib luajit reactphysics simd ctti gltf imgui fmt freetype openal ogg wav
|
||||
FLAGS += -DUF_ENV_LINUX -fPIC
|
||||
DEPS += -pthread -ldl -lX11 -lXrandr
|
||||
INCS := -I./dep/master/include $(INCS)
|
||||
else ifneq (,$(findstring dreamcast,$(ARCH)))
|
||||
FLAGS += -DUF_ENV_DREAMCAST # -DUF_LEAN_AND_MEAN # this apparently crashes
|
||||
REQ_DEPS += opengl gldc json:nlohmann zlib lua simd ctti fmt freetype openal aldc ogg wav png # imgui # reactphysics
|
||||
REQ_DEPS += opengl gldc json:nlohmann zlib lua reactphysics simd ctti fmt freetype openal aldc ogg wav png # imgui
|
||||
INCS := -I./dep/dreamcast/include $(INCS)
|
||||
endif
|
||||
|
||||
|
||||
@ -6,12 +6,7 @@
|
||||
"metadata": {
|
||||
"holdable": true,
|
||||
"physics": {
|
||||
// "gravity": [ 0, -9.81, 0 ],
|
||||
// "inertia": [10, 10, 10],
|
||||
// "mass": 10,
|
||||
|
||||
"type": "bounding box",
|
||||
"recenter": false
|
||||
"type": "bounding box"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -36,7 +36,7 @@
|
||||
// "/^light_[^e]/": { "ignore": true },
|
||||
|
||||
// regexp matches
|
||||
"/^worldspawn_/": { "physics": { "type": "mesh", "static": true } },
|
||||
// "/^worldspawn_/": { "physics": { "type": "mesh", "static": true } },
|
||||
"/^func_door_/": { "action": "load", "payload": { "import": "/door.json", "metadata": { "angle":-1.570795, "normal": [-1,0,0] } } },
|
||||
"/^prop_door_/": { "action": "load", "payload": { "import": "/door.json", "metadata": { "angle":-1.570795, "normal": [-1,0,0] } } },
|
||||
"/^prop_static/": { /*"action": "load", "payload": { "import": "/prop.json", "metadata": { "physics": { "gravity": [ 0, 0, 0 ] } } }*/ },
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
"import": "./base_sourceengine.json",
|
||||
"assets": [
|
||||
// { "filename": "./models/mds_mcdonalds.glb" },
|
||||
{ "filename": "./models/mds_mcdonalds/graph.json" },
|
||||
{ "filename": "/burger.json", "delay": 1 }
|
||||
{ "filename": "./models/mds_mcdonalds/graph.json" }
|
||||
// { "filename": "/burger.json", "delay": 1 }
|
||||
],
|
||||
"metadata": {
|
||||
"graph": {
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
{
|
||||
// "import": "./rp_downtown_v2.json"
|
||||
"import": "./ss2_medsci1.json"
|
||||
// "import": "./ss2_medsci1.json"
|
||||
// "import": "./test_grid.json"
|
||||
// "import": "./sh2_mcdonalds.json"
|
||||
// "import": "./animal_crossing.json"
|
||||
// "import": "./mds_mcdonalds.json"
|
||||
"import": "./mds_mcdonalds.json"
|
||||
// "import": "./gm_construct.json"
|
||||
}
|
||||
@ -2,9 +2,9 @@
|
||||
"import": "./base_sourceengine.json",
|
||||
"assets": [
|
||||
// { "filename": "./models/ss2_medsci1.glb" }
|
||||
// { "filename": "./models/ss2_medsci1/graph.json" }
|
||||
{ "filename": "./models/ss2_medsci1/graph.json" }
|
||||
// { "filename": "./models/ss2_medsci1_small.glb" }
|
||||
{ "filename": "./models/ss2_medsci1_small/graph.json" }
|
||||
// { "filename": "./models/ss2_medsci1_small/graph.json" }
|
||||
// { "filename": "./models/ss2_medsci1_smallish.glb" }
|
||||
// { "filename": "./models/ss2_medsci1_smallish/graph.json" }
|
||||
],
|
||||
|
||||
@ -1 +1 @@
|
||||
opengl
|
||||
vulkan
|
||||
@ -29,6 +29,8 @@ namespace pod {
|
||||
|
||||
// Render information
|
||||
uf::stl::vector<uf::stl::string> primitives; //
|
||||
uf::stl::vector<uf::stl::string> instances; // ugh
|
||||
uf::stl::vector<uf::stl::string> drawCommands; // ugh
|
||||
uf::stl::vector<uf::stl::string> meshes; //
|
||||
|
||||
uf::stl::vector<uf::stl::string> images; //
|
||||
@ -79,8 +81,10 @@ namespace pod {
|
||||
|
||||
// Local storage, used for save/load
|
||||
struct Storage {
|
||||
uf::stl::KeyMap<pod::Instance> instances; // should be unused but is a counter for global instance IDs because I cannot for the life of me figure out a working way to remap otherwise
|
||||
uf::stl::KeyMap<pod::Instance::Addresses> instanceAddresses;
|
||||
uf::stl::KeyMap<uf::stl::vector<pod::Primitive>> primitives;
|
||||
uf::stl::KeyMap<uf::stl::vector<pod::DrawCommand>> drawCommands; // unused
|
||||
uf::stl::KeyMap<uf::Mesh> meshes;
|
||||
|
||||
uf::stl::KeyMap<uf::Image> images;
|
||||
|
||||
@ -62,9 +62,36 @@ namespace pod {
|
||||
|
||||
typedef uint32_t CollisionMask;
|
||||
|
||||
|
||||
struct Collider {
|
||||
enum CategoryMask : uint32_t {
|
||||
CATEGORY_NONE = 0,
|
||||
CATEGORY_STATIC = 1 << 0,
|
||||
CATEGORY_DYNAMIC = 1 << 1,
|
||||
CATEGORY_PLAYER = 1 << 2,
|
||||
CATEGORY_NPC = 1 << 3,
|
||||
CATEGORY_TRIGGER = 1 << 4,
|
||||
CATEGORY_PROJECTILE = 1 << 5,
|
||||
CATEGORY_CHARACTER = CATEGORY_PLAYER | CATEGORY_NPC,
|
||||
CATEGORY_ALL = 0xFFFFFFFF
|
||||
};
|
||||
|
||||
enum CollisionMask : uint32_t {
|
||||
MASK_NONE = 0,
|
||||
MASK_STATIC = CATEGORY_DYNAMIC | CATEGORY_PLAYER | CATEGORY_NPC | CATEGORY_PROJECTILE,
|
||||
MASK_DYNAMIC = CATEGORY_STATIC | CATEGORY_DYNAMIC | CATEGORY_PLAYER | CATEGORY_NPC,
|
||||
MASK_PLAYER = CATEGORY_STATIC | CATEGORY_DYNAMIC | CATEGORY_NPC | CATEGORY_PROJECTILE,
|
||||
MASK_NPC = CATEGORY_STATIC | CATEGORY_DYNAMIC | CATEGORY_PLAYER | CATEGORY_PROJECTILE,
|
||||
MASK_TRIGGER = CATEGORY_PLAYER | CATEGORY_NPC,
|
||||
MASK_PROJECTILE = CATEGORY_STATIC | CATEGORY_DYNAMIC | CATEGORY_PLAYER | CATEGORY_NPC,
|
||||
MASK_CHARACTER = MASK_PLAYER | MASK_NPC,
|
||||
MASK_ALL = 0xFFFFFFFF
|
||||
};
|
||||
|
||||
pod::ShapeType type;
|
||||
pod::CollisionMask mask = 0xFFFFFFFF;
|
||||
pod::CollisionMask category = Collider::CATEGORY_ALL;
|
||||
pod::CollisionMask mask = Collider::MASK_ALL;
|
||||
|
||||
union {
|
||||
pod::Sphere sphere;
|
||||
pod::AABB aabb;
|
||||
@ -105,6 +132,8 @@ namespace pod {
|
||||
pod::Vector3f inertiaTensor = { 1, 1, 1 };
|
||||
pod::Vector3f inverseInertiaTensor = { 1, 1, 1 };
|
||||
|
||||
pod::Vector3f gravity = { NAN, NAN, NAN }; // an invalid gravity will fallback to world gravity
|
||||
|
||||
pod::AABB bounds;
|
||||
pod::Collider collider;
|
||||
pod::PhysicsMaterial material;
|
||||
@ -169,7 +198,15 @@ namespace uf {
|
||||
void UF_API substep( pod::World&, float dt, int substeps );
|
||||
|
||||
void UF_API setMass( pod::RigidBody& body, float mass = 0.0f );
|
||||
void UF_API setInertia( pod::RigidBody& body );
|
||||
void UF_API setColliderCategory( pod::RigidBody&, uint32_t category = pod::Collider::CATEGORY_ALL );
|
||||
void UF_API setColliderCategory( pod::RigidBody&, const uf::stl::string& );
|
||||
void UF_API setColliderMask( pod::RigidBody&, uint32_t mask = pod::Collider::MASK_ALL );
|
||||
void UF_API setColliderMask( pod::RigidBody&, const uf::stl::string& );
|
||||
void UF_API setGravity( pod::RigidBody&, const pod::Vector3f& = { NAN, NAN, NAN } );
|
||||
pod::Vector3f UF_API getGravity( pod::RigidBody& );
|
||||
|
||||
void UF_API updateInertia( pod::RigidBody& body );
|
||||
|
||||
void UF_API applyForce( pod::RigidBody& body, const pod::Vector3f& force );
|
||||
void UF_API applyForceAtPoint( pod::RigidBody body, const pod::Vector3f& force, const pod::Vector3f& point );
|
||||
void UF_API applyImpulse( pod::RigidBody& body, const pod::Vector3f& impulse );
|
||||
|
||||
@ -297,7 +297,7 @@ void ext::PlayerBehavior::tick( uf::Object& self ) {
|
||||
if ( query.hit ) {
|
||||
if ( metadata.movement.floored.print ) UF_MSG_DEBUG("{}: {} | {}", query.contact.penetration, uf::string::toString(*query.body->object), uf::vector::toString(physics.velocity));
|
||||
stats.floored = true;
|
||||
if ( physics.velocity.y < 0.0f ) physics.velocity.y = 0.0f;
|
||||
//if ( physics.velocity.y < 0.0f ) physics.velocity.y = 0.0f;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@ -348,6 +348,8 @@ void ext::PlayerBehavior::tick( uf::Object& self ) {
|
||||
|
||||
#if UF_USE_REACTPHYSICS
|
||||
if ( collider.stats.gravity == pod::Vector3f{0,0,0} ) stats.noclipped = true;
|
||||
#else
|
||||
if ( physics.gravity == pod::Vector3f{0,0,0} ) stats.noclipped = true;
|
||||
#endif
|
||||
|
||||
{
|
||||
@ -429,6 +431,11 @@ void ext::PlayerBehavior::tick( uf::Object& self ) {
|
||||
translator.forward.y += cameraTransform.forward.y;
|
||||
translator.forward = uf::vector::normalize( translator.forward );
|
||||
}
|
||||
#else
|
||||
else if ( stats.noclipped || physics.gravity == pod::Vector3f{0,0,0} ){
|
||||
translator.forward.y += cameraTransform.forward.y;
|
||||
translator.forward = uf::vector::normalize( translator.forward );
|
||||
}
|
||||
#endif
|
||||
|
||||
if ( metadata.system.control ) {
|
||||
@ -441,6 +448,16 @@ void ext::PlayerBehavior::tick( uf::Object& self ) {
|
||||
collider.body->enableGravity(!state);
|
||||
uf::physics::impl::activateCollision(collider, !state);
|
||||
}
|
||||
#else
|
||||
if ( !state ) {
|
||||
uf::physics::impl::setGravity( physics );
|
||||
uf::physics::impl::setColliderCategory( physics, "ALL");
|
||||
uf::physics::impl::setColliderMask( physics, "ALL");
|
||||
} else {
|
||||
uf::physics::impl::setGravity( physics, pod::Vector3f{0,0,0});
|
||||
uf::physics::impl::setColliderCategory( physics, "NONE");
|
||||
uf::physics::impl::setColliderMask( physics, "NONE");
|
||||
}
|
||||
#endif
|
||||
|
||||
stats.noclipped = state;
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
#include <uf/utils/math/physics.h>
|
||||
#include <uf/utils/camera/camera.h>
|
||||
#include <uf/ext/xatlas/xatlas.h>
|
||||
#include <uf/utils/io/fmt.h>
|
||||
|
||||
// it's too unstable right now to do multithreaded loading, perhaps there's a better way
|
||||
#if UF_USE_OPENGL
|
||||
@ -414,6 +415,17 @@ void uf::graph::load( pod::Graph& graph, const uf::stl::string& filename, const
|
||||
graph.settings.stream.hash = graph.metadata["stream"]["hash"].as(graph.settings.stream.hash);
|
||||
graph.settings.stream.lastUpdate = graph.metadata["stream"]["lastUpdate"].as(graph.settings.stream.lastUpdate);
|
||||
}
|
||||
// store offsets
|
||||
{
|
||||
size_t instances = 0;
|
||||
for ( auto& key : storage.primitives.keys ) {
|
||||
instances += storage.primitives.map[key].size();
|
||||
}
|
||||
|
||||
graph.metadata["offsets"]["instances"] = instances;
|
||||
graph.metadata["offsets"]["materials"] = storage.materials.keys.size();
|
||||
graph.metadata["offsets"]["joints"] = storage.joints.keys.size();
|
||||
}
|
||||
|
||||
uf::stl::string key = graph.metadata["key"].as<uf::stl::string>("");
|
||||
if ( key != "" ) {
|
||||
|
||||
@ -1043,14 +1043,6 @@ void uf::graph::process( pod::Graph& graph ) {
|
||||
auto& drawCommand = primitive.drawCommand;
|
||||
auto& instance = primitive.instance;
|
||||
|
||||
// remap instance ID
|
||||
if ( 0 <= drawCommand.instanceID && drawCommand.instanceID < graph.primitives.size() ) {
|
||||
for ( auto key : storage.primitives.keys ) {
|
||||
if ( key == name ) break;
|
||||
drawCommand.instanceID += storage.primitives[key].size();
|
||||
}
|
||||
}
|
||||
|
||||
if ( 0 <= instance.materialID && instance.materialID < graph.materials.size() ) {
|
||||
auto& keys = storage.materials.keys;
|
||||
auto& indices = storage.materials.indices;
|
||||
@ -1291,18 +1283,19 @@ void uf::graph::process( pod::Graph& graph, int32_t index, uf::Object& parent )
|
||||
}
|
||||
|
||||
// what a mess
|
||||
node.object = storage.entities.keys.size();
|
||||
auto objectKeyName = ::objectKey( node );
|
||||
storage.entities[objectKeyName] = &entity;
|
||||
auto& objectData = storage.objects[objectKeyName];
|
||||
auto model = uf::transform::model( transform );
|
||||
objectData.model = model;
|
||||
objectData.previous = model;
|
||||
|
||||
//
|
||||
if ( 0 <= node.mesh && node.mesh < graph.meshes.size() ) {
|
||||
auto& mesh = storage.meshes.map[graph.meshes[node.mesh]];
|
||||
auto& primitives = storage.primitives.map[graph.primitives[node.mesh]];
|
||||
|
||||
node.object = storage.entities.keys.size();
|
||||
auto objectKeyName = ::objectKey( node );
|
||||
storage.entities[objectKeyName] = &entity;
|
||||
auto& objectData = storage.objects[objectKeyName];
|
||||
auto model = uf::transform::model( transform );
|
||||
objectData.model = model;
|
||||
objectData.previous = model;
|
||||
|
||||
pod::Instance::Bounds bounds;
|
||||
pod::DrawCommand* drawCommands = NULL;
|
||||
@ -1316,11 +1309,19 @@ void uf::graph::process( pod::Graph& graph, int32_t index, uf::Object& parent )
|
||||
auto& primitive = primitives[primitiveID];
|
||||
auto& instance = primitive.instance;
|
||||
auto& drawCommand = primitive.drawCommand;
|
||||
auto instanceID = primitiveID;
|
||||
|
||||
instance.objectID = node.object;
|
||||
instance.jointID = graphMetadataJson["renderer"]["skinned"].as<bool>() ? 0 : -1;
|
||||
|
||||
// to-do: fix needing this to fix graphs
|
||||
#if 0
|
||||
auto instanceID = primitiveID;
|
||||
#else
|
||||
size_t instanceID = storage.instances.keys.size();
|
||||
auto instanceKeyName = graph.instances.emplace_back(std::to_string(instanceID));
|
||||
storage.instances[instanceKeyName] = instance;
|
||||
#endif
|
||||
|
||||
bounds.min = uf::vector::min( bounds.min, instance.bounds.min );
|
||||
bounds.max = uf::vector::max( bounds.max, instance.bounds.max );
|
||||
|
||||
|
||||
@ -157,7 +157,11 @@ void uf::ObjectBehavior::initialize( uf::Object& self ) {
|
||||
auto type = metadataJsonPhysics["type"].as<uf::stl::string>();
|
||||
float mass = metadataJsonPhysics["mass"].as<float>();
|
||||
|
||||
auto category = metadataJsonPhysics["category"].as<uf::stl::string>("ALL");
|
||||
auto mask = metadataJsonPhysics["mask"].as<uf::stl::string>("ALL");
|
||||
|
||||
pod::Vector3f offset = uf::vector::decode( metadataJsonPhysics["offset"], pod::Vector3f{} );
|
||||
pod::Vector3f gravity = uf::vector::decode( metadataJsonPhysics["gravity"], pod::Vector3f{ NAN, NAN, NAN } );
|
||||
|
||||
if ( type == "bounding box" || type == "aabb" ) {
|
||||
pod::Vector3f min = uf::vector::decode( metadataJsonPhysics["min"], pod::Vector3f{-0.5f, -0.5f, -0.5f} );
|
||||
@ -175,14 +179,18 @@ void uf::ObjectBehavior::initialize( uf::Object& self ) {
|
||||
uf::physics::impl::create( self, pod::Sphere{ radius }, mass, offset );
|
||||
} else if ( type == "capsule" ) {
|
||||
float radius = metadataJsonPhysics["radius"].as<float>();
|
||||
float height = metadataJsonPhysics["height"].as<float>();
|
||||
float halfHeight = metadataJsonPhysics["height"].as<float>() * 0.5f;
|
||||
|
||||
uf::physics::impl::create( self, pod::Capsule{ radius, height * 0.5f }, mass, offset );
|
||||
uf::physics::impl::create( self, pod::Capsule{ radius, halfHeight }, mass, offset );
|
||||
}
|
||||
|
||||
if ( this->hasComponent<pod::RigidBody>() ) {
|
||||
auto& body = this->getComponent<pod::RigidBody>();
|
||||
|
||||
uf::physics::impl::setColliderCategory( body, category );
|
||||
uf::physics::impl::setColliderMask( body, mask );
|
||||
uf::physics::impl::setGravity( body, gravity );
|
||||
|
||||
if ( this->getName() == "Player" ) {
|
||||
body.inertiaTensor = { FLT_MAX, FLT_MAX, FLT_MAX };
|
||||
body.inverseInertiaTensor = { 0.0f, 0.0f, 0.0f };
|
||||
|
||||
@ -253,7 +253,8 @@ void ext::gltf::load( pod::Graph& graph, const uf::stl::string& filename, const
|
||||
}
|
||||
// load meshes
|
||||
{
|
||||
size_t masterAuxID = 0;
|
||||
size_t masterInstanceID = 0;
|
||||
|
||||
graph.meshes.reserve(model.meshes.size());
|
||||
storage.meshes.reserve(model.meshes.size());
|
||||
|
||||
|
||||
@ -383,17 +383,14 @@ if ( meshopt.should ) {
|
||||
primitives.reserve( meshlets.size() );
|
||||
|
||||
for ( auto& meshlet : meshlets ) {
|
||||
// to-do: check against the meshlet's actual primitive information
|
||||
auto& drawCommand = drawCommands.emplace_back(pod::DrawCommand{
|
||||
.indices = meshlet.indices.size(),
|
||||
.instances = 1,
|
||||
.indexID = indexID,
|
||||
.vertexID = vertexID,
|
||||
.instanceID = meshlet.primitive.drawCommand.instanceID,
|
||||
.auxID = meshlet.primitive.drawCommand.auxID,
|
||||
.materialID = meshlet.primitive.drawCommand.materialID,
|
||||
.vertices = meshlet.vertices.size(),
|
||||
});
|
||||
meshlet.primitive.drawCommand.instances = 1;
|
||||
meshlet.primitive.drawCommand.instanceID = ++masterInstanceID; // this doesn't matter......
|
||||
meshlet.primitive.drawCommand.indexID = indexID;
|
||||
meshlet.primitive.drawCommand.indices = meshlet.indices.size();
|
||||
meshlet.primitive.drawCommand.vertexID = vertexID;
|
||||
meshlet.primitive.drawCommand.vertices = meshlet.vertices.size();
|
||||
|
||||
drawCommands.emplace_back(meshlet.primitive.drawCommand);
|
||||
|
||||
primitives.emplace_back( meshlet.primitive );
|
||||
|
||||
|
||||
@ -62,53 +62,79 @@ size_t ext::xatlas::unwrapExperimental( pod::Graph& graph ) {
|
||||
source = mesh;
|
||||
source.updateDescriptor();
|
||||
|
||||
for (auto& view : source.makeViews({"position", "uv", "st", "index"})) {
|
||||
auto pos = view["position"];
|
||||
auto uv = view["uv"];
|
||||
auto st = view["st"];
|
||||
auto idx = view["index"];
|
||||
uf::Mesh::Input vertexInput = mesh.vertex;
|
||||
|
||||
// Must have positions + indices
|
||||
if (!pos.valid() || !idx.valid()) continue;
|
||||
if (view.vertex.count == 0 || view.index.count == 0) continue;
|
||||
uf::Mesh::Attribute positionAttribute;
|
||||
uf::Mesh::Attribute uvAttribute;
|
||||
uf::Mesh::Attribute stAttribute;
|
||||
|
||||
for ( auto& attribute : mesh.vertex.attributes ) {
|
||||
if ( attribute.descriptor.name == "position" ) positionAttribute = attribute;
|
||||
else if ( attribute.descriptor.name == "uv" ) uvAttribute = attribute;
|
||||
else if ( attribute.descriptor.name == "st" ) stAttribute = attribute;
|
||||
}
|
||||
UF_ASSERT( positionAttribute.descriptor.name == "position" && uvAttribute.descriptor.name == "uv" && stAttribute.descriptor.name == "st" );
|
||||
|
||||
if ( mesh.index.count ) {
|
||||
uf::Mesh::Input indexInput = mesh.index;
|
||||
uf::Mesh::Attribute indexAttribute = mesh.index.attributes.front();
|
||||
|
||||
// Choose xatlas index format
|
||||
::xatlas::IndexFormat indexType = ::xatlas::IndexFormat::UInt32;
|
||||
switch (idx.attribute.descriptor.size) {
|
||||
switch ( mesh.index.size ) {
|
||||
case sizeof(uint16_t): indexType = ::xatlas::IndexFormat::UInt16; break;
|
||||
case sizeof(uint32_t): indexType = ::xatlas::IndexFormat::UInt32; break;
|
||||
default: UF_EXCEPTION("unsupported index type"); break;
|
||||
}
|
||||
|
||||
// Get atlas target from indirect auxID or default 0
|
||||
size_t atlasID = 0;
|
||||
if ( 0 <= view.indirectIndex ) {
|
||||
auto* drawCommand = (pod::DrawCommand*) mesh.getBuffer(mesh.indirect).data();
|
||||
atlasID = drawCommand[view.indirectIndex].auxID;
|
||||
if ( mesh.indirect.count ) {
|
||||
auto& primitives = /*graph.storage*/storage.primitives[name];
|
||||
pod::DrawCommand* drawCommands = (pod::DrawCommand*) mesh.getBuffer(mesh.indirect).data();
|
||||
|
||||
for ( auto i = 0; i < mesh.indirect.count; ++i ) {
|
||||
size_t atlasID = drawCommands[i].auxID;
|
||||
|
||||
vertexInput = mesh.remapVertexInput( i );
|
||||
indexInput = mesh.remapIndexInput( i );
|
||||
|
||||
auto& atlas = atlases[atlasID];
|
||||
auto& entry = atlas.entries.emplace_back();
|
||||
entry.index = index;
|
||||
entry.commandID = i;
|
||||
|
||||
auto& decl = entry.decl;
|
||||
|
||||
decl.vertexPositionData = static_cast<uint8_t*>(positionAttribute.pointer) + positionAttribute.stride * vertexInput.first;
|
||||
decl.vertexPositionStride = positionAttribute.stride;
|
||||
|
||||
decl.vertexUvData = static_cast<uint8_t*>(uvAttribute.pointer) + uvAttribute.stride * vertexInput.first;
|
||||
decl.vertexUvStride = uvAttribute.stride;
|
||||
|
||||
decl.vertexCount = vertexInput.count;
|
||||
|
||||
decl.indexCount = indexInput.count;
|
||||
decl.indexData = static_cast<uint8_t*>(indexAttribute.pointer) + indexAttribute.stride * indexInput.first;
|
||||
decl.indexFormat = indexType;
|
||||
}
|
||||
} else {
|
||||
size_t atlasID = 0;
|
||||
auto& atlas = atlases[atlasID];
|
||||
auto& entry = atlas.entries.emplace_back();
|
||||
entry.index = index;
|
||||
|
||||
auto& decl = entry.decl;
|
||||
decl.vertexPositionData = static_cast<uint8_t*>(positionAttribute.pointer) + positionAttribute.stride * vertexInput.first;
|
||||
decl.vertexPositionStride = positionAttribute.stride;
|
||||
|
||||
decl.vertexUvData = static_cast<uint8_t*>(uvAttribute.pointer) + uvAttribute.stride * vertexInput.first;
|
||||
decl.vertexUvStride = uvAttribute.stride;
|
||||
|
||||
decl.vertexCount = vertexInput.count;
|
||||
|
||||
decl.indexCount = indexInput.count;
|
||||
decl.indexData = static_cast<uint8_t*>(indexAttribute.pointer) + indexAttribute.stride * indexInput.first;
|
||||
decl.indexFormat = indexType;
|
||||
}
|
||||
|
||||
auto& atlas = atlases[atlasID];
|
||||
auto& entry = atlas.entries.emplace_back();
|
||||
entry.index = index;
|
||||
|
||||
auto& decl = entry.decl;
|
||||
decl.vertexPositionData = pos.data(view.vertex.first);
|
||||
decl.vertexPositionStride = pos.stride();
|
||||
|
||||
if ( uv.valid() ) {
|
||||
decl.vertexUvData = uv.data(view.vertex.first);
|
||||
decl.vertexUvStride = uv.stride();
|
||||
} else if ( st.valid() ) {
|
||||
decl.vertexUvData = st.data(view.vertex.first);
|
||||
decl.vertexUvStride = st.stride();
|
||||
}
|
||||
|
||||
decl.vertexCount = view.vertex.count;
|
||||
|
||||
decl.indexCount = view.index.count;
|
||||
decl.indexData = idx.data(view.index.first);
|
||||
decl.indexFormat = indexType;
|
||||
}
|
||||
} else UF_EXCEPTION("to-do: not require indices for meshes");
|
||||
}
|
||||
|
||||
::xatlas::ChartOptions chartOptions{};
|
||||
|
||||
@ -1,50 +1,54 @@
|
||||
// BVH
|
||||
namespace {
|
||||
// collects a list of nodes that are overlapping with each other
|
||||
void traverseNodePair( const pod::BVH& bvh, int leftID, int rightID, pod::BVH::pair_t& pairs ) {
|
||||
const auto& left = bvh.nodes[leftID];
|
||||
const auto& right = bvh.nodes[rightID];
|
||||
void traverseNodePair( const pod::BVH& bvh, int indexA, int indexB, pod::BVH::pair_t& pairs ) {
|
||||
const auto& nodeA = bvh.nodes[indexA];
|
||||
const auto& nodeB = bvh.nodes[indexB];
|
||||
|
||||
if ( !::aabbOverlap( left.bounds, right.bounds ) ) return;
|
||||
if ( !::aabbOverlap( nodeA.bounds, nodeB.bounds ) ) return;
|
||||
|
||||
if ( left.count > 0 && right.count > 0 ) {
|
||||
for ( auto i = 0; i < left.count; ++i )
|
||||
for ( auto j = 0; j < right.count; ++j ) {
|
||||
int a = bvh.indices[left.start + i];
|
||||
int b = bvh.indices[right.start + j];
|
||||
pairs.emplace_back(std::pair{a, b});
|
||||
if ( nodeA.count > 0 && nodeB.count > 0 ) {
|
||||
for ( auto i = 0; i < nodeA.count; ++i )
|
||||
for ( auto j = 0; j < nodeB.count; ++j ) {
|
||||
int indexA = bvh.indices[nodeA.start + i];
|
||||
int indexB = bvh.indices[nodeB.start + j];
|
||||
pairs.emplace_back(std::pair{indexA, indexB});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ( left.count == 0 ) {
|
||||
::traverseNodePair( bvh, left.left, rightID, pairs );
|
||||
::traverseNodePair( bvh, left.right, rightID, pairs );
|
||||
} else if ( right.count == 0 ) {
|
||||
::traverseNodePair( bvh, leftID, right.left, pairs );
|
||||
::traverseNodePair( bvh, leftID, right.right, pairs );
|
||||
if ( nodeA.count == 0 ) {
|
||||
::traverseNodePair( bvh, nodeA.left, indexB, pairs );
|
||||
::traverseNodePair( bvh, nodeA.right, indexB, pairs );
|
||||
} else if ( nodeB.count == 0 ) {
|
||||
::traverseNodePair( bvh, indexA, nodeB.left, pairs );
|
||||
::traverseNodePair( bvh, indexA, nodeB.right, pairs );
|
||||
}
|
||||
}
|
||||
// collects a list of nodes from each BVH that are overlapping with each other (for mesh v mesh)
|
||||
void traverseNodePair( const pod::BVH& a, int nodeA, const pod::BVH& b, int nodeB, pod::BVH::pair_t& pairs ) {
|
||||
const auto& nA = a.nodes[nodeA];
|
||||
const auto& nB = b.nodes[nodeB];
|
||||
void traverseNodePair( const pod::BVH& bvhA, int indexA, const pod::BVH& bvhB, int indexB, pod::BVH::pair_t& pairs ) {
|
||||
const auto& nodeA = bvhA.nodes[indexA];
|
||||
const auto& nodeB = bvhB.nodes[indexB];
|
||||
|
||||
if ( !::aabbOverlap( nA.bounds, nB.bounds ) ) return;
|
||||
if ( !::aabbOverlap( nodeA.bounds, nodeB.bounds ) ) return;
|
||||
|
||||
if ( nA.count > 0 && nB.count > 0 ) {
|
||||
for ( auto i = 0; i < nA.count; i++ )
|
||||
for ( auto j = 0; j < nB.count; j++ )
|
||||
pairs.emplace_back(a.indices[nA.start+i], b.indices[nB.start+j]);
|
||||
if ( nodeA.count > 0 && nodeB.count > 0 ) {
|
||||
for ( auto i = 0; i < nodeA.count; i++ ) {
|
||||
for ( auto j = 0; j < nodeB.count; j++ ) {
|
||||
auto indexA = bvhA.indices[nodeA.start+i];
|
||||
auto indexB = bvhB.indices[nodeB.start+j];
|
||||
pairs.emplace_back(std::pair{indexA, indexB});
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ( nA.count == 0 ) {
|
||||
::traverseNodePair( a, nA.left, b , nodeB, pairs );
|
||||
::traverseNodePair( a, nA.right, b , nodeB, pairs );
|
||||
} else {
|
||||
::traverseNodePair( a, nodeA, b, nB.left, pairs );
|
||||
::traverseNodePair( a, nodeA, b, nB.right, pairs );
|
||||
if ( nodeA.count == 0 ) {
|
||||
::traverseNodePair( bvhA, nodeA.left, bvhB , indexB, pairs );
|
||||
::traverseNodePair( bvhA, nodeA.right, bvhB , indexB, pairs );
|
||||
} else if ( nodeB.count == 0 ) {
|
||||
::traverseNodePair( bvhA, indexA, bvhB, nodeB.left, pairs );
|
||||
::traverseNodePair( bvhA, indexA, bvhB, nodeB.right, pairs );
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,9 +58,14 @@ namespace {
|
||||
if ( node.count > 0 ) {
|
||||
for ( auto i = 0; i < node.count; ++i ) {
|
||||
for ( auto j = i + 1; j < node.count; ++j ) {
|
||||
int a = bvh.indices[node.start + i];
|
||||
int b = bvh.indices[node.start + j];
|
||||
pairs.emplace_back(std::pair{a, b});
|
||||
int indexA = bvh.indices[node.start + i];
|
||||
int indexB = bvh.indices[node.start + j];
|
||||
|
||||
if ( !::aabbOverlap( bvh.nodes[indexA].bounds, bvh.nodes[indexB].bounds ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pairs.emplace_back(std::pair{indexA, indexB});
|
||||
}
|
||||
}
|
||||
return;
|
||||
|
||||
@ -74,6 +74,13 @@ namespace {
|
||||
return ::getTransform( body ).position;
|
||||
}
|
||||
|
||||
bool shouldCollide( const pod::Collider& a, const pod::Collider& b ) {
|
||||
return ( a.category & b.mask ) && ( b.category & a.mask );
|
||||
}
|
||||
bool shouldCollide( const pod::RigidBody& a, const pod::RigidBody& b ) {
|
||||
return ::shouldCollide( a.collider, b.collider );
|
||||
}
|
||||
|
||||
// normalizes the delta between two bodies / contacts by the distance (as it was already computed) if non-zero
|
||||
// a lot of collider v colliders use this semantic
|
||||
pod::Vector3f normalizeDelta( const pod::Vector3f& delta, float dist, float eps = EPS(1.0e-6), const pod::Vector3f& fallback = pod::Vector3f{0,1,0} ) {
|
||||
|
||||
@ -6,14 +6,18 @@
|
||||
|
||||
namespace {
|
||||
bool warmupSolver = true;
|
||||
bool blockContactSolver = false;
|
||||
bool psgContactSolver = true;
|
||||
bool useGjk = false;
|
||||
bool blockContactSolver = false; // blockNxN solver is flawed
|
||||
bool psgContactSolver = true; // iterative solver is flawed
|
||||
bool useGjk = false; // currently don't have a way to broadphase mesh => narrowphase tri via GJK
|
||||
bool fixedStep = true;
|
||||
int substeps = 0;
|
||||
int substeps = 4;
|
||||
|
||||
int solverIterations = 10;
|
||||
float baumgarteCorrectionPercent = 0.005f;
|
||||
// increasing these make things lag
|
||||
int broadphaseBvhCapacity = 1;
|
||||
int meshBvhCapacity = 1;
|
||||
|
||||
int solverIterations = 5;
|
||||
float baumgarteCorrectionPercent = 0.005f; // needs to be very small or the correction is too large
|
||||
float baumgarteCorrectionSlop = 0.001f;
|
||||
uf::stl::unordered_map<size_t, pod::Manifold> manifoldsCache;
|
||||
}
|
||||
@ -106,7 +110,7 @@ void uf::physics::impl::step( pod::World& world, float dt ) {
|
||||
uf::stl::vector<std::pair<int,int>> pairs;
|
||||
|
||||
// create BVH
|
||||
::buildBroadphaseBVH(bvh, bodies);
|
||||
::buildBroadphaseBVH( bvh, bodies, ::broadphaseBvhCapacity );
|
||||
// query for overlaps
|
||||
::queryOverlaps( bvh, pairs );
|
||||
// iterate overlaps
|
||||
@ -114,6 +118,9 @@ void uf::physics::impl::step( pod::World& world, float dt ) {
|
||||
auto& a = *bodies[ia];
|
||||
auto& b = *bodies[ib];
|
||||
|
||||
// could be also pruned in the broadphase, but traversal needs to be agnostic between a BVH for bodies or a BVH for triangles
|
||||
if ( !::shouldCollide( a, b ) ) continue;
|
||||
|
||||
pod::Manifold manifold;
|
||||
if ( ::generateContacts( a, b, manifold, dt ) ) {
|
||||
// bodies with meshes already reorient the normal to the triangle's center
|
||||
@ -169,9 +176,47 @@ void uf::physics::impl::step( pod::World& world, float dt ) {
|
||||
void uf::physics::impl::setMass( pod::RigidBody& body, float mass ) {
|
||||
body.mass = mass;
|
||||
body.inverseMass = 1.0f / mass;
|
||||
uf::physics::impl::setInertia( body );
|
||||
uf::physics::impl::updateInertia( body );
|
||||
}
|
||||
void uf::physics::impl::setInertia( pod::RigidBody& body ) {
|
||||
|
||||
void uf::physics::impl::setColliderCategory( pod::RigidBody& body, uint32_t category ) {
|
||||
body.collider.category = category;
|
||||
}
|
||||
void uf::physics::impl::setColliderCategory( pod::RigidBody& body, const uf::stl::string& category ) {
|
||||
auto c = uf::string::uppercase( category );
|
||||
if ( c == "NONE" ) return uf::physics::impl::setColliderCategory( body, pod::Collider::CATEGORY_NONE );
|
||||
if ( c == "STATIC" ) return uf::physics::impl::setColliderCategory( body, pod::Collider::CATEGORY_STATIC );
|
||||
if ( c == "DYNAMIC" ) return uf::physics::impl::setColliderCategory( body, pod::Collider::CATEGORY_DYNAMIC );
|
||||
if ( c == "PLAYER" ) return uf::physics::impl::setColliderCategory( body, pod::Collider::CATEGORY_PLAYER );
|
||||
if ( c == "NPC" ) return uf::physics::impl::setColliderCategory( body, pod::Collider::CATEGORY_NPC );
|
||||
if ( c == "TRIGGER" ) return uf::physics::impl::setColliderCategory( body, pod::Collider::CATEGORY_TRIGGER );
|
||||
if ( c == "PROJECTILE" ) return uf::physics::impl::setColliderCategory( body, pod::Collider::CATEGORY_PROJECTILE );
|
||||
if ( c == "CHARACTER" ) return uf::physics::impl::setColliderCategory( body, pod::Collider::CATEGORY_CHARACTER );
|
||||
if ( c == "ALL" ) return uf::physics::impl::setColliderCategory( body, pod::Collider::CATEGORY_ALL );
|
||||
}
|
||||
void uf::physics::impl::setColliderMask( pod::RigidBody& body, uint32_t mask ) {
|
||||
body.collider.mask = mask;
|
||||
}
|
||||
void uf::physics::impl::setColliderMask( pod::RigidBody& body, const uf::stl::string& mask ) {
|
||||
auto m = uf::string::uppercase( mask );
|
||||
if ( m == "NONE" ) return uf::physics::impl::setColliderMask( body, pod::Collider::MASK_NONE );
|
||||
if ( m == "STATIC" ) return uf::physics::impl::setColliderMask( body, pod::Collider::MASK_STATIC );
|
||||
if ( m == "DYNAMIC" ) return uf::physics::impl::setColliderMask( body, pod::Collider::MASK_DYNAMIC );
|
||||
if ( m == "PLAYER" ) return uf::physics::impl::setColliderMask( body, pod::Collider::MASK_PLAYER );
|
||||
if ( m == "NPC" ) return uf::physics::impl::setColliderMask( body, pod::Collider::MASK_NPC );
|
||||
if ( m == "TRIGGER" ) return uf::physics::impl::setColliderMask( body, pod::Collider::MASK_TRIGGER );
|
||||
if ( m == "PROJECTILE" ) return uf::physics::impl::setColliderMask( body, pod::Collider::MASK_PROJECTILE );
|
||||
if ( m == "CHARACTER" ) return uf::physics::impl::setColliderCategory( body, pod::Collider::MASK_CHARACTER );
|
||||
if ( m == "ALL" ) return uf::physics::impl::setColliderMask( body, pod::Collider::MASK_ALL );
|
||||
}
|
||||
void uf::physics::impl::setGravity( pod::RigidBody& body, const pod::Vector3f& gravity ) {
|
||||
body.gravity = gravity;
|
||||
}
|
||||
pod::Vector3f uf::physics::impl::getGravity( pod::RigidBody& body ) {
|
||||
return uf::vector::isValid( body.gravity ) ? body.gravity : body.world->gravity;
|
||||
}
|
||||
|
||||
void uf::physics::impl::updateInertia( pod::RigidBody& body ) {
|
||||
if ( body.isStatic || body.mass <= 0 ) {
|
||||
body.inverseInertiaTensor = {};
|
||||
return;
|
||||
@ -254,6 +299,11 @@ pod::RigidBody& uf::physics::impl::create( pod::World& world, uf::Object& object
|
||||
body.inverseMass = mass == 0.0f ? 0.0f : 1.0f / mass;
|
||||
body.isStatic = mass == 0.0f;
|
||||
|
||||
if ( body.isStatic ) {
|
||||
uf::physics::impl::setColliderCategory(body, "STATIC");
|
||||
uf::physics::impl::setColliderMask(body, "STATIC");
|
||||
}
|
||||
|
||||
// insert into world
|
||||
world.bodies.emplace_back(&body);
|
||||
|
||||
@ -264,7 +314,7 @@ pod::RigidBody& uf::physics::impl::create( pod::World& world, uf::Object& object
|
||||
body.collider.type = pod::ShapeType::AABB;
|
||||
body.collider.u.aabb = aabb;
|
||||
body.bounds = ::computeAABB( body );
|
||||
uf::physics::impl::setInertia( body );
|
||||
uf::physics::impl::updateInertia( body );
|
||||
UF_MSG_DEBUG("Creating body of type: {}, mass={}, min={}, max={}", "AABB", mass, uf::vector::toString(aabb.min), uf::vector::toString(aabb.max) );
|
||||
return body;
|
||||
}
|
||||
@ -273,7 +323,7 @@ pod::RigidBody& uf::physics::impl::create( pod::World& world, uf::Object& object
|
||||
body.collider.type = pod::ShapeType::SPHERE;
|
||||
body.collider.u.sphere = sphere;
|
||||
body.bounds = ::computeAABB( body );
|
||||
uf::physics::impl::setInertia( body );
|
||||
uf::physics::impl::updateInertia( body );
|
||||
UF_MSG_DEBUG("Creating body of type={}, mass={}, radius={}", "SPHERE", mass, sphere.radius );
|
||||
return body;
|
||||
}
|
||||
@ -282,7 +332,7 @@ pod::RigidBody& uf::physics::impl::create( pod::World& world, uf::Object& object
|
||||
body.collider.type = pod::ShapeType::PLANE;
|
||||
body.collider.u.plane = plane;
|
||||
body.bounds = ::computeAABB( body );
|
||||
uf::physics::impl::setInertia( body );
|
||||
uf::physics::impl::updateInertia( body );
|
||||
UF_MSG_DEBUG("Creating body of type={}, mass={}, normal={}, offset={}", "PLANE", mass, uf::vector::toString( plane.normal ), plane.offset );
|
||||
return body;
|
||||
}
|
||||
@ -291,7 +341,7 @@ pod::RigidBody& uf::physics::impl::create( pod::World& world, uf::Object& object
|
||||
body.collider.type = pod::ShapeType::CAPSULE;
|
||||
body.collider.u.capsule = capsule;
|
||||
body.bounds = ::computeAABB( body );
|
||||
uf::physics::impl::setInertia( body );
|
||||
uf::physics::impl::updateInertia( body );
|
||||
UF_MSG_DEBUG("Creating body of type={}, mass={}, radius={}, height={}", "CAPSULE", mass, capsule.radius, capsule.halfHeight * 2.0f );
|
||||
return body;
|
||||
}
|
||||
@ -300,7 +350,7 @@ pod::RigidBody& uf::physics::impl::create( pod::World& world, uf::Object& object
|
||||
body.collider.type = pod::ShapeType::TRIANGLE;
|
||||
body.collider.u.triangle = tri;
|
||||
body.bounds = ::computeAABB( body );
|
||||
uf::physics::impl::setInertia( body );
|
||||
uf::physics::impl::updateInertia( body );
|
||||
UF_MSG_DEBUG("Creating body of type={}, mass={}, point[0]={}, point[1]={}, point[2]={}", "TRIANGLE", mass, uf::vector::toString( tri.points[0] ), uf::vector::toString( tri.points[1] ), uf::vector::toString( tri.points[2] ) );
|
||||
return body;
|
||||
}
|
||||
@ -310,10 +360,10 @@ pod::RigidBody& uf::physics::impl::create( pod::World& world, uf::Object& object
|
||||
body.collider.u.mesh.mesh = &mesh;
|
||||
body.collider.u.mesh.bvh = new pod::BVH;
|
||||
|
||||
::buildMeshBVH( *body.collider.u.mesh.bvh, mesh );
|
||||
::buildMeshBVH( *body.collider.u.mesh.bvh, mesh, ::meshBvhCapacity );
|
||||
|
||||
body.bounds = ::computeAABB( body );
|
||||
uf::physics::impl::setInertia( body );
|
||||
uf::physics::impl::updateInertia( body );
|
||||
UF_MSG_DEBUG("Creating body of type={}, mass={}, nodes={} indices={} ", "MESH", mass, body.collider.u.mesh.bvh->nodes.size(), body.collider.u.mesh.bvh->indices.size());
|
||||
return body;
|
||||
}
|
||||
|
||||
@ -214,7 +214,7 @@ namespace {
|
||||
|
||||
// linear integration
|
||||
pod::Vector3f acceleration = body.forceAccumulator * body.inverseMass;
|
||||
acceleration += world.gravity; // apply gravity
|
||||
acceleration += uf::physics::impl::getGravity( body ); // apply gravity
|
||||
body.velocity += acceleration * dt;
|
||||
body.transform/*.reference*/->position += body.velocity * dt;
|
||||
|
||||
|
||||
@ -123,16 +123,12 @@ namespace {
|
||||
pod::BVH::pair_t pairs;
|
||||
::traverseNodePair(bvhA, 0, bvhB, 0, pairs);
|
||||
|
||||
UF_MSG_DEBUG("candidates={}", pairs.size());
|
||||
|
||||
bool hit = false;
|
||||
// do collision per triangle
|
||||
for (auto [ idA, idB] : pairs ) {
|
||||
auto tA = ::fetchTriangle( meshA, idA, a ); // transform triangles to world space
|
||||
auto tB = ::fetchTriangle( meshB, idB, b );
|
||||
|
||||
UF_MSG_DEBUG("triA={}, triB={}", idA, idB );
|
||||
|
||||
if ( !::triangleTriangle( tA, tB, manifold, eps ) ) continue;
|
||||
hit = true;
|
||||
}
|
||||
|
||||
@ -87,8 +87,6 @@ namespace {
|
||||
|
||||
float disc = b*b - 4*a*c;
|
||||
|
||||
UF_MSG_DEBUG( "center={}, r={}, oc={}, a, b, c, disc", uf::vector::toString( center ), r, uf::vector::toString( oc ), a, b, c, disc );
|
||||
|
||||
if ( disc < 0 ) return false;
|
||||
|
||||
float sqrtDisc = std::sqrt(disc);
|
||||
@ -97,8 +95,6 @@ namespace {
|
||||
|
||||
float t = ( t0 >= 0 ) ? t0 : t1;
|
||||
|
||||
UF_MSG_DEBUG( "sqrtDisc={}, t0={}, t1={}, t={}, rayHit.contact.penetration={}", sqrtDisc, t0, t1, t, rayHit.contact.penetration );
|
||||
|
||||
if ( t < 0 ) return false; // both behind ray
|
||||
|
||||
// compare against current best hit
|
||||
|
||||
Loading…
Reference in New Issue
Block a user