Cogs.Core
RayPick.cpp
1#include "RayPick.h"
2
3#include "Context.h"
4
5#include "Components/Core/CameraComponent.h"
6#include "Components/Core/TransformComponent.h"
7
8#include "Systems/Core/CameraSystem.h"
9#include "Systems/Core/ClipShapeSystem.h"
10
11#include "Services/Variables.h"
12
13#include "Platform/Instrumentation.h"
14
15#include "Resources/Mesh.h"
16#include "Resources/Buffer.h"
17
18#include "Math/RayIntersection.h"
19
20#include "Foundation/ComponentModel/Entity.h"
21#include "Foundation/Logging/Logger.h"
22
23
24using namespace Cogs::Geometry;
25
26namespace
27{
28 using namespace Cogs;
29 using namespace Cogs::Core;
30
32
35 EntityId getRootEntityId(const ComponentModel::Component* component)
36 {
37 const Entity* container = component->getContainer();
38
39 if (container) {
40 const TransformComponent* transformComponent = container->getComponent<TransformComponent>();
41
42 if (transformComponent) {
43 if (const Component* parentTransform = transformComponent->parent.resolve(); parentTransform) {
44 return getRootEntityId(parentTransform);
45 }
46 }
47 return container->getId();
48 }
49
50 return NoEntity;
51 }
52
53 void filterHits(std::vector<RayPicking::RayPickHit>& hits, float rayLength, const glm::vec3& rayStartPos, PickingFlags pickingFlags)
54 {
55 const float rayLengthSq = rayLength * rayLength;
56 for (auto it = hits.begin(); it != hits.end();) {
57 bool erase = false;
58
59 // Erase hits further away than rayLength.
60 if (glm::distance2(it->position, rayStartPos) > rayLengthSq) {
61 erase = true;
62 }
63 // Erase duplicate hits, where duplicates means hitting the same entity.
64 else if ((pickingFlags & PickingFlags::RemoveDuplicateEntities) == PickingFlags::RemoveDuplicateEntities) {
65 erase = std::find_if(std::next(it), hits.end(),
66 [it](const RayPicking::RayPickHit& otherHit) {
67 return it->entity == otherHit.entity;
68 }) != hits.end();
69 }
70 // Erase duplicate hits, where duplicates means hitting the same point on the same entity.
71 else if ((pickingFlags & PickingFlags::RemoveDuplicates) == PickingFlags::RemoveDuplicates) {
72 erase = std::find_if(std::next(it), hits.end(),
73 [it](const RayPicking::RayPickHit& otherHit) {
74 return it->entity == otherHit.entity && it->textureCoords == otherHit.textureCoords;
75 }) != hits.end();
76 }
77
78 if (erase) {
79 std::swap(*it, hits.back());
80 hits.pop_back();
81 }
82 else {
83 ++it;
84 }
85 }
86 }
87}
88
90 const CameraComponent& camera,
91 const glm::vec2& cameraPosition,
92 float rayLength,
93 float radius,
94 PickingFlags pickingFlags,
95 PicksReturned returnFlag,
96 const RayPickFilter& filter,
97 std::vector<RayPicking::RayPickHit>& hits)
98{
99 CpuInstrumentationScope(SCOPE_RAYPICK, "pickCamera");
100
101 if ((camera.flags & CameraFlags::EnablePicking) == 0) {
102 LOG_WARNING(logger, "Camera picking is not enabled.");
103 return NoEntity;
104 }
105 const CameraData& cameraData = context->cameraSystem->getData(&camera);
106
107 glm::vec2 normPos = 2.f * (cameraPosition + 0.5f) / cameraData.viewportSize - 1.f;
108 // We don't want each system to sort the hits, we do it here when we gather them all.
109 PicksReturned retFlag = returnFlag == PicksReturned::AllSorted ? PicksReturned::AllUnsorted : returnFlag;
110
111 bool hitSomething = false;
112 for (IRayPickable* pickable : pickables) {
113 hitSomething |= pickable->pickCamera(context, camera, normPos, rayLength, radius, pickingFlags, retFlag, filter, hits);
114 }
115
116 // Filter results based on various flags.
117 // TODO: would be best that these hits were not in the results in the first place, should be each system's job to make sure of that.
118 const glm::vec3 cameraPos = glm::vec3(cameraData.inverseViewMatrix[3]);
119 filterHits(hits, rayLength, cameraPos, pickingFlags);
120
121 if (returnFlag == PicksReturned::AllSorted) {
122 std::sort(hits.begin(), hits.end());
123 }
124
125 // Use a bool insted of returning !hits.emtpy() just in case the function is called with a non empty hits vector.
126 return hitSomething;
127}
128
130 const glm::vec3 & position,
131 const glm::quat & rotation,
132 float rayLength,
133 float radius,
134 PickingFlags pickingFlags,
135 PicksReturned returnFlag,
136 const RayPickFilter& filter,
137 std::vector<RayPicking::RayPickHit>& hits)
138{
139 CpuInstrumentationScope(SCOPE_RAYPICK, "pickRay");
140
141 // We don't want each system to sort the hits, we do it here when we gather them all.
142 PicksReturned retFlag = returnFlag == PicksReturned::AllSorted ? PicksReturned::AllUnsorted : returnFlag;
143
144 bool hitSomething = false;
145 for (IRayPickable* pickable : pickables) {
146 pickable->pickRay(context, position, rotation, rayLength, radius, pickingFlags, retFlag, filter, hits);
147 }
148 // Filter results based on various flags.
149 // TODO: would be best that these hits were not in the results in the first place, should be each system's job to make sure of that.
150 filterHits(hits, rayLength, position, pickingFlags);
151
152 if (returnFlag == PicksReturned::AllSorted) {
153 std::sort(hits.begin(), hits.end());
154 }
155
156 // Use a bool instead of returning !hits.emtpy() just in case the function is called with a non empty hits vector.
157 return hitSomething;
158}
159
161{
162 pickables.push_back(pickable);
163 std::sort(pickables.begin(), pickables.end(), [](const IRayPickable* a, const IRayPickable* b) { return a->order < b->order; });
164}
165
167{
168 pickables.erase(std::remove(pickables.begin(), pickables.end(), pickable), pickables.end());
169}
170
171
172Cogs::Core::RayPicking::RayPickHit::RayPickHit(const ComponentModel::Component& comp, bool returnChildEntity, const glm::vec3& pos, float dist, const glm::vec2& texCoords)
173 : position(pos)
174 , textureCoords(texCoords)
175 , distance(dist)
176{
177 if (returnChildEntity) {
178 entity = comp.getContainer()->getId();
179 root = getRootEntityId(&comp);
180 }
181 else {
182 entity = getRootEntityId(&comp);
183 }
184}
185
186// ----
187
189{
190 return entityTypeHash != RayPicking::NoPickEntityType &&
191 static_cast<const EntityData *>(comp.getContainer()->getUserData())->templateId != entityTypeHash;
192}
193
194
196 const CameraComponent& camera,
197 const glm::vec2& normPosition,
198 float /*rayLength*/,
199 float radius,
200 PickingFlags pickingFlags,
201 PicksReturned returnFlag,
202 const RayPicking::RayPickFilter& filter,
203 std::vector<RayPicking::RayPickHit>& hits)
204{
205 const CameraData& cameraData = context->cameraSystem->getData(&camera);
206
207 const glm::mat4 rawViewProjection = cameraData.rawViewProjection;
208
209 const float sx = cameraData.viewportSize.x / radius; // scale up so that regionSize wrt viewportSize maps to [-1,1]
210 const float sy = cameraData.viewportSize.y / radius;
211 const glm::mat4 worldPickMatrix = glm::mat4(sx, 0.f, 0.f, 0.f,
212 0.f, sy, 0.f, 0.f,
213 0.f, 0.f, 1.0f, 0.f,
214 -normPosition.x * sx, -normPosition.y * sy, 0.f, 1.f) * rawViewProjection;
215
216 // Use the camera position as origin of the ray (maybe should be the near plane but seems accurate enough)
217 const glm::mat4 viewMatrix = cameraData.viewMatrix;
218 return pickImpl(context, worldPickMatrix, rawViewProjection, viewMatrix, filter, pickingFlags, returnFlag, hits);
219}
220
221
223 const glm::vec3& startPos,
224 const glm::quat& rot,
225 float rayLength,
226 float radius,
227 PickingFlags pickingFlags,
228 PicksReturned returnFlag,
229 const RayPicking::RayPickFilter& filter,
230 std::vector<RayPicking::RayPickHit>& hits)
231{
232 const float right = radius;
233 // const float left = -radius;
234 const float top = radius;
235 // const float bottom = -radius;
236 // const float fNear = 0;
237 const float fFar = rayLength;
238 // already simplified some operations (2/right - left) == (1/right) given that right == -left
239 // term -(f+n)/(f-n); n == 0 => -f/f = -1.
240 const glm::mat4 projectionMatrix(
241 1 / right, 0, 0, 0,
242 0, 1 / top, 0, 0,
243 0, 0, -2 / fFar, 0,
244 0, 0, -1, 1
245 );
246
247 const glm::mat4 worldViewMatrix = glm::toMat4(glm::conjugate(rot)) * glm::translate(glm::mat4(1.f), -startPos);
248 const glm::mat4 worldPickMatrix = projectionMatrix * worldViewMatrix;
249
250 return pickImpl(context, worldPickMatrix, worldPickMatrix, worldViewMatrix, filter, pickingFlags, returnFlag, hits);
251}
252
253
255 Cogs::Core::MeshRayPickableBase::getTextureCoordinateInfo(const Cogs::Core::Mesh& mesh)
256{
257 TextureCoordinateInfo info{};
258 for (size_t vertTypeI = 0; vertTypeI < VertexDataType::LastVertexType; ++vertTypeI) {
259 VertexDataType::EVertexDataType vertexType = static_cast<VertexDataType::EVertexDataType>(vertTypeI);
260 if (mesh.hasStream(vertexType)) {
261 const DataStream& stream = mesh.streams[mesh.streamIndexes[vertexType]];
262 const Cogs::VertexFormat* vertexFormat = Cogs::VertexFormats::getVertexFormat(stream.format);
263 for (const Cogs::VertexElement& element : vertexFormat->elements) {
264 if (element.semantic == Cogs::ElementSemantic::TextureCoordinate && element.semanticIndex == 0) {
265 info.ptr = static_cast<const uint8_t*>(stream.buffer->data()) + stream.offset + element.offset;
266 info.format = element.format;
267 info.stride = stream.stride;
268 info.count = stream.numElements;
269 }
271
272 // In some cases, we can store the ids in a smaller type by subtracting a fixed offset. This fixed offset
273 // is then stored as a per-instance texture coordinate stream that has one element per mesh. Then the
274 // actual id is the sum of the per-vertex id and the per-mesh id.
275
276 const uint8_t* ptr = static_cast<const uint8_t*>(stream.buffer->data()) + stream.offset + element.offset;
277 switch (element.format) {
278 case Cogs::Format::R16_UINT:
279 info.idOffset = *reinterpret_cast<const uint16_t*>(ptr);
280 info.hasIdOffset = true;
281 break;
282 case Cogs::Format::R32_UINT:
283 info.idOffset = *reinterpret_cast<const uint32_t*>(ptr);
284 info.hasIdOffset = true;
285 break;
286 case Cogs::Format::R32_FLOAT:
287 info.idOffset = static_cast<uint32_t>(std::round(*reinterpret_cast<const float*>(ptr)));
288 info.hasIdOffset = true;
289 break;
290 default:
291 break;
292 }
293 }
294 }
295 }
296 }
297 return info;
298}
299
300bool Cogs::Core::MeshRayPickableBase::clippedByClipComp(const ClipShapeData& clipData, const glm::vec4& position)
301{
302 switch (clipData.shape) {
304 if (glm::min(glm::min(glm::min(dot(clipData.planes[0], position), dot(clipData.planes[1], position)),
305 glm::min(dot(clipData.planes[2], position), dot(clipData.planes[3], position))),
306 glm::min(dot(clipData.planes[4], position), dot(clipData.planes[5], position))) < 0.f) {
307 return true;
308 }
309 break;
310
312 if (0.f <= glm::min(glm::min(glm::min(dot(clipData.planes[0], position), dot(clipData.planes[1], position)),
313 glm::min(dot(clipData.planes[2], position), dot(clipData.planes[3], position))),
314 glm::min(dot(clipData.planes[4], position), dot(clipData.planes[5], position)))) {
315 return true;
316 }
317 break;
318
319 default:
320 break;
321 }
322
323 return false;
324}
325
326glm::vec2 Cogs::Core::MeshRayPickableBase::getTextureCoords(const TextureCoordinateInfo& textureInfo, const RayIntersectionHit& hit, const PickingFlags pickingFlags)
327{
328 bool flatInterpolation = (pickingFlags & PickingFlags::FlatTexcoordInterpolation) == PickingFlags::FlatTexcoordInterpolation;
329 bool addInstanceTexcoords = (pickingFlags & PickingFlags::AddInstanceTexcoords) == PickingFlags::AddInstanceTexcoords;
330
331 glm::vec2 t0, t1, t2;
332 switch (textureInfo.format) {
333 case Cogs::DataFormat::R32_FLOAT:
334 t0 = glm::vec2(*reinterpret_cast<const float*>(textureInfo.ptr + textureInfo.stride * hit.index.x));
335 if (!flatInterpolation) {
336 t1 = glm::vec2(*reinterpret_cast<const float*>(textureInfo.ptr + textureInfo.stride * hit.index.y));
337 t2 = glm::vec2(*reinterpret_cast<const float*>(textureInfo.ptr + textureInfo.stride * hit.index.z));
338 }
339 break;
340 case Cogs::DataFormat::R32G32_FLOAT:
341 t0 = *reinterpret_cast<const glm::vec2*>(textureInfo.ptr + textureInfo.stride * hit.index.x);
342 if (!flatInterpolation) {
343 t1 = *reinterpret_cast<const glm::vec2*>(textureInfo.ptr + textureInfo.stride * hit.index.y);
344 t2 = *reinterpret_cast<const glm::vec2*>(textureInfo.ptr + textureInfo.stride * hit.index.z);
345 }
346 break;
347 case Cogs::DataFormat::R8_UINT:
348 t0 = glm::vec2(*reinterpret_cast<const uint8_t*>(textureInfo.ptr + textureInfo.stride * hit.index.x));
349 if (!flatInterpolation) {
350 t1 = glm::vec2(*reinterpret_cast<const uint8_t*>(textureInfo.ptr + textureInfo.stride * hit.index.y));
351 t2 = glm::vec2(*reinterpret_cast<const uint8_t*>(textureInfo.ptr + textureInfo.stride * hit.index.z));
352 }
353 break;
354 case Cogs::DataFormat::R16_UINT:
355 t0 = glm::vec2(*reinterpret_cast<const uint16_t*>(textureInfo.ptr + textureInfo.stride * hit.index.x));
356 if (!flatInterpolation) {
357 t1 = glm::vec2(*reinterpret_cast<const uint16_t*>(textureInfo.ptr + textureInfo.stride * hit.index.y));
358 t2 = glm::vec2(*reinterpret_cast<const uint16_t*>(textureInfo.ptr + textureInfo.stride * hit.index.z));
359 }
360 break;
361 case Cogs::DataFormat::R32_UINT:
362 t0 = glm::vec2(static_cast<float>(*reinterpret_cast<const uint32_t*>(textureInfo.ptr + textureInfo.stride * hit.index.x)));
363 if (!flatInterpolation) {
364 t1 = glm::vec2(static_cast<float>(*reinterpret_cast<const uint32_t*>(textureInfo.ptr + textureInfo.stride * hit.index.y)));
365 t2 = glm::vec2(static_cast<float>(*reinterpret_cast<const uint32_t*>(textureInfo.ptr + textureInfo.stride * hit.index.z)));
366 }
367 break;
368 case Cogs::DataFormat::Unknown: // No texcoords
369 if (addInstanceTexcoords && textureInfo.hasIdOffset) {
370 // If we lack per-vertex ids, but per-mesh id has been requested and is available, return that.
371 return glm::vec2(static_cast<float>(textureInfo.idOffset));
372 }
374 default:
375 LOG_WARNING_ONCE(logger, "Unhandled texcoord format 0x%x", static_cast<uint32_t>(textureInfo.format));
377 break;
378 }
379
380 glm::vec2 rv;
381 if (flatInterpolation) {
382 rv = t0;
383 }
384 else {
385 rv = hit.bary.x * t0 + hit.bary.y * t1 + hit.bary.z * t2;
386 }
387
388 if (addInstanceTexcoords && textureInfo.hasIdOffset) {
389 rv.x = float(uint32_t(rv.x) + textureInfo.idOffset);
390 }
391 return rv;
392}
Base class for Component instances.
Definition: Component.h:143
class Entity * getContainer() const
Get the container currently owning this component instance.
Definition: Component.h:151
Container for components, providing composition of dynamic entities.
Definition: Entity.h:18
T * getComponent() const
Get a pointer to the first component implementing the given type in the entity.
Definition: Entity.h:35
constexpr size_t getId() const noexcept
Get the unique identifier of this entity.
Definition: Entity.h:113
constexpr void * getUserData() const noexcept
Get user data.
Definition: Entity.h:137
CameraFlags flags
Camera behavior flags.
A Context instance contains all the services, systems and runtime components needed to use Cogs.
Definition: Context.h:83
Interface for modules implementing custom picking.
Definition: RayPick.h:140
bool pickCamera(Context *context, const CameraComponent &camera, const glm::vec2 &normPosition, float, float radius, PickingFlags pickingFlags, PicksReturned returnFlag, const RayPicking::RayPickFilter &filter, std::vector< RayPicking::RayPickHit > &hits) override
Do a ray pick from a normalized screen space position in the camera direction and return all hits.
Definition: RayPick.cpp:195
bool pickRay(Context *context, const glm::vec3 &startPos, const glm::quat &rot, float rayLength, float radius, PickingFlags pickingFlags, PicksReturned returnFlag, const RayPicking::RayPickFilter &filter, std::vector< RayPicking::RayPickHit > &hits) override
Do a ray pick from a position and orientation in world space and return all hits.
Definition: RayPick.cpp:222
COGSCORE_DLL_API bool pickRay(Context *context, const glm::vec3 &position, const glm::quat &rotation, float rayLength, float radius, PickingFlags pickingFlags, PicksReturned returnFlag, const RayPicking::RayPickFilter &filter, std::vector< RayPicking::RayPickHit > &hits)
RayPicking using defined ray starting from pos.
Definition: RayPick.cpp:129
COGSCORE_DLL_API void addPickable(IRayPickable *pickable)
Add Component / System for custom picking.
Definition: RayPick.cpp:160
COGSCORE_DLL_API bool pickCamera(Context *context, const CameraComponent &camera, const glm::vec2 &cameraPosition, float rayLength, float radius, PickingFlags pickingFlags, PicksReturned returnFlag, const RayPicking::RayPickFilter &filter, std::vector< RayPicking::RayPickHit > &hits)
RayPicking using ray from camera position along camera viewing direction.
Definition: RayPick.cpp:89
COGSCORE_DLL_API void removePickable(IRayPickable *pickable)
Remove Component / System from custom picking.
Definition: RayPick.cpp:166
std::vector< IRayPickable * > pickables
Storage for registered pick extensions (addPickable)
Definition: RayPick.h:135
static constexpr glm::vec2 NoPickTextureCoords
Marks no match in texture.
Definition: RayPick.h:32
Defines a 4x4 transformation matrix for the entity and a global offset for root entities.
ComponentModel::ComponentHandle parent
Parent transform of the component.
Log implementation class.
Definition: LogManager.h:140
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
@ InvertedCube
Clip the inside of a cube.
@ Cube
Clip the outside of a cube,.
PicksReturned
  * Options for returning picking hits.
