void CDrawUtilities::DrawSpotLight(const Fvector& p, const Fvector& d, float range, float phi, u32 clr) { Fmatrix T; Fvector p1; float H,P; float da = PI_MUL_2/LINE_DIVISION; float b = range*_cos(PI_DIV_2-phi/2); float a = range*_sin(PI_DIV_2-phi/2); d.getHP (H,P); T.setHPB (H,P,0); T.translate_over(p); _VertexStream* Stream = &RCache.Vertex; u32 vBase; FVF::L* pv = (FVF::L*)Stream->Lock(LINE_DIVISION*2+2,vs_L->vb_stride,vBase); for (float angle=0; angle<PI_MUL_2; angle+=da){ float _sa =_sin(angle); float _ca =_cos(angle); p1.x = b * _ca; p1.y = b * _sa; p1.z = a; T.transform_tiny(p1); // fill VB pv->set (p,clr); pv++; pv->set (p1,clr); pv++; } p1.mad (p,d,range); pv->set (p,clr); pv++; pv->set (p1,clr); pv++; Stream->Unlock (LINE_DIVISION*2+2,vs_L->vb_stride); // and Render it as triangle list DU_DRAW_DP (D3DPT_LINELIST,vs_L,vBase,LINE_DIVISION+1); }
//--------------------------------------------------------------------- Fmatrix CPhantom::XFORM_center() { Fvector center; Center (center); Fmatrix xform = XFORM(); return xform.translate_over(center); }
void CDrawUtilities::DrawPlane (const Fvector& center, const Fvector2& scale, const Fvector& rotate, u32 clr_s, u32 clr_w, BOOL bCull, BOOL bSolid, BOOL bWire) { Fmatrix M; M.setHPB (rotate.y,rotate.x,rotate.z); M.translate_over(center); // fill VB _VertexStream* Stream = &RCache.Vertex; u32 vBase; if (bSolid){ DU_DRAW_SH(dxRenderDeviceRender::Instance().m_SelectionShader); FVF::L* pv = (FVF::L*)Stream->Lock(5,vs_L->vb_stride,vBase); pv->set (-scale.x, 0, -scale.y, clr_s); M.transform_tiny(pv->p); pv++; pv->set (-scale.x, 0, +scale.y, clr_s); M.transform_tiny(pv->p); pv++; pv->set (+scale.x, 0, +scale.y, clr_s); M.transform_tiny(pv->p); pv++; pv->set (+scale.x, 0, -scale.y, clr_s); M.transform_tiny(pv->p); pv++; pv->set (*(pv-4)); Stream->Unlock(5,vs_L->vb_stride); if (!bCull) DU_DRAW_RS(D3DRS_CULLMODE,D3DCULL_NONE); DU_DRAW_DP (D3DPT_TRIANGLEFAN,vs_L,vBase,2); if (!bCull) DU_DRAW_RS(D3DRS_CULLMODE,D3DCULL_CCW); } if (bWire){ DU_DRAW_SH(dxRenderDeviceRender::Instance().m_WireShader); FVF::L* pv = (FVF::L*)Stream->Lock(5,vs_L->vb_stride,vBase); pv->set (-scale.x, 0, -scale.y, clr_w); M.transform_tiny(pv->p); pv++; pv->set (+scale.x, 0, -scale.y, clr_w); M.transform_tiny(pv->p); pv++; pv->set (+scale.x, 0, +scale.y, clr_w); M.transform_tiny(pv->p); pv++; pv->set (-scale.x, 0, +scale.y, clr_w); M.transform_tiny(pv->p); pv++; pv->set (*(pv-4)); Stream->Unlock(5,vs_L->vb_stride); DU_DRAW_DP (D3DPT_LINESTRIP,vs_L,vBase,4); } }
void CCustomZone::Hit (SHit* pHDS) { Fmatrix M; M.identity(); M.translate_over (pHDS->p_in_bone_space); M.mulA_43 (XFORM()); PlayBulletParticles (M.c); }
void CDrawUtilities::DrawSphere(const Fmatrix& parent, const Fvector& center, float radius, u32 clr_s, u32 clr_w, BOOL bSolid, BOOL bWire) { Fmatrix B; B.scale (radius,radius,radius); B.translate_over (center); B.mulA_43 (parent); RCache.set_xform_world(B); DrawIdentSphere (bSolid, bWire, clr_s,clr_w); }
void CDrawUtilities::DrawAABB(const Fvector& p0, const Fvector& p1, u32 clr_s, u32 clr_w, BOOL bSolid, BOOL bWire) { Fmatrix R; Fvector C; C.set((p1.x+p0.x)*0.5f,(p1.y+p0.y)*0.5f,(p1.z+p0.z)*0.5f); R.scale (_abs(p1.x-p0.x),_abs(p1.y-p0.y),_abs(p1.z-p0.z)); R.translate_over(C); RCache.set_xform_world(R); DrawIdentBox (bSolid,bWire,clr_s,clr_w); }
void CSpectator::cam_Update (CActor* A) { if (A){ const Fmatrix& M = A->XFORM(); CCameraBase* pACam = A->cam_Active(); CCameraBase* cam = cameras[cam_active]; switch(cam_active) { case eacFirstEye:{ Fvector P, D, N; pACam->Get (P, D, N); cam->Set (P, D, N); }break; case eacLookAt:{ float y,p,r; M.getHPB (y,p,r); cam->Set (pACam->yaw,pACam->pitch,-r); } case eacFreeLook:{ cam->SetParent (A); Fmatrix tmp; tmp.identity(); Fvector point, point1, dangle; point.set (0.f,1.6f,0.f); point1.set (0.f,1.6f,0.f); M.transform_tiny (point); tmp.translate_over(point); tmp.transform_tiny (point1); if (!A->g_Alive()) point.set(point1); cam->Update (point,dangle); }break; } //----------------------------------- Fvector P, D, N; cam->Get(P, D, N); cameras[eacFreeFly]->Set(P, D, N); cameras[eacFreeFly]->Set(cam->yaw, cam->pitch, 0); P.y -= 1.6f; XFORM().translate_over(P); //----------------------------------- g_pGameLevel->Cameras().Update(cam); }else{ CCameraBase* cam = cameras[eacFreeFly]; Fvector point, dangle; point.set (0.f,1.6f,0.f); XFORM().transform_tiny (point); // apply shift dangle.set (0,0,0); cam->Update (point,dangle); // cam->vPosition.set(point0); g_pGameLevel->Cameras().Update (cam); // hud output }; }
void CEnvironment::RenderSky () { #ifndef _EDITOR if (0==g_pGameLevel) return ; #endif // clouds_sh.create ("clouds","null"); //. this is the bug-fix for the case when the sky is broken //. for some unknown reason the geoms happen to be invalid sometimes //. if vTune show this in profile, please add simple cache (move-to-forward last found) //. to the following functions: //. CResourceManager::_CreateDecl //. CResourceManager::CreateGeom if(bNeed_re_create_env) { sh_2sky.create (&m_b_skybox,"skybox_2t"); sh_2geom.create (v_skybox_fvf,RCache.Vertex.Buffer(), RCache.Index.Buffer()); clouds_sh.create ("clouds","null"); clouds_geom.create (v_clouds_fvf,RCache.Vertex.Buffer(), RCache.Index.Buffer()); bNeed_re_create_env = FALSE; } ::Render->rmFar (); // draw sky box Fmatrix mSky; mSky.rotateY (CurrentEnv.sky_rotation); mSky.translate_over (Device.vCameraPosition); u32 i_offset,v_offset; u32 C = color_rgba(iFloor(CurrentEnv.sky_color.x*255.f), iFloor(CurrentEnv.sky_color.y*255.f), iFloor(CurrentEnv.sky_color.z*255.f), iFloor(CurrentEnv.weight*255.f)); // Fill index buffer u16* pib = RCache.Index.Lock (20*3,i_offset); CopyMemory (pib,hbox_faces,20*3*2); RCache.Index.Unlock (20*3); // Fill vertex buffer v_skybox* pv = (v_skybox*) RCache.Vertex.Lock (12,sh_2geom.stride(),v_offset); for (u32 v=0; v<12; v++) pv[v].set (hbox_verts[v*2],C,hbox_verts[v*2+1]); RCache.Vertex.Unlock (12,sh_2geom.stride()); // Render RCache.set_xform_world (mSky); RCache.set_Geometry (sh_2geom); RCache.set_Shader (sh_2sky); RCache.set_Textures (&CurrentEnv.sky_r_textures); RCache.Render (D3DPT_TRIANGLELIST,v_offset,0,12,i_offset,20); // Sun ::Render->rmNormal (); eff_LensFlare->Render (TRUE,FALSE,FALSE); }
void CEditShape::Render(int priority, bool strictB2F) { inherited::Render(priority, strictB2F); if (1==priority){ if (strictB2F){ Device.SetShader (Device.m_WireShader); Device.SetRS (D3DRS_CULLMODE,D3DCULL_NONE); u32 clr = Selected()?subst_alpha(m_DrawTranspColor, color_get_A(m_DrawTranspColor)*2):m_DrawTranspColor; Fvector zero ={0.f,0.f,0.f}; for (ShapeIt it=shapes.begin(); it!=shapes.end(); ++it) { switch(it->type) { case cfSphere: { Fsphere& S = it->data.sphere; Fmatrix B; B.scale (S.R,S.R,S.R); B.translate_over (S.P); B.mulA_43 (_Transform()); RCache.set_xform_world(B); Device.SetShader (Device.m_WireShader); DU_impl.DrawCross (zero,1.f,m_DrawEdgeColor,false); DU_impl.DrawIdentSphere (true,true,clr,m_DrawEdgeColor); }break; case cfBox: { Fmatrix B = it->data.box; B.mulA_43 (_Transform()); RCache.set_xform_world(B); DU_impl.DrawIdentBox(true,true,clr,m_DrawEdgeColor); }break; } } Device.SetRS(D3DRS_CULLMODE,D3DCULL_CCW); }else{ if( Selected()&&m_Box.is_valid() ){ Device.SetShader (Device.m_SelectionShader); RCache.set_xform_world (_Transform()); u32 clr = 0xFFFFFFFF; Device.SetShader (Device.m_WireShader); DU_impl.DrawSelectionBox(m_Box,&clr); } } } }
CParticlesObject* CBaseMonster::PlayParticles(const shared_str& name, const Fvector &position, const Fvector &dir, BOOL auto_remove, BOOL xformed) { CParticlesObject* ps = CParticlesObject::Create(name.c_str(),auto_remove); // вычислить позицию и направленность партикла Fmatrix matrix; matrix.identity (); matrix.k.set (dir); Fvector::generate_orthonormal_basis_normalized(matrix.k,matrix.j,matrix.i); matrix.translate_over (position); (xformed) ? ps->SetXFORM (matrix) : ps->UpdateParent(matrix,zero_vel); ps->Play (false); return ps; }
void CPoltergeist::Die(CObject* who) { if (m_tele) { if (state_invisible) { setVisible(true); if (PPhysicsShell()) { Fmatrix M; M.set (XFORM()); M.translate_over (m_current_position); PPhysicsShell()->SetTransform (M); } else Position() = m_current_position; } } inherited::Die (who); Energy::disable (); ability()->on_die (); }
BOOL CBlackGraviArtefact::net_Spawn(CSE_Abstract* DC) { if(!inherited::net_Spawn(DC)) return FALSE; CParticlesObject* pStaticPG; pStaticPG = CParticlesObject::Create("anomaly\\galantine",FALSE); Fmatrix pos; //pos.rotateY(1.57); //pos.mulA(pos); pos.scale(0.7f,0.7f,0.7f); pos.translate_over(XFORM().c); Fvector vel; vel.set(0,0,0); pStaticPG->UpdateParent(pos, vel); pStaticPG->Play(); return TRUE; }
bool ESceneObjectTool::ExportBreakableObjects(SExportStreams* F) { bool bResult = true; CGeomPartExtractor* extractor=0; Fbox bb; if (!GetBox(bb)) return false; extractor = xr_new<CGeomPartExtractor>(); extractor->Initialize(bb,EPS_L,2); UI->SetStatus ("Export breakable objects..."); // collect verts&&faces { SPBItem* pb = UI->ProgressStart(m_Objects.size(),"Prepare geometry..."); for (ObjectIt it=m_Objects.begin(); it!=m_Objects.end(); it++){ pb->Inc(); CSceneObject* obj = dynamic_cast<CSceneObject*>(*it); VERIFY(obj); if (obj->IsStatic()){ CEditableObject *O = obj->GetReference(); const Fmatrix& T = obj->_Transform(); for(EditMeshIt M=O->FirstMesh();M!=O->LastMesh();M++) if (!build_mesh (T,*M,extractor,SGameMtl::flBreakable,FALSE)){bResult=false;break;} } } UI->ProgressEnd(pb); } if (!extractor->Process()) bResult = false; // export parts if (bResult){ SBPartVec& parts = extractor->GetParts(); SPBItem* pb = UI->ProgressStart(parts.size(),"Export Parts..."); for (SBPartVecIt p_it=parts.begin(); p_it!=parts.end(); p_it++){ pb->Inc(); SBPart* P = *p_it; if (P->Valid()){ // export visual AnsiString sn = AnsiString().sprintf("meshes\\brkbl#%d.ogf",(p_it-parts.begin())); xr_string fn = Scene->LevelPath()+sn.c_str(); IWriter* W = FS.w_open(fn.c_str()); R_ASSERT(W); if (!P->Export(*W,1)){ ELog.DlgMsg (mtError,"Invalid breakable object."); bResult = false; break; } FS.w_close (W); // export spawn object { AnsiString entity_ref = "breakable_object"; ISE_Abstract* m_Data = create_entity(entity_ref.c_str()); VERIFY(m_Data); CSE_Visual* m_Visual = m_Data->visual(); VERIFY(m_Visual); // set params m_Data->set_name (entity_ref.c_str()); m_Data->set_name_replace (sn.c_str()); m_Data->position().set (P->m_RefOffset); m_Data->angle().set (P->m_RefRotate); m_Visual->set_visual (sn.c_str(),false); if (s_draw_dbg){ Fmatrix MX; MX.setXYZi (P->m_RefRotate); MX.translate_over (P->m_RefOffset); Fvector DR = {0,0,1}; MX.transform_dir (DR); Tools->m_DebugDraw.AppendLine(P->m_RefOffset,Fvector().mad(P->m_RefOffset,MX.k,1.f),0xFF0000FF,false,false); } NET_Packet Packet; m_Data->Spawn_Write (Packet,TRUE); F->spawn.stream.open_chunk (F->spawn.chunk++); F->spawn.stream.w (Packet.B.data,Packet.B.count); F->spawn.stream.close_chunk (); destroy_entity (m_Data); } }else{ ELog.Msg(mtError,"Can't export invalid part #%d",p_it-parts.begin()); } } UI->ProgressEnd(pb); } // clean up xr_delete(extractor); return bResult; }
void CBurer::UpdateGraviObject() { if ( !m_gravi_object.active ) { return; } if ( !m_gravi_object.enemy || (m_gravi_object.enemy && m_gravi_object.enemy->getDestroy()) ) { m_gravi_object.deactivate(); return; } if ( m_gravi_object.from_pos.distance_to(m_gravi_object.cur_pos) > m_gravi_object.from_pos.distance_to(m_gravi_object.target_pos) ) { m_gravi_object.deactivate(); return; } float dt = float(Device.dwTimeGlobal - m_gravi_object.time_last_update); float dist = dt * float(m_gravi.speed)/1000.f; if (dist < m_gravi.step) return; Fvector new_pos; Fvector dir; dir.sub(m_gravi_object.target_pos,m_gravi_object.cur_pos); dir.normalize(); new_pos.mad(m_gravi_object.cur_pos,dir,dist); // Trace to enemy Fvector enemy_center; m_gravi_object.enemy->Center(enemy_center); dir.sub(enemy_center, new_pos); dir.normalize(); float trace_dist = float(m_gravi.step); collide::rq_result l_rq; if (Level().ObjectSpace.RayPick(new_pos, dir, trace_dist, collide::rqtBoth, l_rq, NULL)) { const CObject *enemy = smart_cast<const CObject *>(m_gravi_object.enemy); if ((l_rq.O == enemy) && (l_rq.range < trace_dist)) { // check for visibility bool b_enemy_visible = false; xr_vector<CObject *> visible_objects; feel_vision_get(visible_objects); // find object for (u32 i = 0; i<visible_objects.size(); i++) { if (visible_objects[i] == enemy) { b_enemy_visible = true; break; } } if (b_enemy_visible) { Fvector impulse_dir; impulse_dir.set(0.0f,0.0f,1.0f); impulse_dir.normalize(); HitEntity(m_gravi_object.enemy, m_gravi.hit_power, m_gravi.impulse_to_enemy, impulse_dir, ALife::eHitTypeStrike, false); m_gravi_object.deactivate(); return; } } } m_gravi_object.cur_pos = new_pos; m_gravi_object.time_last_update = Device.dwTimeGlobal; // --------------------------------------------------------------------- // draw particle CParticlesObject* ps = CParticlesObject::Create(particle_gravi_wave,TRUE); // вычислить позицию и направленность партикла Fmatrix pos; pos.identity(); pos.k.set(dir); Fvector::generate_orthonormal_basis_normalized(pos.k,pos.j,pos.i); // установить позицию pos.translate_over(m_gravi_object.cur_pos); ps->UpdateParent(pos, zero_vel); ps->Play(false); // hit objects m_nearest.clear_not_free (); Level().ObjectSpace.GetNearest (m_nearest,m_gravi_object.cur_pos, m_gravi.radius, NULL); //xr_vector<CObject*> &m_nearest = Level().ObjectSpace.q_nearest; for (u32 i=0;i<m_nearest.size();i++) { CPhysicsShellHolder *obj = smart_cast<CPhysicsShellHolder *>(m_nearest[i]); if (!obj || !obj->m_pPhysicsShell) continue; Fvector dir; dir.sub(obj->Position(), m_gravi_object.cur_pos); dir.normalize(); obj->m_pPhysicsShell->applyImpulse(dir,m_gravi.impulse_to_objects * obj->m_pPhysicsShell->getMass()); } // играть звук Fvector snd_pos = m_gravi_object.cur_pos; snd_pos.y += 0.5f; if (sound_gravi_wave._feedback()) { sound_gravi_wave.set_position (snd_pos); } else ::Sound->play_at_pos (sound_gravi_wave,0,snd_pos); }
void CActor::cam_Update(float dt, float fFOV) { if(m_holder) return; if( (mstate_real & mcClimb) && (cam_active!=eacFreeLook) ) camUpdateLadder(dt); on_weapon_shot_update(); float y_shift =0; if( GamePersistent().GameType() != eGameIDSingle && ik_cam_shift && character_physics_support() && character_physics_support()->ik_controller() ) { y_shift = character_physics_support()->ik_controller()->Shift(); float cam_smooth_k = 1.f; if(_abs(y_shift-current_ik_cam_shift)>ik_cam_shift_tolerance) { cam_smooth_k = 1.f - ik_cam_shift_speed * dt/0.01f; } if(_abs(y_shift)<ik_cam_shift_tolerance/2.f) cam_smooth_k = 1.f - ik_cam_shift_speed * 1.f/0.01f * dt; clamp( cam_smooth_k, 0.f, 1.f ); current_ik_cam_shift = cam_smooth_k * current_ik_cam_shift + y_shift * ( 1.f - cam_smooth_k ); } else current_ik_cam_shift = 0; Fvector point = {0,CameraHeight() + current_ik_cam_shift,0}; Fvector dangle = {0,0,0}; Fmatrix xform; xform.setXYZ (0,r_torso.yaw,0); xform.translate_over(XFORM().c); // lookout if (this == Level().CurrentControlEntity()) cam_Lookout( xform, point.y ); if (!fis_zero(r_torso.roll)) { float radius = point.y*0.5f; float valid_angle = r_torso.roll/2.f; calc_point (point,radius,0,valid_angle); dangle.z = (PI_DIV_2-((PI+valid_angle)/2)); } float flCurrentPlayerY = xform.c.y; // Smooth out stair step ups if ((character_physics_support()->movement()->Environment()==CPHMovementControl::peOnGround) && (flCurrentPlayerY-fPrevCamPos>0)){ fPrevCamPos += dt*1.5f; if (fPrevCamPos > flCurrentPlayerY) fPrevCamPos = flCurrentPlayerY; if (flCurrentPlayerY-fPrevCamPos>0.2f) fPrevCamPos = flCurrentPlayerY-0.2f; point.y += fPrevCamPos-flCurrentPlayerY; }else{ fPrevCamPos = flCurrentPlayerY; } float _viewport_near = VIEWPORT_NEAR; // calc point xform.transform_tiny (point); CCameraBase* C = cam_Active(); C->Update (point,dangle); C->f_fov = fFOV; if(eacFirstEye != cam_active) { cameras[eacFirstEye]->Update (point,dangle); cameras[eacFirstEye]->f_fov = fFOV; } if (Level().CurrentEntity() == this) { collide_camera( *cameras[eacFirstEye], _viewport_near, this ); } if( psActorFlags.test(AF_PSP) ) { Cameras().UpdateFromCamera (C); }else { Cameras().UpdateFromCamera (cameras[eacFirstEye]); } fCurAVelocity = vPrevCamDir.sub(cameras[eacFirstEye]->vDirection).magnitude()/Device.fTimeDelta; vPrevCamDir = cameras[eacFirstEye]->vDirection; #ifdef DEBUG if( dbg_draw_camera_collision ) { dbg_draw_viewport( *cameras[eacFirstEye], _viewport_near ); dbg_draw_viewport( Cameras(), _viewport_near ); } #endif if (Level().CurrentEntity() == this) { Level().Cameras().UpdateFromCamera (C); if(eacFirstEye == cam_active && !Level().Cameras().GetCamEffector(cefDemo)){ Cameras().ApplyDevice (_viewport_near); } } }
void CActor::cam_Update(float dt, float fFOV) { if(m_holder) return; if( (mstate_real & mcClimb) && (cam_active!=eacFreeLook) ) camUpdateLadder(dt); on_weapon_shot_update(); // Alex ADD: smooth crouch fix if (!CurrentHeight)CurrentHeight = CameraHeight(); float HeightInterpolationSpeed = 9.f; if (CurrentHeight != CameraHeight() && !Device.dwPrecacheFrame) { CurrentHeight = (CurrentHeight * (1.0f - HeightInterpolationSpeed*dt)) + (CameraHeight() * HeightInterpolationSpeed*dt); } Fvector point = { 0, CurrentHeight, 0 }; //Fvector point = {0,CameraHeight(),0}; Fvector dangle = {0,0,0}; Fmatrix xform; xform.setXYZ (0,r_torso.yaw,0); xform.translate_over(XFORM().c); // lookout if (this == Level().CurrentControlEntity()) cam_Lookout( xform, point.y ); if (!fis_zero(r_torso.roll)) { float radius = point.y*0.5f; float valid_angle = r_torso.roll/2.f; calc_point (point,radius,0,valid_angle); dangle.z = (PI_DIV_2-((PI+valid_angle)/2)); } float flCurrentPlayerY = xform.c.y; // Smooth out stair step ups if ((character_physics_support()->movement()->Environment()==peOnGround) && (flCurrentPlayerY-fPrevCamPos>0)){ fPrevCamPos += dt*1.5f; if (fPrevCamPos > flCurrentPlayerY) fPrevCamPos = flCurrentPlayerY; if (flCurrentPlayerY-fPrevCamPos>0.2f) fPrevCamPos = flCurrentPlayerY-0.2f; point.y += fPrevCamPos-flCurrentPlayerY; }else{ fPrevCamPos = flCurrentPlayerY; } float _viewport_near = VIEWPORT_NEAR; // calc point xform.transform_tiny (point); CCameraBase* C = cam_Active(); C->Update (point,dangle); C->f_fov = fFOV; if(eacFirstEye != cam_active) { cameras[eacFirstEye]->Update (point,dangle); cameras[eacFirstEye]->f_fov = fFOV; } if (Level().CurrentEntity() == this) collide_camera( *cameras[eacFirstEye], _viewport_near ); if( psActorFlags.test(AF_PSP) ) { Cameras().UpdateFromCamera (C); }else { Cameras().UpdateFromCamera (cameras[eacFirstEye]); } fCurAVelocity = vPrevCamDir.sub(cameras[eacFirstEye]->vDirection).magnitude()/Device.fTimeDelta; vPrevCamDir = cameras[eacFirstEye]->vDirection; #ifdef DEBUG if( dbg_draw_camera_collision ) { dbg_draw_viewport( *cameras[eacFirstEye], _viewport_near ); dbg_draw_viewport( Cameras(), _viewport_near ); } #endif if (Level().CurrentEntity() == this) { Level().Cameras().UpdateFromCamera (C); if(eacFirstEye == cam_active && !Level().Cameras().GetCamEffector(cefDemo)){ Cameras().ApplyDevice (_viewport_near); } } }