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
171Cogs::Core::RayPicking::RayPickFilter::RayPickFilter(std::span<const RenderLayers> ordering_, RenderLayers layerMask, size_t entityTypeHash)
172: layerMask(layerMask),
173 entityTypeHash(entityTypeHash)
174{
175 size_t m = std::min(std::size(ordering), ordering_.size());
176 for (size_t i = 0; i < m; i++) {
177 ordering[i] = ordering_[i];
178 }
179 for (size_t i = m; i < std::size(ordering); i++) {
180 ordering[i] = RenderLayers::All;
181 }
182}
183
184
185Cogs::Core::RayPicking::RayPickHit::RayPickHit(const RenderComponent& comp, bool returnChildEntity, const glm::vec3& pos, Ordinal ordinal, float dist, const glm::vec2& texCoords)
186 : position(pos)
187 , textureCoords(texCoords)
188 , distance(dist)
189 , ordinal(ordinal)
190{
191 if (returnChildEntity) {
192 entity = comp.getContainer()->getId();
193 root = getRootEntityId(&comp);
194 }
195 else {
196 entity = getRootEntityId(&comp);
197 }
198}
199
200// ----
201
203{
204 return entityTypeHash != RayPicking::NoPickEntityType &&
205 static_cast<const EntityData *>(comp.getContainer()->getUserData())->templateId != entityTypeHash;
206}
207
208
210 const CameraComponent& camera,
211 const glm::vec2& normPosition,
212 float /*rayLength*/,
213 float radius,
214 PickingFlags pickingFlags,
215 PicksReturned returnFlag,
216 const RayPicking::RayPickFilter& filter,
217 std::vector<RayPicking::RayPickHit>& hits)
218{
219 const CameraData& cameraData = context->cameraSystem->getData(&camera);
220
221 const glm::mat4 rawViewProjection = cameraData.rawViewProjection;
222
223 const float sx = cameraData.viewportSize.x / radius; // scale up so that regionSize wrt viewportSize maps to [-1,1]
224 const float sy = cameraData.viewportSize.y / radius;
225 const glm::mat4 worldPickMatrix = glm::mat4(sx, 0.f, 0.f, 0.f,
226 0.f, sy, 0.f, 0.f,
227 0.f, 0.f, 1.0f, 0.f,
228 -normPosition.x * sx, -normPosition.y * sy, 0.f, 1.f) * rawViewProjection;
229
230 // Use the camera position as origin of the ray (maybe should be the near plane but seems accurate enough)
231 const glm::mat4 viewMatrix = cameraData.viewMatrix;
232 return pickImpl(context, worldPickMatrix, rawViewProjection, viewMatrix, filter, pickingFlags, returnFlag, hits);
233}
234
235
237 const glm::vec3& startPos,
238 const glm::quat& rot,
239 float rayLength,
240 float radius,
241 PickingFlags pickingFlags,
242 PicksReturned returnFlag,
243 const RayPicking::RayPickFilter& filter,
244 std::vector<RayPicking::RayPickHit>& hits)
245{
246 const float right = radius;
247 // const float left = -radius;
248 const float top = radius;
249 // const float bottom = -radius;
250 // const float fNear = 0;
251 const float fFar = rayLength;
252 // already simplified some operations (2/right - left) == (1/right) given that right == -left
253 // term -(f+n)/(f-n); n == 0 => -f/f = -1.
254 const glm::mat4 projectionMatrix(
255 1 / right, 0, 0, 0,
256 0, 1 / top, 0, 0,
257 0, 0, -2 / fFar, 0,
258 0, 0, -1, 1
259 );
260
261 const glm::mat4 worldViewMatrix = glm::toMat4(glm::conjugate(rot)) * glm::translate(glm::mat4(1.f), -startPos);
262 const glm::mat4 worldPickMatrix = projectionMatrix * worldViewMatrix;
263
264 return pickImpl(context, worldPickMatrix, worldPickMatrix, worldViewMatrix, filter, pickingFlags, returnFlag, hits);
265}
266
267
269 Cogs::Core::MeshRayPickableBase::getTextureCoordinateInfo(const Cogs::Core::Mesh& mesh)
270{
271 TextureCoordinateInfo info{};
272 for (size_t vertTypeI = 0; vertTypeI < VertexDataType::LastVertexType; ++vertTypeI) {
273 VertexDataType::EVertexDataType vertexType = static_cast<VertexDataType::EVertexDataType>(vertTypeI);
274 if (mesh.hasStream(vertexType)) {
275 const DataStream& stream = mesh.streams[mesh.streamIndexes[vertexType]];
276 const Cogs::VertexFormat* vertexFormat = Cogs::VertexFormats::getVertexFormat(stream.format);
277 for (const Cogs::VertexElement& element : vertexFormat->elements) {
278 if (element.semantic == Cogs::ElementSemantic::TextureCoordinate && element.semanticIndex == 0) {
279 info.ptr = static_cast<const uint8_t*>(stream.buffer->data()) + stream.offset + element.offset;
280 info.format = element.format;
281 info.stride = stream.stride;
282 info.count = stream.numElements;
283 }
285
286 // In some cases, we can store the ids in a smaller type by subtracting a fixed offset. This fixed offset
287 // is then stored as a per-instance texture coordinate stream that has one element per mesh. Then the
288 // actual id is the sum of the per-vertex id and the per-mesh id.
289
290 const uint8_t* ptr = static_cast<const uint8_t*>(stream.buffer->data()) + stream.offset + element.offset;
291 switch (element.format) {
292 case Cogs::Format::R16_UINT:
293 info.idOffset = *reinterpret_cast<const uint16_t*>(ptr);
294 info.hasIdOffset = true;
295 break;
296 case Cogs::Format::R32_UINT:
297 info.idOffset = *reinterpret_cast<const uint32_t*>(ptr);
298 info.hasIdOffset = true;
299 break;
300 case Cogs::Format::R32_FLOAT:
301 info.idOffset = static_cast<uint32_t>(std::round(*reinterpret_cast<const float*>(ptr)));
302 info.hasIdOffset = true;
303 break;
304 default:
305 break;
306 }
307 }
308 }
309 }
310 }
311 return info;
312}
313
314bool Cogs::Core::MeshRayPickableBase::clippedByClipComp(const ClipShapeData& clipData, const glm::vec4& position)
315{
316 switch (clipData.shape) {
318 if (glm::min(glm::min(glm::min(dot(clipData.planes[0], position), dot(clipData.planes[1], position)),
319 glm::min(dot(clipData.planes[2], position), dot(clipData.planes[3], position))),
320 glm::min(dot(clipData.planes[4], position), dot(clipData.planes[5], position))) < 0.f) {
321 return true;
322 }
323 break;
324
326 if (0.f <= glm::min(glm::min(glm::min(dot(clipData.planes[0], position), dot(clipData.planes[1], position)),
327 glm::min(dot(clipData.planes[2], position), dot(clipData.planes[3], position))),
328 glm::min(dot(clipData.planes[4], position), dot(clipData.planes[5], position)))) {
329 return true;
330 }
331 break;
332
333 default:
334 break;
335 }
336
337 return false;
338}
339
340glm::vec2 Cogs::Core::MeshRayPickableBase::getTextureCoords(const TextureCoordinateInfo& textureInfo, const RayIntersectionHit& hit, const PickingFlags pickingFlags)
341{
342 bool flatInterpolation = (pickingFlags & PickingFlags::FlatTexcoordInterpolation) == PickingFlags::FlatTexcoordInterpolation;
343 bool addInstanceTexcoords = (pickingFlags & PickingFlags::AddInstanceTexcoords) == PickingFlags::AddInstanceTexcoords;
344
345 glm::vec2 t0, t1, t2;
346 switch (textureInfo.format) {
347 case Cogs::DataFormat::R32_FLOAT:
348 t0 = glm::vec2(*reinterpret_cast<const float*>(textureInfo.ptr + textureInfo.stride * hit.index.x));
349 if (!flatInterpolation) {
350 t1 = glm::vec2(*reinterpret_cast<const float*>(textureInfo.ptr + textureInfo.stride * hit.index.y));
351 t2 = glm::vec2(*reinterpret_cast<const float*>(textureInfo.ptr + textureInfo.stride * hit.index.z));
352 }
353 break;
354 case Cogs::DataFormat::R32G32_FLOAT:
355 t0 = *reinterpret_cast<const glm::vec2*>(textureInfo.ptr + textureInfo.stride * hit.index.x);
356 if (!flatInterpolation) {
357 t1 = *reinterpret_cast<const glm::vec2*>(textureInfo.ptr + textureInfo.stride * hit.index.y);
358 t2 = *reinterpret_cast<const glm::vec2*>(textureInfo.ptr + textureInfo.stride * hit.index.z);
359 }
360 break;
361 case Cogs::DataFormat::R8_UINT:
362 t0 = glm::vec2(*reinterpret_cast<const uint8_t*>(textureInfo.ptr + textureInfo.stride * hit.index.x));
363 if (!flatInterpolation) {
364 t1 = glm::vec2(*reinterpret_cast<const uint8_t*>(textureInfo.ptr + textureInfo.stride * hit.index.y));
365 t2 = glm::vec2(*reinterpret_cast<const uint8_t*>(textureInfo.ptr + textureInfo.stride * hit.index.z));
366 }
367 break;
368 case Cogs::DataFormat::R16_UINT:
369 t0 = glm::vec2(*reinterpret_cast<const uint16_t*>(textureInfo.ptr + textureInfo.stride * hit.index.x));
370 if (!flatInterpolation) {
371 t1 = glm::vec2(*reinterpret_cast<const uint16_t*>(textureInfo.ptr + textureInfo.stride * hit.index.y));
372 t2 = glm::vec2(*reinterpret_cast<const uint16_t*>(textureInfo.ptr + textureInfo.stride * hit.index.z));
373 }
374 break;
375 case Cogs::DataFormat::R32_UINT:
376 t0 = glm::vec2(static_cast<float>(*reinterpret_cast<const uint32_t*>(textureInfo.ptr + textureInfo.stride * hit.index.x)));
377 if (!flatInterpolation) {
378 t1 = glm::vec2(static_cast<float>(*reinterpret_cast<const uint32_t*>(textureInfo.ptr + textureInfo.stride * hit.index.y)));
379 t2 = glm::vec2(static_cast<float>(*reinterpret_cast<const uint32_t*>(textureInfo.ptr + textureInfo.stride * hit.index.z)));
380 }
381 break;
382 case Cogs::DataFormat::Unknown: // No texcoords
383 if (addInstanceTexcoords && textureInfo.hasIdOffset) {
384 // If we lack per-vertex ids, but per-mesh id has been requested and is available, return that.
385 return glm::vec2(static_cast<float>(textureInfo.idOffset));
386 }
388 default:
389 LOG_WARNING_ONCE(logger, "Unhandled texcoord format 0x%x", static_cast<uint32_t>(textureInfo.format));
391 break;
392 }
393
394 glm::vec2 rv;
395 if (flatInterpolation) {
396 rv = t0;
397 }
398 else {
399 rv = hit.bary.x * t0 + hit.bary.y * t1 + hit.bary.z * t2;
400 }
401
402 if (addInstanceTexcoords && textureInfo.hasIdOffset) {
403 rv.x = float(uint32_t(rv.x) + textureInfo.idOffset);
404 }
405 return rv;
406}
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:186
COGSCORE_DLL_API 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:209
COGSCORE_DLL_API 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:236
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:181
static constexpr glm::vec2 NoPickTextureCoords
Marks no match in texture.
Definition: RayPick.h:33
Base component for all rendering content.
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.
RenderLayers
Contains common render layers.
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:202
EntityId entity
The EntityId picked.
Definition: RayPick.h:72
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