Definition: PickingFlags.h:40
@ AllUnsorted
Return all hits in no specific order.
@ AllSorted
Return all hits sorted based on distance from the ray start, closest first.
@ EnablePicking
Supports picking.
PickingFlags
Options for COGS picking.
Definition: PickingFlags.h:12
@ FlatTexcoordInterpolation
Do not interpolate texture coordinates. Needed if integer IDs are encoded as texture coordinates.
@ AddInstanceTexcoords
If mesh has an offset to IDs encoded in a per-instance texcoords stream, add this to the result.
Contains geometry calculations and generation.
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:181
Contains all Cogs related functionality.
Definition: FieldSetter.h:23
@ InstanceData
Per instance data.
@ TextureCoordinate
Texture coordinate semantic.
COGSFOUNDATION_API class Component * resolve() const
Resolve the handle, returning a pointer to the held Component instance.
Definition: Component.cpp:65
void * data()
Get a pointer to the buffer data.
Definition: Buffer.h:74
Contains data describing a Camera instance and its derived data structured such as matrix data and vi...
Definition: CameraSystem.h:67
glm::vec4 planes[6]
Clipping planes in world space.
Contains a stream of data used by Mesh resources.
Definition: Mesh.h:80
uint32_t numElements
Number of elements of the type given by format contained in data.
Definition: Mesh.h:108
uint32_t offset
Byte offset from the start of the buffer.
Definition: Mesh.h:102
VertexFormatHandle format
A pointer to the format describing the contents of the byte buffer.
Definition: Mesh.h:99
uint32_t stride
Element stride.
Definition: Mesh.h:105
Cogs::Core::ResourceBufferHandle buffer
Data buffer.
Definition: Mesh.h:96
Holds extra housekeeping data for an Entity instance.
Definition: EntityStore.h:24
Meshes contain streams of vertex data in addition to index data and options defining geometry used fo...
Definition: Mesh.h:265
bool hasStream(VertexDataType::EVertexDataType type) const
Check if the Mesh has a DataStream for the given type.
Definition: Mesh.h:933
bool isUnwantedType(const ComponentModel::Component &comp) const
Helper function used to determine if a given component belongs to an accepted entity type.
Definition: RayPick.cpp:188
EntityId entity
The EntityId picked.
Definition: RayPick.h:38
EVertexDataType
Contains data types.
Definition: Mesh.h:26
Vertex element structure used to describe a single data element in a vertex for the input assembler.
Definition: VertexFormat.h:38
InputType inputType
Input type of the element, vertex or instance data.
Definition: VertexFormat.h:43
DataFormat format
Format of the element.
Definition: VertexFormat.h:40
uint16_t offset
Offset in bytes from the vertex position in memory.
Definition: VertexFormat.h:39
uint16_t semanticIndex
Index for the semantic mapping.
Definition: VertexFormat.h:42
ElementSemantic semantic
Semantic mapping of the element (position, normal, etc...).
Definition: VertexFormat.h:41
Vertex format structure used to describe a single vertex for the input assembler.
Definition: VertexFormat.h:60
std::vector< VertexElement > elements
Vector containing all vertex elements of this format.
Definition: VertexFormat.h:62