Oh, now I understood, I was really completely wrong with using RaycastSystem. I’ve done as @eizenhorn said, and it works. Thank both of you a lot!
The only thing that I don’t really like is GetEntityQuery is a protected method, and I can’t create a query inside of my RaycastUtils. So, I created it in SelectionSystem and it looks ugly
Is it the only approach to create EntityQuery in my case?
Here is what I have now
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;
using BoxCollider = SE.Core.Collisions.BoxCollider;
using SphereCollider = SE.Core.Collisions.SphereCollider;
public struct IgnoreRaycast : IComponentData {}
public static class RaycastUtils {
[BurstCompile]
private struct RaycastJob : IJobChunk {
[ReadOnly] public ArchetypeChunkEntityType entityType;
[ReadOnly] public ArchetypeChunkComponentType<LocalToWorld> localToWorldType;
[ReadOnly] public ArchetypeChunkComponentType<BoxCollider> boxColliderType;
[ReadOnly] public ArchetypeChunkComponentType<SphereCollider> sphereColliderType;
public Ray ray;
public NativeArray<Entity> closestEntityResult;
[DeallocateOnJobCompletion]
public NativeArray<float> closestDistanceResult;
private static float3 TransformNormal(in float3 normal, in float4x4 matrix) {
return new float3(
normal.x * matrix.c0.x + normal.y * matrix.c1.x + normal.z * matrix.c2.x,
normal.x * matrix.c0.y + normal.y * matrix.c1.y + normal.z * matrix.c2.y,
normal.x * matrix.c0.z + normal.y * matrix.c1.z + normal.z * matrix.c2.z);
}
private bool hit_sphere(in float3 center, in float radius, in float3 ro, in float3 rd, out float distance) {
distance = float.MaxValue;
float3 oc = ro - center;
float a = math.dot(rd, rd);
float b = (float) 2.0 * math.dot(oc, rd);
float c = math.dot(oc, oc) - radius * radius;
float discriminant = b * b - 4 * a * c;
if (discriminant < 0.0) {
return false;
} else {
float numerator = -b - math.sqrt(discriminant);
if (numerator > 0.0) {
distance = (float) (numerator / (2.0 * a));
return true;
}
numerator = -b + math.sqrt(discriminant);
if (numerator > 0.0) {
distance = (float) (numerator / (2.0 * a));
return true;
} else {
return false;
}
}
}
public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex) {
if (chunk.Has(boxColliderType)) {
var entities = chunk.GetNativeArray(entityType);
var chunkLocalToWorlds = chunk.GetNativeArray(localToWorldType);
var chunkBoxColliders = chunk.GetNativeArray(boxColliderType);
for (var i = 0; i < chunk.Count; i++) {
float4x4 worldToLocalMatrix = math.inverse(chunkLocalToWorlds[i].Value);
Ray localRay = ray;
localRay.origin = math.transform(worldToLocalMatrix, localRay.origin);
localRay.direction = math.normalize(TransformNormal(localRay.direction, worldToLocalMatrix));
Bounds bounds = new Bounds(chunkBoxColliders[i].center, chunkBoxColliders[i].size);
if (bounds.IntersectRay(localRay, out float distance)) {
if (distance < closestDistanceResult[0]) {
closestDistanceResult[0] = distance;
closestEntityResult[0] = entities[i];
}
}
}
} else if (chunk.Has(sphereColliderType)) {
var entities = chunk.GetNativeArray(entityType);
var chunkLocalToWorlds = chunk.GetNativeArray(localToWorldType);
var sphereColliders = chunk.GetNativeArray(sphereColliderType);
for (var i = 0; i < chunk.Count; i++) {
float4x4 worldToLocalMatrix = math.inverse(chunkLocalToWorlds[i].Value);
Ray localRay = ray;
localRay.origin = math.transform(worldToLocalMatrix, localRay.origin);
localRay.direction = math.normalize(TransformNormal(localRay.direction, worldToLocalMatrix));
if (hit_sphere(sphereColliders[i].center, sphereColliders[i].radius, localRay.origin, localRay.direction, out float distance)) {
if (distance < closestDistanceResult[0]) {
closestDistanceResult[0] = distance;
closestEntityResult[0] = entities[i];
}
}
}
}
}
}
public static JobHandle Raycast(ComponentSystemBase system, EntityQuery group, Ray ray, JobHandle inputDeps, NativeArray<Entity> closestEntityResult) {
NativeArray<float> closestDistanceResult = new NativeArray<float>(1, Allocator.TempJob, NativeArrayOptions.UninitializedMemory) {[0] = float.MaxValue};
JobHandle raycastJobHandle = new RaycastJob {
entityType = system.GetArchetypeChunkEntityType(),
localToWorldType = system.GetArchetypeChunkComponentType<LocalToWorld>(true),
boxColliderType = system.GetArchetypeChunkComponentType<BoxCollider>(true),
sphereColliderType = system.GetArchetypeChunkComponentType<SphereCollider>(true),
closestEntityResult = closestEntityResult,
closestDistanceResult = closestDistanceResult,
ray = ray,
}.Schedule(group, inputDeps);
return raycastJobHandle;
}
}
And SelectionSystem
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Entities;
using Unity.Jobs;
using Unity.Transforms;
using UnityEngine;
using BoxCollider = SE.Core.Collisions.BoxCollider;
using SphereCollider = SE.Core.Collisions.SphereCollider;
public struct Selected : IComponentData {
}
[AlwaysUpdateSystem]
public class SelectionSystem : JobComponentSystem {
private struct SelectJob : IJob {
#pragma warning disable 649
[NativeSetThreadIndex] int threadId;
#pragma warning restore 649
public EntityCommandBuffer.Concurrent entityCommandBuffer;
[DeallocateOnJobCompletion]
public NativeArray<Entity> closestEntityResult;
public void Execute() {
if (closestEntityResult[0] != Entity.Null) {
entityCommandBuffer.AddComponent(threadId, closestEntityResult[0], new Selected());
}
}
}
private EndSimulationEntityCommandBufferSystem m_endSimulationEntityCommandBufferSystem;
private EntityQuery m_group;
protected override void OnCreate() {
base.OnCreate();
m_endSimulationEntityCommandBufferSystem = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
m_group = GetEntityQuery(new EntityQueryDesc {
All = new[] {
ComponentType.ReadOnly<LocalToWorld>(),
},
Any = new[] {
ComponentType.ReadOnly<BoxCollider>(),
ComponentType.ReadOnly<SphereCollider>(),
},
None = new[] {
ComponentType.ReadOnly<IgnoreRaycast>(),
},
Options = EntityQueryOptions.FilterWriteGroup
});
}
protected override JobHandle OnUpdate(JobHandle inputDeps) {
if (MouseInput.leftButtonWasPressed) {
EntityCommandBuffer.Concurrent removeSelectedJobCommandBuffer = m_endSimulationEntityCommandBufferSystem.CreateCommandBuffer().ToConcurrent();
EntityCommandBuffer.Concurrent selectJobCommandBuffer = m_endSimulationEntityCommandBufferSystem.CreateCommandBuffer().ToConcurrent();
NativeArray<Entity> closestEntityResult = new NativeArray<Entity>(1, Allocator.TempJob, NativeArrayOptions.UninitializedMemory){[0] = Entity.Null};
JobHandle removeSelectedComponentsHandle = Entities.ForEach((Entity entity, int entityInQueryIndex, in Selected selected) => {
removeSelectedJobCommandBuffer.RemoveComponent<Selected>(entityInQueryIndex, entity);
}).Schedule(inputDeps);
Ray ray = CameraAccessor.camera.ScreenPointToRay(MouseInput.position);
JobHandle raycastJobHandle = RaycastUtils.Raycast(this, m_group, ray, removeSelectedComponentsHandle, closestEntityResult);
JobHandle selectJobHandle = new SelectJob {
entityCommandBuffer = selectJobCommandBuffer,
closestEntityResult = closestEntityResult,
}.Schedule(raycastJobHandle);
m_endSimulationEntityCommandBufferSystem.AddJobHandleForProducer(removeSelectedComponentsHandle);
m_endSimulationEntityCommandBufferSystem.AddJobHandleForProducer(selectJobHandle);
return selectJobHandle;
}
return inputDeps;
}
}
Take a look at how weird creation EntityQuery in SelectionSystem looks.