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