#include "BulletCollision/CollisionShapes/btSphereShape.h" #include "BulletDynamics/ConstraintSolver/btPoint2PointConstraint.h" #include "camera.hpp" #include "ecs.hpp" #include "math-helpers.hpp" #include "physics.hpp" #include "pk.h" #include "vendor-glm-include.hpp" PkeCamera NullCamera {}; CompInstance NullCameraInstance{}; PkeCamera *ActiveCamera = &NullCamera; struct pke_camera_master { pk_membucket *bkt; pk_bkt_arr_t bktc_cameras{}; } cam_mstr; btSphereShape CameraShape{1.f}; PkeCamera &PkeCamera_Register_Inner(PkeCamera &cam, CompInstance &inst, const InstPos &instPos) { btVector3 gravity(0.f, 0.f, 0.f); inst.comp_instance_flags |= COMPONENT_INSTANCE_FLAG_DO_NOT_SERIALIZE; cam.phys.instHandle = inst.instanceHandle; inst.physicsLayer = PhysicsCollision{0}; inst.physicsMask = PhysicsCollision{0}; btVector3 localInertia(0, 0, 0); CameraShape.calculateLocalInertia(instPos.mass, localInertia); inst.bt.motionState = pk_new(MemBkt_Bullet); new (inst.bt.motionState) btDefaultMotionState(instPos.posRot); inst.bt.rigidBody = pk_new(MemBkt_Bullet); new (inst.bt.rigidBody) btRigidBody(instPos.mass, inst.bt.motionState, &CameraShape, localInertia); inst.bt.rigidBody->setLinearVelocity(btVector3(0,0,0)); inst.bt.rigidBody->setAngularVelocity(btVector3(0,0,0)); inst.bt.rigidBody->getCollisionShape()->setLocalScaling(instPos.scale); BtDynamicsWorld->addRigidBody(inst.bt.rigidBody); inst.bt.rigidBody->setGravity(gravity); inst.bt.rigidBody->getBroadphaseProxy()->m_collisionFilterGroup = static_cast(inst.physicsLayer); inst.bt.rigidBody->getBroadphaseProxy()->m_collisionFilterMask = static_cast(inst.physicsMask); inst.bt.rigidBody->setUserPointer(reinterpret_cast(&inst)); return cam; } PkeCamera &PkeCamera_Register(pk_uuid uuid, const InstPos &instPos) { CameraHandle cameraHandle{pk_bkt_arr_new_handle(&cam_mstr.bktc_cameras)}; CompInstance *inst; auto &cam = cam_mstr.bktc_cameras[cameraHandle]; new (&cam) PkeCamera{}; cam.uuid = uuid; ECS_CreateEntity(&cam, nullptr); cam.camHandle = cameraHandle; inst = ECS_CreateInstance(&cam, pk_uuid_zed, nullptr, nullptr); return PkeCamera_Register_Inner(cam, *inst, instPos); } PkeCamera *PkeCamera_Get(CameraHandle cameraHandle) { assert(cameraHandle != CameraHandle_MAX); return &cam_mstr.bktc_cameras[cameraHandle]; } PkeCamera *PkeCamera_Get(EntityHandle handle) { using CamFindFn = pk_tmpln_2; CamFindFn cam_find_cb{}; cam_find_cb.func = [handle](const PkeCamera *user_obj_data, const PkeCamera *arr_obj_data) { (void)user_obj_data; return arr_obj_data->handle == handle; }; pk_bkt_arr_handle cam_handle = pk_bkt_arr_find_first_handle(&cam_mstr.bktc_cameras, &CamFindFn::invoke, &cam_find_cb, NULL); if (cam_handle == CameraHandle_MAX) { return nullptr; } return &cam_mstr.bktc_cameras[cam_handle]; } void PkeCamera_TargetInstance(CameraHandle cameraHandle, CompInstance *inst) { assert(cameraHandle != CameraHandle_MAX); auto &cam = cam_mstr.bktc_cameras[cameraHandle]; CompInstance *selfInstance = ECS_GetInstance(cam.phys.instHandle); if (cam.phys.constraint != nullptr && cam.phys.constraint != CAFE_BABE(btTypedConstraint)) { PkeCamera_UntargetInstance(cameraHandle); } btVector3 cameraOffset(0.f, -10.f, -10.f); btTransform trfm; btQuaternion bQuatRot; inst->bt.motionState->getWorldTransform(trfm); glm::vec3 gPos; BulletToGlm(trfm.getOrigin(), gPos); if (gPos != glm::vec3(0, 0, 0)) { gPos = glm::normalize(-gPos); } glm::quat gQuatRot = glm::quatLookAt(-gPos, glm::vec3(0, -1, 0)); GlmToBullet(gQuatRot, bQuatRot); trfm.setOrigin(trfm.getOrigin() + cameraOffset); trfm.setRotation(bQuatRot); cam.phys.target_inst_uuid = inst->uuid; cam.phys.target_inst_handle = inst->instanceHandle; selfInstance->bt.motionState->setWorldTransform(trfm); selfInstance->bt.rigidBody->setWorldTransform(trfm); selfInstance->bt.rigidBody->setLinearVelocity(btVector3(0,0,0)); selfInstance->bt.rigidBody->setAngularVelocity(btVector3(0,0,0)); selfInstance->bt.rigidBody->activate(); assert(cam.phys.constraint == nullptr || cam.phys.constraint == CAFE_BABE(btTypedConstraint)); /* 2025-01-14 JCB * This was throwing bullet3 errors, so I've decided to split the work. * This commit completes the 'targeting' task, where a camera tracks a target. * This may or may not make a good starting point for the 'following' task. * cam.phys.constraint = pk_new(MemBkt_Bullet); new (cam.phys.constraint) btPoint2PointConstraint(*selfInstance->bt.rigidBody, *inst->bt.rigidBody, btVector3(0.f, -1.f, -1.f), cameraOffset); BtDynamicsWorld->addConstraint(cam.phys.constraint); */ cam.view = PKE_CAMERA_VIEW_TARGET; cam.stale = PKE_CAMERA_STALE_POSROT; } void PkeCamera_UntargetInstance(CameraHandle cameraHandle) { assert(cameraHandle != CameraHandle_MAX); auto &cam = cam_mstr.bktc_cameras[cameraHandle]; BtDynamicsWorld->removeConstraint(cam.phys.constraint); pk_delete(cam.phys.constraint, MemBkt_Bullet); cam.phys.constraint = CAFE_BABE(btTypedConstraint); cam.stale = PKE_CAMERA_STALE_POSROT; } void PkeCamera_SetPrimary(CameraHandle cameraHandle) { bool b; pk_iter_t iter_cam{}; assert(cameraHandle != CameraHandle_MAX); b = pk_bkt_arr_iter_begin(&cam_mstr.bktc_cameras, &iter_cam); while (b == true) { // TODO 2025-05-28 JCB // There was some speculative scene filtering happening here. // I removed it because it was comparing entity parents and I don't // think that is accurate. // Instead, should be a per-viewport or per-world check? iter_cam->isPrimary = iter_cam->camHandle == cameraHandle; b = pk_bkt_arr_iter_increment(&cam_mstr.bktc_cameras, &iter_cam); } } pk_bkt_arr &PkeCamera_GetPkBktArr() { return cam_mstr.bktc_cameras; } void PkeCamera_Init() { cam_mstr.bkt = pk_mem_bucket_create("pk_bkt_arr cam", 1024 * 1024, PK_MEMBUCKET_FLAG_NONE); new (&cam_mstr.bktc_cameras) pk_bkt_arr_t{ pk_bkt_arr_handle_MAX_constexpr, cam_mstr.bkt, cam_mstr.bkt }; NullCamera.type = PKE_CAMERA_TYPE_ORTHOGONAL; NullCamera.view = PKE_CAMERA_VIEW_TARGET; NullCamera.stale = PKE_CAMERA_STALE_ALL; InstPos instPos{ .posRot = {}, .scale = btVector3(1.f, 1.f, 1.f), .mass = 1.f, }; PkeCamera_Register_Inner(NullCamera, NullCameraInstance, instPos); } void PkeCamera_Teardown() { BtDynamicsWorld->removeRigidBody(NullCameraInstance.bt.rigidBody); pk_delete(NullCameraInstance.bt.motionState, MemBkt_Bullet); pk_delete(NullCameraInstance.bt.rigidBody, MemBkt_Bullet); NullCameraInstance.bt.motionState = CAFE_BABE(btDefaultMotionState); NullCameraInstance.bt.rigidBody = CAFE_BABE(btRigidBody); pk_bkt_arr_teardown(&cam_mstr.bktc_cameras); pk_mem_bucket_destroy(cam_mstr.bkt); } void PkeCamera_Tick(double delta) { /* 2024-01-16 - JCB - This seems excessive to loop through every camera and do this. * I think this could be simplified, but it might be premature optimization. * Why we're looping all: * - To avoid any weird scenarios where the active camera is changed and * 1 frame the camera is in the wrong position. * - To prevent various 'saves' that save camera positions from saving bad positional data * It might be possible to handle these two scenarios explicitly, or it * could be that removing pos and rot from the camera would make this unnecessary? * See the camera serializer for more. */ (void)delta; CompInstance *inst; pk_iter_t iter_cam{}; bool b; b = pk_bkt_arr_iter_begin(&cam_mstr.bktc_cameras, &iter_cam); while (b == true) { inst = ECS_GetInstance(iter_cam->phys.instHandle); if (iter_cam->isMarkedForRemoval == true) { if (ActiveCamera == iter_cam.data) { ActiveCamera = &NullCamera; } if (iter_cam->phys.constraint != nullptr && iter_cam->phys.constraint != CAFE_BABE(btTypedConstraint)) { // reminder: this is not currently handled by ECS BtDynamicsWorld->removeConstraint(iter_cam->phys.constraint); pk_delete(iter_cam->phys.constraint, MemBkt_Bullet); } if (inst != nullptr) { if (inst->bt.rigidBody != nullptr) { BtDynamicsWorld->removeRigidBody(inst->bt.rigidBody); pk_delete(inst->bt.rigidBody, MemBkt_Bullet); } if (inst->bt.motionState != nullptr) { pk_delete(inst->bt.motionState, MemBkt_Bullet); } } pk_bkt_arr_free_handle(&cam_mstr.bktc_cameras, iter_cam->camHandle); new (iter_cam.data) PkeCamera{}; b = pk_bkt_arr_iter_increment(&cam_mstr.bktc_cameras, &iter_cam); continue; } assert(inst != nullptr); if (inst->isNeedingUpdated == true) { iter_cam->stale = iter_cam->stale | PKE_CAMERA_STALE_POSROT; inst->isNeedingUpdated = false; } if (iter_cam->phys.target_inst_uuid == pk_uuid_zed) { b = pk_bkt_arr_iter_increment(&cam_mstr.bktc_cameras, &iter_cam); continue; } iter_cam->stale = iter_cam->stale | PKE_CAMERA_STALE_POSROT; b = pk_bkt_arr_iter_increment(&cam_mstr.bktc_cameras, &iter_cam); }; }