diff options
| author | Jonathan Bradley <jcb@pikum.xyz> | 2024-01-15 18:20:58 -0500 |
|---|---|---|
| committer | Jonathan Bradley <jcb@pikum.xyz> | 2024-01-15 18:20:58 -0500 |
| commit | c30b1f9b2f5d231e98194db526560eb4e010edff (patch) | |
| tree | 328825d5187f5f0b3ffd6754f8795f6a5947b8f7 | |
| parent | 05a6ca44e40da855a1ddc32cfe799edef74f7bdf (diff) | |
major refactor so cameras are entities and have a rigid body instance
| -rw-r--r-- | editor/editor.cpp | 20 | ||||
| -rw-r--r-- | src/camera.cpp | 136 | ||||
| -rw-r--r-- | src/camera.hpp | 29 | ||||
| -rw-r--r-- | src/ecs.cpp | 35 | ||||
| -rw-r--r-- | src/game.cpp | 25 | ||||
| -rw-r--r-- | src/math-helpers.cpp | 1 | ||||
| -rw-r--r-- | src/window.cpp | 4 |
7 files changed, 204 insertions, 46 deletions
diff --git a/editor/editor.cpp b/editor/editor.cpp index a081fb0..6ce8f76 100644 --- a/editor/editor.cpp +++ b/editor/editor.cpp @@ -681,9 +681,15 @@ void RecordImGuiCameras() { return; } if (ImGui::Button("Create")) { - auto &cam = PkeCamera_Register(); - cam.pos = ActiveCamera->pos; - cam.rot = ActiveCamera->rot; + InstPos instPos{}; + btVector3 pos; + btQuaternion quat; + GlmToBullet(ActiveCamera->pos, pos); + GlmToBullet(ActiveCamera->rot, quat); + instPos.mass = 1.f; + instPos.posRot.setOrigin(pos); + instPos.posRot.setRotation(quat); + auto &cam = PkeCamera_Register(instPos); cam.target = ActiveCamera->target; cam.type = ActiveCamera->type; cam.orientation = ActiveCamera->orientation; @@ -719,11 +725,15 @@ void RecordImGuiCameras() { ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); ImGui::BeginDisabled(selectedCamera == cam.handle); - if (ImGui::Button("Select")) { - selectedCamera = cam.handle; + if (ImGui::Button("Show")) { + selectedCamera = cam.camHandle; ActiveCamera = const_cast<PkeCamera *>(&cam); ActiveCamera->stale = PKE_CAMERA_STALE_ALL; } + ImGui::SameLine(); + if (ImGui::Button("Select")) { + selectedEntity = cam.phys.inst; + } ImGui::EndDisabled(); ImGui::TableSetColumnIndex(1); ImGui::Text("0x%016lX", cam.handle.hash); diff --git a/src/camera.cpp b/src/camera.cpp index c7a861c..e66a6cf 100644 --- a/src/camera.cpp +++ b/src/camera.cpp @@ -1,26 +1,65 @@ +#include "BulletCollision/CollisionShapes/btSphereShape.h" +#include "BulletDynamics/ConstraintSolver/btPoint2PointConstraint.h" #include "bucketed-array.hpp" #include "camera.hpp" +#include "ecs.hpp" +#include "math-helpers.hpp" #include "memory.hpp" +#include "physics.hpp" PkeCamera NullCamera { - .handle = CameraHandle_MAX, + .camHandle = CameraHandle_MAX, .pos = glm::vec3(3.f, 3.f, 3.f), .rot = glm::quat(1.f, 0.f, 0.f, 0.f), .target = glm::vec3(0.f), .type = PKE_CAMERA_TYPE_ORTHOGONAL, .orientation = PKE_CAMERA_ORIENTATION_TARGET, .stale = PKE_CAMERA_STALE_ALL, + .phys = { + .inst = nullptr, + .constraint = nullptr, + }, }; PkeCamera *ActiveCamera = &NullCamera; const PkeHandleItemIndex_T MAX_CAMERAS_PER_BUCKET = 32; BucketContainer<PkeCamera, CameraHandle> Camera_BucketContainer{}; -PkeCamera &PkeCamera_Register() { +btSphereShape CameraShape{1.f}; + +PkeCamera &PkeCamera_Register(const InstPos &instPos) { CameraHandle cameraHandle{Buckets_NewHandle(Camera_BucketContainer)}; + auto &cam = Camera_BucketContainer.buckets[cameraHandle.bucketIndex][cameraHandle.itemIndex]; - cam.handle = cameraHandle; + new (&cam) PkeCamera{}; + + ECS_CreateEntity(&cam, nullptr); + cam.camHandle = cameraHandle; + cam.phys.inst = ECS_CreateInstance(&cam, nullptr); + + btVector3 gravity(0.f, 0.f, 0.f); + + cam.phys.inst->physicsLayer = PhysicsCollision{0}; + cam.phys.inst->physicsMask = PhysicsCollision{0}; + + btVector3 localInertia(0, 0, 0); + CameraShape.calculateLocalInertia(instPos.mass, localInertia); + cam.phys.inst->bt.motionState = Pke_New<btDefaultMotionState>(MemBkt_Bullet); + new (cam.phys.inst->bt.motionState) btDefaultMotionState(instPos.posRot); + + cam.phys.inst->bt.rigidBody = Pke_New<btRigidBody>(MemBkt_Bullet); + new (cam.phys.inst->bt.rigidBody) btRigidBody(instPos.mass, cam.phys.inst->bt.motionState, &CameraShape, localInertia); + + cam.phys.inst->bt.rigidBody->setLinearVelocity(btVector3(0,0,0)); + cam.phys.inst->bt.rigidBody->setAngularVelocity(btVector3(0,0,0)); + cam.phys.inst->bt.rigidBody->getCollisionShape()->setLocalScaling(instPos.scale); + BtDynamicsWorld->addRigidBody(cam.phys.inst->bt.rigidBody); + cam.phys.inst->bt.rigidBody->setGravity(gravity); + cam.phys.inst->bt.rigidBody->getBroadphaseProxy()->m_collisionFilterGroup = static_cast<PhysicsCollision_T>(cam.phys.inst->physicsLayer); + cam.phys.inst->bt.rigidBody->getBroadphaseProxy()->m_collisionFilterMask = static_cast<PhysicsCollision_T>(cam.phys.inst->physicsMask); + cam.phys.inst->bt.rigidBody->setUserPointer(reinterpret_cast<void *>(cam.phys.inst)); + return cam; } @@ -29,16 +68,68 @@ PkeCamera *PkeCamera_Get(CameraHandle cameraHandle) { return &Camera_BucketContainer.buckets[cameraHandle.bucketIndex][cameraHandle.itemIndex]; } +void PkeCamera_AttachToInstance(CameraHandle cameraHandle, CompInstance *inst) { + assert(cameraHandle != CameraHandle_MAX); + auto &cam = Camera_BucketContainer.buckets[cameraHandle.bucketIndex][cameraHandle.itemIndex]; + + btVector3 cameraOffset(0.f, -10.f, -10.f); + + btTransform trfm; + inst->bt.motionState->getWorldTransform(trfm); + BulletToGlm(trfm.getOrigin(), cam.target); + trfm.setOrigin(trfm.getOrigin() + cameraOffset); + BulletToGlm(trfm.getOrigin(), cam.pos); + + cam.phys.inst->bt.motionState->setWorldTransform(trfm); + cam.phys.inst->bt.rigidBody->setWorldTransform(trfm); + cam.phys.inst->bt.rigidBody->setLinearVelocity(btVector3(0,0,0)); + cam.phys.inst->bt.rigidBody->setAngularVelocity(btVector3(0,0,0)); + cam.phys.inst->bt.rigidBody->activate(); + + assert(cam.phys.constraint == nullptr || cam.phys.constraint == CAFE_BABE(btTypedConstraint)); + cam.phys.constraint = Pke_New<btTypedConstraint>(MemBkt_Bullet); + new (cam.phys.constraint) btPoint2PointConstraint(*cam.phys.inst->bt.rigidBody, *inst->bt.rigidBody, btVector3(0.f, 0.f, 0.f), cameraOffset); + BtDynamicsWorld->addConstraint(cam.phys.constraint); + + cam.orientation = PKE_CAMERA_ORIENTATION_TARGET; + cam.stale = PKE_CAMERA_STALE_POSROT; +} + +void PkeCamera_DetachFromInstance(CameraHandle cameraHandle, CompInstance *inst) { + assert(cameraHandle != CameraHandle_MAX); + auto &cam = Camera_BucketContainer.buckets[cameraHandle.bucketIndex][cameraHandle.itemIndex]; + BtDynamicsWorld->removeConstraint(cam.phys.constraint); + Pke_Delete<btTypedConstraint>(cam.phys.constraint, MemBkt_Bullet); + cam.phys.constraint = CAFE_BABE(btTypedConstraint); + cam.stale = PKE_CAMERA_STALE_POSROT; +} + void PkeCamera_Destroy(CameraHandle cameraHandle) { assert(cameraHandle != CameraHandle_MAX); auto &cam = Camera_BucketContainer.buckets[cameraHandle.bucketIndex][cameraHandle.itemIndex]; - cam.handle = CameraHandle_MAX; + + BtDynamicsWorld->removeRigidBody(cam.phys.inst->bt.rigidBody); + Pke_Delete<btRigidBody>(cam.phys.inst->bt.rigidBody, MemBkt_Bullet); + cam.phys.inst->bt.rigidBody = CAFE_BABE(btRigidBody); + + Pke_Delete<btDefaultMotionState>(cam.phys.inst->bt.motionState, MemBkt_Bullet); + cam.phys.inst->bt.motionState = CAFE_BABE(btDefaultMotionState); + + if (cam.phys.constraint != nullptr && cam.phys.constraint != CAFE_BABE(btTypedConstraint)) { + BtDynamicsWorld->removeConstraint(cam.phys.constraint); + Pke_Delete<btTypedConstraint>(cam.phys.constraint, MemBkt_Bullet); + } + + ECS_MarkForRemoval(&cam); + cam.camHandle = CameraHandle_MAX; cam.pos = glm::vec3(0); cam.rot = glm::quat{}; cam.target = glm::vec3(0); cam.type = PkeCameraType_MAX; cam.orientation = PkeCameraOrientation_MAX; cam.stale = PkeCameraStaleFlags_MAX; + cam.phys.inst = CAFE_BABE(CompInstance); + cam.phys.constraint = CAFE_BABE(btTypedConstraint); } int64_t PkeCamera_GetBucketCount() { @@ -62,3 +153,40 @@ void PkeCamera_Teardown() { Buckets_Destroy(Camera_BucketContainer); } +void PkeCamera_Tick(double delta) { + /* + for (PkeHandleBucketIndex_T b = 0; b <= Camera_BucketContainer.pkeHandle.bucketIndex; ++b) { + auto &bkt = Camera_BucketContainer.buckets[b]; + long itemCount = Camera_BucketContainer.pkeHandle.bucketIndex == b ? Camera_BucketContainer.pkeHandle.itemIndex : Camera_BucketContainer.limits.itemIndex; + for (PkeHandleItemIndex_T i = 0; i < itemCount; ++i) { + auto &cam = bkt[i]; + if (cam.handle == EntityHandle_MAX) { + continue; + } + btTransform trfm; + if (cam.phys.constraint != nullptr && cam.phys.constraint != CAFE_BABE(btTypedConstraint)) { + cam.phys.constraint->getRigidBodyB().getMotionState()->getWorldTransform(trfm); + BulletToGlm(trfm.getOrigin(), cam.target); + } + cam.phys.inst->bt.motionState->getWorldTransform(trfm); + BulletToGlm(trfm.getOrigin(), cam.pos); + cam.stale = cam.stale | PKE_CAMERA_STALE_POSROT; + cam.phys.inst->isNeedingUpdated = false; + } + } + */ + btTransform trfm; + if (ActiveCamera->phys.constraint != nullptr && ActiveCamera->phys.constraint != CAFE_BABE(btTypedConstraint)) { + ActiveCamera->phys.constraint->getRigidBodyB().getMotionState()->getWorldTransform(trfm); + BulletToGlm(trfm.getOrigin(), ActiveCamera->target); + ActiveCamera->stale = ActiveCamera->stale | PKE_CAMERA_STALE_ROT; + } + if (ActiveCamera->phys.inst != nullptr && ActiveCamera->phys.inst != CAFE_BABE(CompInstance)) { + ActiveCamera->phys.inst->bt.motionState->getWorldTransform(trfm); + BulletToGlm(trfm.getOrigin(), ActiveCamera->pos); + BulletToGlm(trfm.getRotation(), ActiveCamera->rot); + ActiveCamera->stale = ActiveCamera->stale | PKE_CAMERA_STALE_POSROT; + ActiveCamera->phys.inst->isNeedingUpdated = false; + } +} + diff --git a/src/camera.hpp b/src/camera.hpp index ab10a5a..1adfbc7 100644 --- a/src/camera.hpp +++ b/src/camera.hpp @@ -1,6 +1,7 @@ #ifndef PKE_CAMERA_HPP #define PKE_CAMERA_HPP +#include "components.hpp" #include "macros.hpp" #include "vendor/glm_include.hpp" #include "memory-type-defs.hpp" @@ -15,37 +16,43 @@ struct CameraHandle : public PkeHandle {}; constexpr CameraHandle CameraHandle_MAX = CameraHandle{}; -const PkeCameraType PKE_CAMERA_TYPE_PERSPECTIVE = PkeCameraType{1 << 0}; -const PkeCameraType PKE_CAMERA_TYPE_ORTHOGONAL = PkeCameraType{1 << 1}; +constexpr PkeCameraType PKE_CAMERA_TYPE_PERSPECTIVE = PkeCameraType{1 << 0}; +constexpr PkeCameraType PKE_CAMERA_TYPE_ORTHOGONAL = PkeCameraType{1 << 1}; -const PkeCameraOrientation PKE_CAMERA_ORIENTATION_TARGET = PkeCameraOrientation{1 << 0}; -const PkeCameraOrientation PKE_CAMERA_ORIENTATION_FREE = PkeCameraOrientation{1 << 1}; +constexpr PkeCameraOrientation PKE_CAMERA_ORIENTATION_TARGET = PkeCameraOrientation{1 << 0}; +constexpr PkeCameraOrientation PKE_CAMERA_ORIENTATION_FREE = PkeCameraOrientation{1 << 1}; -const PkeCameraStaleFlags PKE_CAMERA_STALE_POS = PkeCameraStaleFlags{1 << 0}; -const PkeCameraStaleFlags PKE_CAMERA_STALE_ROT = PkeCameraStaleFlags{1 << 1}; -const PkeCameraStaleFlags PKE_CAMERA_STALE_ORIENTATION = PkeCameraStaleFlags{1 << 2}; -const PkeCameraStaleFlags PKE_CAMERA_STALE_ALL = PkeCameraStaleFlags{0xFF}; +constexpr PkeCameraStaleFlags PKE_CAMERA_STALE_POS = PkeCameraStaleFlags{1 << 0}; +constexpr PkeCameraStaleFlags PKE_CAMERA_STALE_ROT = PkeCameraStaleFlags{1 << 1}; +constexpr PkeCameraStaleFlags PKE_CAMERA_STALE_POSROT = PkeCameraStaleFlags{1 << 0 | 1 << 1}; +constexpr PkeCameraStaleFlags PKE_CAMERA_STALE_PERSPECTIVE = PkeCameraStaleFlags{1 << 2}; +constexpr PkeCameraStaleFlags PKE_CAMERA_STALE_ALL = PkeCameraStaleFlags{0xFF}; -struct PkeCamera { - CameraHandle handle = CameraHandle_MAX; +struct PkeCamera : public Entity_Base { + CameraHandle camHandle = CameraHandle_MAX; glm::vec3 pos = glm::vec3(0); glm::quat rot = glm::quat{}; glm::vec3 target = glm::vec3(0); PkeCameraType type = PkeCameraType_MAX; PkeCameraOrientation orientation = PkeCameraOrientation_MAX; PkeCameraStaleFlags stale = PkeCameraStaleFlags_MAX; + struct Phys { + CompInstance *inst = nullptr; + btTypedConstraint *constraint = nullptr; + } phys; bool isPrimary = false; }; extern PkeCamera NullCamera; extern PkeCamera *ActiveCamera; void PkeCamera_Init(); -PkeCamera &PkeCamera_Register(); +PkeCamera &PkeCamera_Register(const InstPos &instPos); PkeCamera *PkeCamera_Get(CameraHandle handle); int64_t PkeCamera_GetBucketCount(); PkeCamera *PkeCamera_GetCameras(int64_t bucketIndex, int64_t &count); void PkeCamera_Destroy(CameraHandle handle); void PkeCamera_Teardown(); +void PkeCamera_Tick(double delta); void PkeCamera_Unregister(uint64_t id); diff --git a/src/ecs.cpp b/src/ecs.cpp index d087e45..203adb5 100644 --- a/src/ecs.cpp +++ b/src/ecs.cpp @@ -160,15 +160,17 @@ void ECS_Tick(double delta) { if (entityRemovalCount > 0) { int64_t removeIndex = entitiesYetToBeRemoved.FindFirstIndex(DynArrayFindComponent, inst.entHandle); if (removeIndex != -1) { - int64_t afterIndex = updateGrBinds.FindFirstIndex(DynArrayFindGrBinds, inst.grBindsHandle); - updateGrBindsAfter *after = nullptr; - if (afterIndex != -1) { - after = &updateGrBinds[afterIndex]; - } else { - after = &updateGrBinds.Push(); - after->grBindsHandle = inst.grBindsHandle; + if (inst.grBindsHandle != GrBindsHandle_MAX) { + int64_t afterIndex = updateGrBinds.FindFirstIndex(DynArrayFindGrBinds, inst.grBindsHandle); + updateGrBindsAfter *after = nullptr; + if (afterIndex != -1) { + after = &updateGrBinds[afterIndex]; + } else { + after = &updateGrBinds.Push(); + after->grBindsHandle = inst.grBindsHandle; + } + after->count += 1; } - after->count += 1; inst.entHandle = EntityHandle_MAX; inst.grBindsHandle = GrBindsHandle_MAX; @@ -183,7 +185,7 @@ void ECS_Tick(double delta) { continue; } } - if (updateGrBinds.Count() > 0) { + if (updateGrBinds.Count() > 0 && inst.instanceHandle != InstanceHandle_MAX) { int64_t afterIndex = updateGrBinds.FindFirstIndex(DynArrayFindGrBinds, inst.grBindsHandle); if (afterIndex > -1) { auto &after = updateGrBinds[afterIndex]; @@ -251,6 +253,8 @@ void ECS_Tick_Late(double delta) { continue; if (inst.entHandle == EntityHandle_MAX) continue; + if (inst.grBindsHandle == GrBindsHandle_MAX) + continue; auto &grBinds = ecs.bc.grBinds.buckets[inst.grBindsHandle.bucketIndex][inst.grBindsHandle.itemIndex]; @@ -433,7 +437,6 @@ CompGrBinds *ECS_GetGrBinds(uint64_t bucketIndex, uint64_t &itemCount) { CompInstance *ECS_CreateInstance(Entity_Base *entity, CompGrBinds *entityTypeGrBinds) { assert(entity != nullptr && entity != CAFE_BABE(Entity_Base)); - assert(entityTypeGrBinds != nullptr && entityTypeGrBinds != CAFE_BABE(CompGrBinds)); InstanceHandle instanceHandle{Buckets_NewHandle(ecs.bc.instances)}; @@ -441,13 +444,15 @@ CompInstance *ECS_CreateInstance(Entity_Base *entity, CompGrBinds *entityTypeGrB auto *comp = &instBkt[instanceHandle.itemIndex]; new (comp) CompInstance{}; comp->entHandle = entity->handle; - comp->grBindsHandle = entityTypeGrBinds->grBindsHandle; comp->instanceHandle = instanceHandle; - comp->index = entityTypeGrBinds->instanceCounter++; - comp->isNeedingUpdated = true; - if (entityTypeGrBinds->instanceCounter > entityTypeGrBinds->instanceBufferMaxCount) { - EntitiesWithExcessInstances.Push(ECS_GetEntity(entityTypeGrBinds->entHandle)); + if (entityTypeGrBinds != nullptr) { + comp->grBindsHandle = entityTypeGrBinds->grBindsHandle; + comp->index = entityTypeGrBinds->instanceCounter++; + comp->isNeedingUpdated = true; + if (entityTypeGrBinds->instanceCounter > entityTypeGrBinds->instanceBufferMaxCount) { + EntitiesWithExcessInstances.Push(ECS_GetEntity(entityTypeGrBinds->entHandle)); + } } return comp; diff --git a/src/game.cpp b/src/game.cpp index f1f6612..24b98cd 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -150,14 +150,20 @@ void ParseCamera(PkeLevel *level, std::ifstream &stream) { PkeCamera cam{}; while (stream.getline(readLine, readLineLength)) { if (strcmp(readLine, PKE_FILE_OBJ_END) == 0) { - auto &rCam = PkeCamera_Register(); - rCam.pos = cam.pos; - rCam.rot = cam.rot; + InstPos instPos{}; + btVector3 pos; + btQuaternion quat; + GlmToBullet(cam.pos, pos); + GlmToBullet(cam.rot, quat); + instPos.mass = 1.f; + instPos.posRot.setOrigin(pos); + instPos.posRot.setRotation(quat); + auto &rCam = PkeCamera_Register(instPos); rCam.target = cam.target; rCam.type = cam.type; rCam.orientation = cam.orientation; rCam.isPrimary = cam.isPrimary; - PkeLevel_RegisterCamera(level->levelHandle, rCam.handle); + PkeLevel_RegisterCamera(level->levelHandle, rCam.camHandle); if (rCam.isPrimary == true) { ActiveCamera = &rCam; } @@ -269,8 +275,7 @@ void ParseInstance(Entity_Base *parentEntity, std::ifstream &stream) { } if (strstr(readLine, PKE_FILE_INSTANCE_ENTITY_TYPE_CODE)) { uint64_t prefixLen = strlen(PKE_FILE_INSTANCE_ENTITY_TYPE_CODE); - uint64_t len = strlen(readLine + prefixLen); - memcpy(entTypeCode, readLine + prefixLen, len); + strncpy(entTypeCode, readLine + prefixLen, 21); continue; } if (strstr(readLine, PKE_FILE_INSTANCE_POS_POS)) { @@ -522,6 +527,8 @@ void Game_Tick(double delta) { } } + PkeCamera_Tick(delta); + EntityType_Tick_Late(delta); ECS_Tick_Late(delta); } @@ -532,11 +539,11 @@ void Game_Main(PKEWindowProperties windowProps, const char *executablePath) { try { PkeThreads_Init(); AM_Init(); - PkeLevel_Init(); - PkeCamera_Init(); + ECS_Init(); Physics_Init(); + PkeCamera_Init(); + PkeLevel_Init(); Game_Init(); - ECS_Init(); CreateWindow(windowProps); EntityType_Init(); PkeProject_Load(pkeSettings.args.projectPath); diff --git a/src/math-helpers.cpp b/src/math-helpers.cpp index ebcd719..5150e55 100644 --- a/src/math-helpers.cpp +++ b/src/math-helpers.cpp @@ -6,6 +6,7 @@ void GlmToBullet(const glm::vec3 &vec, btVector3 &btVec) { btVec.setX(vec.x); btVec.setY(vec.y); btVec.setZ(vec.z); + btVec.setW(0.f); } void GlmToBullet(const glm::quat &quat, btQuaternion &btQuat) { diff --git a/src/window.cpp b/src/window.cpp index 0c08a2a..61e7098 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -917,7 +917,7 @@ void UpdateCamera() { UBO.view = glm::lookAt(glm::vec3(0), ActiveCamera->pos - ActiveCamera->target, glm::vec3(0.f, 1.f, 0.f)); } } - if (bool(ActiveCamera->stale & PKE_CAMERA_STALE_ORIENTATION)) { + if (bool(ActiveCamera->stale & PKE_CAMERA_STALE_PERSPECTIVE)) { if (bool(ActiveCamera->type == PKE_CAMERA_TYPE_PERSPECTIVE)) { UBO.proj = glm::perspective(glm::radians(45.f), Extent.width / (float)Extent.height, 0.1f, 1000.f); UBO.proj[1][1] *= -1; @@ -2534,7 +2534,7 @@ void RecreateSwapchain() { } Extent.width = width; Extent.height = height; - ActiveCamera->stale = ActiveCamera->stale | PKE_CAMERA_STALE_ORIENTATION; + ActiveCamera->stale = ActiveCamera->stale | PKE_CAMERA_STALE_PERSPECTIVE; DetermineMonitor(); vkDeviceWaitIdle(vkDevice); DestroySwapchain(); |
