aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/linden/indra/newview/lltoolbrush.cpp
diff options
context:
space:
mode:
authorJacek Antonelli2008-08-15 23:44:46 -0500
committerJacek Antonelli2008-08-15 23:44:46 -0500
commit38d6d37f2d982fa959e9e8a4a3f7e1ccfad7b5d4 (patch)
treeadca584755d22ca041a2dbfc35d4eca01f70b32c /linden/indra/newview/lltoolbrush.cpp
parentREADME.txt (diff)
downloadmeta-impy-38d6d37f2d982fa959e9e8a4a3f7e1ccfad7b5d4.zip
meta-impy-38d6d37f2d982fa959e9e8a4a3f7e1ccfad7b5d4.tar.gz
meta-impy-38d6d37f2d982fa959e9e8a4a3f7e1ccfad7b5d4.tar.bz2
meta-impy-38d6d37f2d982fa959e9e8a4a3f7e1ccfad7b5d4.tar.xz
Second Life viewer sources 1.13.2.12
Diffstat (limited to 'linden/indra/newview/lltoolbrush.cpp')
-rw-r--r--linden/indra/newview/lltoolbrush.cpp604
1 files changed, 604 insertions, 0 deletions
diff --git a/linden/indra/newview/lltoolbrush.cpp b/linden/indra/newview/lltoolbrush.cpp
new file mode 100644
index 0000000..1822c63
--- /dev/null
+++ b/linden/indra/newview/lltoolbrush.cpp
@@ -0,0 +1,604 @@
1/**
2 * @file lltoolbrush.cpp
3 * @brief Implementation of the toolbrushes
4 *
5 * Copyright (c) 2001-2007, Linden Research, Inc.
6 *
7 * The source code in this file ("Source Code") is provided by Linden Lab
8 * to you under the terms of the GNU General Public License, version 2.0
9 * ("GPL"), unless you have obtained a separate licensing agreement
10 * ("Other License"), formally executed by you and Linden Lab. Terms of
11 * the GPL can be found in doc/GPL-license.txt in this distribution, or
12 * online at http://secondlife.com/developers/opensource/gplv2
13 *
14 * There are special exceptions to the terms and conditions of the GPL as
15 * it is applied to this Source Code. View the full text of the exception
16 * in the file doc/FLOSS-exception.txt in this software distribution, or
17 * online at http://secondlife.com/developers/opensource/flossexception
18 *
19 * By copying, modifying or distributing this software, you acknowledge
20 * that you have read and understood your obligations described above,
21 * and agree to abide by those obligations.
22 *
23 * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
24 * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
25 * COMPLETENESS OR PERFORMANCE.
26 */
27
28#include "llviewerprecompiledheaders.h"
29
30#include "lltoolbrush.h"
31#include "lltoolselectland.h"
32
33#include "llgl.h"
34
35#include "message.h"
36
37#include "llagent.h"
38#include "llcallbacklist.h"
39#include "llviewercontrol.h"
40#include "llfloatertools.h"
41#include "llregionposition.h"
42#include "llstatusbar.h"
43#include "llsurface.h"
44#include "llsurfacepatch.h"
45#include "lltoolmgr.h"
46#include "llui.h"
47#include "llviewerparcelmgr.h"
48#include "llviewerparceloverlay.h"
49#include "llviewerregion.h"
50#include "llviewerwindow.h"
51#include "llworld.h"
52#include "viewer.h"
53#include "llparcel.h"
54
55#include "llglheaders.h"
56
57const std::string REGION_BLOCKS_TERRAFORM_MSG = "This region does not allow terraforming.\n"
58 "You will need to buy land in another part of the world to terraform it.";
59
60// Globals
61LLToolBrushLand *gToolLand = NULL;
62
63///============================================================================
64/// Local function declarations, constants, enums, and typedefs
65///============================================================================
66
67const S32 LAND_BRUSH_SIZE_COUNT = 3;
68const F32 LAND_BRUSH_SIZE[LAND_BRUSH_SIZE_COUNT] = {1.0f, 2.0f, 4.0f};
69const S32 LAND_STEPS = 3;
70const F32 LAND_METERS_PER_SECOND = 1.0f;
71enum
72{
73 E_LAND_LEVEL = 0,
74 E_LAND_RAISE = 1,
75 E_LAND_LOWER = 2,
76 E_LAND_SMOOTH = 3,
77 E_LAND_NOISE = 4,
78 E_LAND_REVERT = 5,
79 E_LAND_INVALID = 6,
80};
81const LLColor4 OVERLAY_COLOR(1.0f, 1.0f, 1.0f, 1.0f);
82
83///============================================================================
84/// Class LLToolBrushLand
85///============================================================================
86
87// constructor
88LLToolBrushLand::LLToolBrushLand()
89: LLTool("Land"),
90 mStartingZ( 0.0f ),
91 mMouseX( 0 ),
92 mMouseY(0),
93 mGotHover(FALSE),
94 mLastShowParcelOwners(FALSE),
95 mBrushSelected(FALSE)
96{
97 mBrushIndex = gSavedSettings.getS32("RadioLandBrushSize");
98}
99
100void LLToolBrushLand::modifyLandAtPointGlobal(const LLVector3d &pos_global,
101 MASK mask)
102{
103 S32 radioAction = gSavedSettings.getS32("RadioLandBrushAction");
104
105 determineAffectedRegions(mLastAffectedRegions, pos_global);
106 for(LLViewerRegion* regionp = mLastAffectedRegions.getFirstData();
107 regionp != NULL;
108 regionp = mLastAffectedRegions.getNextData())
109 {
110 //BOOL is_changed = FALSE;
111 LLVector3 pos_region = regionp->getPosRegionFromGlobal(pos_global);
112 LLSurface &land = regionp->getLand();
113 char action = E_LAND_LEVEL;
114 switch (radioAction)
115 {
116 case 0:
117 // // average toward mStartingZ
118 action = E_LAND_LEVEL;
119 break;
120 case 1:
121 action = E_LAND_RAISE;
122 break;
123 case 2:
124 action = E_LAND_LOWER;
125 break;
126 case 3:
127 action = E_LAND_SMOOTH;
128 break;
129 case 4:
130 action = E_LAND_NOISE;
131 break;
132 case 5:
133 action = E_LAND_REVERT;
134 break;
135 default:
136 action = E_LAND_INVALID;
137 break;
138 }
139
140 // Don't send a message to the region if nothing changed.
141 //if(!is_changed) continue;
142
143 // Now to update the patch information so it will redraw correctly.
144 LLSurfacePatch *patchp= land.resolvePatchRegion(pos_region);
145 if (patchp)
146 {
147 patchp->dirtyZ();
148 }
149
150 // Also force the property lines to update, normals to recompute, etc.
151 regionp->forceUpdate();
152
153 // tell the simulator what we've done
154 F32 seconds = 1.0f / gFPSClamped;
155 F32 x_pos = (F32)pos_region.mV[VX];
156 F32 y_pos = (F32)pos_region.mV[VY];
157 U8 brush_size = (U8)mBrushIndex;
158 LLMessageSystem* msg = gMessageSystem;
159 msg->newMessageFast(_PREHASH_ModifyLand);
160 msg->nextBlockFast(_PREHASH_AgentData);
161 msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
162 msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
163 msg->nextBlockFast(_PREHASH_ModifyBlock);
164 msg->addU8Fast(_PREHASH_Action, (U8)action);
165 msg->addU8Fast(_PREHASH_BrushSize, brush_size);
166 msg->addF32Fast(_PREHASH_Seconds, seconds);
167 msg->addF32Fast(_PREHASH_Height, mStartingZ);
168 msg->nextBlockFast(_PREHASH_ParcelData);
169 msg->addS32Fast(_PREHASH_LocalID, -1);
170 msg->addF32Fast(_PREHASH_West, x_pos );
171 msg->addF32Fast(_PREHASH_South, y_pos );
172 msg->addF32Fast(_PREHASH_East, x_pos );
173 msg->addF32Fast(_PREHASH_North, y_pos );
174 msg->sendMessage(regionp->getHost());
175 }
176}
177
178void LLToolBrushLand::modifyLandInSelectionGlobal()
179{
180 if (gParcelMgr->selectionEmpty())
181 {
182 return;
183 }
184
185 if (gToolMgr->getCurrentTool(gKeyboard->currentMask(TRUE)) == gToolParcel)
186 {
187 // selecting land, don't do anything
188 return;
189 }
190
191 LLVector3d min;
192 LLVector3d max;
193
194 gParcelMgr->getSelection(min, max);
195
196 S32 radioAction = gSavedSettings.getS32("RadioLandBrushAction");
197
198 mLastAffectedRegions.removeAllNodes();
199
200 determineAffectedRegions(mLastAffectedRegions, LLVector3d(min.mdV[VX], min.mdV[VY], 0));
201 determineAffectedRegions(mLastAffectedRegions, LLVector3d(min.mdV[VX], max.mdV[VY], 0));
202 determineAffectedRegions(mLastAffectedRegions, LLVector3d(max.mdV[VX], min.mdV[VY], 0));
203 determineAffectedRegions(mLastAffectedRegions, LLVector3d(max.mdV[VX], max.mdV[VY], 0));
204
205 LLRegionPosition mid_point_region((min + max) * 0.5);
206 LLViewerRegion* center_region = mid_point_region.getRegion();
207 if (center_region)
208 {
209 LLVector3 pos_region = mid_point_region.getPositionRegion();
210 U32 grids = center_region->getLand().mGridsPerEdge;
211 S32 i = llclamp( (S32)pos_region.mV[VX], 0, (S32)grids );
212 S32 j = llclamp( (S32)pos_region.mV[VY], 0, (S32)grids );
213 mStartingZ = center_region->getLand().getZ(i+j*grids);
214 }
215 else
216 {
217 mStartingZ = 0.f;
218 }
219
220 // Stop if our selection include a no-terraform region
221 for(LLViewerRegion* regionp = mLastAffectedRegions.getFirstData();
222 regionp != NULL;
223 regionp = mLastAffectedRegions.getNextData())
224 {
225 if (!canTerraform(regionp))
226 {
227 alertNoTerraform(regionp);
228 return;
229 }
230 }
231
232 for(LLViewerRegion* regionp = mLastAffectedRegions.getFirstData();
233 regionp != NULL;
234 regionp = mLastAffectedRegions.getNextData())
235 {
236 //BOOL is_changed = FALSE;
237 LLVector3 min_region = regionp->getPosRegionFromGlobal(min);
238 LLVector3 max_region = regionp->getPosRegionFromGlobal(max);
239
240 min_region.clamp(0.f, regionp->getWidth());
241 max_region.clamp(0.f, regionp->getWidth());
242 F32 seconds = 1.0f;
243
244 LLSurface &land = regionp->getLand();
245 char action = E_LAND_LEVEL;
246 switch (radioAction)
247 {
248 case 0:
249 // // average toward mStartingZ
250 action = E_LAND_LEVEL;
251 seconds = 1.f;
252 break;
253 case 1:
254 action = E_LAND_RAISE;
255 break;
256 case 2:
257 action = E_LAND_LOWER;
258 break;
259 case 3:
260 action = E_LAND_SMOOTH;
261 seconds = 10.f;
262 break;
263 case 4:
264 action = E_LAND_NOISE;
265 seconds = 0.5f;
266 break;
267 case 5:
268 action = E_LAND_REVERT;
269 seconds = 0.5f;
270 break;
271 default:
272 //action = E_LAND_INVALID;
273 //seconds = 0.0f;
274 return;
275 break;
276 }
277
278 // Don't send a message to the region if nothing changed.
279 //if(!is_changed) continue;
280
281 // Now to update the patch information so it will redraw correctly.
282 LLSurfacePatch *patchp= land.resolvePatchRegion(min_region);
283 if (patchp)
284 {
285 patchp->dirtyZ();
286 }
287
288 // Also force the property lines to update, normals to recompute, etc.
289 regionp->forceUpdate();
290
291 // tell the simulator what we've done
292 U8 brush_size = (U8)mBrushIndex;
293 LLMessageSystem* msg = gMessageSystem;
294 msg->newMessageFast(_PREHASH_ModifyLand);
295 msg->nextBlockFast(_PREHASH_AgentData);
296 msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
297 msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
298 msg->nextBlockFast(_PREHASH_ModifyBlock);
299 msg->addU8Fast(_PREHASH_Action, (U8)action);
300 msg->addU8Fast(_PREHASH_BrushSize, brush_size);
301 msg->addF32Fast(_PREHASH_Seconds, seconds);
302 msg->addF32Fast(_PREHASH_Height, mStartingZ);
303
304 BOOL parcel_selected = gParcelMgr->getWholeParcelSelected();
305 LLParcel* selected_parcel = gParcelMgr->getSelectedParcel();
306
307 if (parcel_selected && selected_parcel)
308 {
309 msg->nextBlockFast(_PREHASH_ParcelData);
310 msg->addS32Fast(_PREHASH_LocalID, selected_parcel->getLocalID());
311 msg->addF32Fast(_PREHASH_West, min_region.mV[VX] );
312 msg->addF32Fast(_PREHASH_South, min_region.mV[VY] );
313 msg->addF32Fast(_PREHASH_East, max_region.mV[VX] );
314 msg->addF32Fast(_PREHASH_North, max_region.mV[VY] );
315 }
316 else
317 {
318 msg->nextBlockFast(_PREHASH_ParcelData);
319 msg->addS32Fast(_PREHASH_LocalID, -1);
320 msg->addF32Fast(_PREHASH_West, min_region.mV[VX] );
321 msg->addF32Fast(_PREHASH_South, min_region.mV[VY] );
322 msg->addF32Fast(_PREHASH_East, max_region.mV[VX] );
323 msg->addF32Fast(_PREHASH_North, max_region.mV[VY] );
324 }
325
326 msg->sendMessage(regionp->getHost());
327 }
328}
329
330void LLToolBrushLand::brush( void )
331{
332 LLVector3d spot;
333 if( gViewerWindow->mousePointOnLandGlobal( mMouseX, mMouseY, &spot ) )
334 {
335 // Round to nearest X,Y grid
336 spot.mdV[VX] = floor( spot.mdV[VX] + 0.5 );
337 spot.mdV[VY] = floor( spot.mdV[VY] + 0.5 );
338
339 modifyLandAtPointGlobal(spot, gKeyboard->currentMask(TRUE));
340 }
341}
342
343BOOL LLToolBrushLand::handleMouseDown(S32 x, S32 y, MASK mask)
344{
345 BOOL handled = FALSE;
346
347 // Find the z value of the initial click.
348 LLVector3d spot;
349 if( gViewerWindow->mousePointOnLandGlobal( x, y, &spot ) )
350 {
351 // Round to nearest X,Y grid
352 spot.mdV[VX] = floor( spot.mdV[VX] + 0.5 );
353 spot.mdV[VY] = floor( spot.mdV[VY] + 0.5 );
354
355 LLRegionPosition region_position( spot );
356 LLViewerRegion* regionp = region_position.getRegion();
357
358 if (!canTerraform(regionp))
359 {
360 alertNoTerraform(regionp);
361 return TRUE;
362 }
363
364 LLVector3 pos_region = region_position.getPositionRegion();
365 U32 grids = regionp->getLand().mGridsPerEdge;
366 S32 i = llclamp( (S32)pos_region.mV[VX], 0, (S32)grids );
367 S32 j = llclamp( (S32)pos_region.mV[VY], 0, (S32)grids );
368 mStartingZ = regionp->getLand().getZ(i+j*grids);
369 mMouseX = x;
370 mMouseY = y;
371 gIdleCallbacks.addFunction( &LLToolBrushLand::onIdle, (void*)this );
372 setMouseCapture( TRUE );
373
374 gParcelMgr->setSelectionVisible(FALSE);
375 handled = TRUE;
376 }
377
378 return handled;
379}
380
381BOOL LLToolBrushLand::handleHover( S32 x, S32 y, MASK mask )
382{
383 lldebugst(LLERR_USER_INPUT) << "hover handled by LLToolBrushLand ("
384 << (hasMouseCapture() ? "active":"inactive")
385 << ")" << llendl;
386 mMouseX = x;
387 mMouseY = y;
388 mGotHover = TRUE;
389 gViewerWindow->getWindow()->setCursor(UI_CURSOR_TOOLLAND);
390 return TRUE;
391}
392
393BOOL LLToolBrushLand::handleMouseUp(S32 x, S32 y, MASK mask)
394{
395 BOOL handled = FALSE;
396 mLastAffectedRegions.removeAllNodes();
397 if( hasMouseCapture() )
398 {
399 // Release the mouse
400 setMouseCapture( FALSE );
401
402 gParcelMgr->setSelectionVisible(TRUE);
403
404 gIdleCallbacks.deleteFunction( &LLToolBrushLand::onIdle, (void*)this );
405 handled = TRUE;
406 }
407
408 return handled;
409}
410
411void LLToolBrushLand::handleSelect()
412{
413 gEditMenuHandler = this;
414
415 gFloaterTools->setStatusText("Click and hold to modify land");
416// if (!mBrushSelected)
417 {
418 mLastShowParcelOwners = gSavedSettings.getBOOL("ShowParcelOwners");
419 gSavedSettings.setBOOL("ShowParcelOwners", TRUE);
420 mBrushSelected = TRUE;
421 }
422}
423
424
425void LLToolBrushLand::handleDeselect()
426{
427 if( gEditMenuHandler == this )
428 {
429 gEditMenuHandler = NULL;
430 }
431 gFloaterTools->setStatusText("");
432 gSavedSettings.setBOOL("ShowParcelOwners", mLastShowParcelOwners);
433 gParcelMgr->setSelectionVisible(TRUE);
434 mBrushSelected = FALSE;
435}
436
437// Draw the area that will be affected.
438void LLToolBrushLand::render()
439{
440 if(mGotHover)
441 {
442 //llinfos << "LLToolBrushLand::render()" << llendl;
443 LLVector3d spot;
444 if(gViewerWindow->mousePointOnLandGlobal(mMouseX, mMouseY, &spot))
445 {
446 spot.mdV[VX] = floor( spot.mdV[VX] + 0.5 );
447 spot.mdV[VY] = floor( spot.mdV[VY] + 0.5 );
448
449 mBrushIndex = gSavedSettings.getS32("RadioLandBrushSize");
450 LLLinkedList<LLViewerRegion> regions;
451 determineAffectedRegions(regions, spot);
452
453 // Now, for each region, render the overlay
454 LLVector3 pos_world = gAgent.getRegion()->getPosRegionFromGlobal(spot);
455 for(LLViewerRegion* region = regions.getFirstData();
456 region != NULL;
457 region = regions.getNextData())
458 {
459 renderOverlay(region->getLand(),
460 region->getPosRegionFromGlobal(spot),
461 pos_world);
462 }
463 }
464 mGotHover = FALSE;
465 }
466}
467
468void LLToolBrushLand::renderOverlay(LLSurface& land, const LLVector3& pos_region,
469 const LLVector3& pos_world)
470{
471 glMatrixMode(GL_MODELVIEW);
472 LLGLSNoTexture gls_no_texture;
473 LLGLDepthTest mDepthTest(GL_TRUE);
474 glPushMatrix();
475 glColor4fv(OVERLAY_COLOR.mV);
476 glTranslatef(0.0f, 0.0f, 1.0f);
477 //glPushMatrix();
478 //glTranslatef(spot.mV[VX], spot.mV[VY], 100.0f);
479 //gl_rect_2d(0, 10, 10, 0);
480 //glPopMatrix();
481 S32 i = (S32) pos_region.mV[VX];
482 S32 j = (S32) pos_region.mV[VY];
483 S32 half_edge = llfloor(LAND_BRUSH_SIZE[mBrushIndex]);
484 //F32 dz = 0.0f;
485 //S32 dist = 0;
486 glBegin(GL_POINTS);
487 for(S32 di = -half_edge; di <= half_edge; di++)
488 {
489 if((i+di) < 0 || (i+di) >= (S32)land.mGridsPerEdge) continue;
490 for(S32 dj = -half_edge; dj <= half_edge; dj++)
491 {
492 if( (j+dj) < 0 || (j+dj) >= (S32)land.mGridsPerEdge ) continue;
493 glVertex3f(pos_world.mV[VX] + di, pos_world.mV[VY] + dj,
494 land.getZ((i+di)+(j+dj)*land.mGridsPerEdge));
495 }
496 }
497 glEnd();
498 glPopMatrix();
499}
500
501void LLToolBrushLand::determineAffectedRegions(LLLinkedList<LLViewerRegion>& regions,
502 const LLVector3d& spot ) const
503{
504 LLVector3d corner(spot);
505 corner.mdV[VX] -= (LAND_BRUSH_SIZE[mBrushIndex] / 2);
506 corner.mdV[VY] -= (LAND_BRUSH_SIZE[mBrushIndex] / 2);
507 LLViewerRegion* region = NULL;
508 region = gWorldPointer->getRegionFromPosGlobal(corner);
509 if(region && !regions.checkData(region))
510 {
511 regions.addData(region);
512 }
513 corner.mdV[VY] += LAND_BRUSH_SIZE[mBrushIndex];
514 region = gWorldPointer->getRegionFromPosGlobal(corner);
515 if(region && !regions.checkData(region))
516 {
517 regions.addData(region);
518 }
519 corner.mdV[VX] += LAND_BRUSH_SIZE[mBrushIndex];
520 region = gWorldPointer->getRegionFromPosGlobal(corner);
521 if(region && !regions.checkData(region))
522 {
523 regions.addData(region);
524 }
525 corner.mdV[VY] -= LAND_BRUSH_SIZE[mBrushIndex];
526 region = gWorldPointer->getRegionFromPosGlobal(corner);
527 if(region && !regions.checkData(region))
528 {
529 regions.addData(region);
530 }
531}
532
533// static
534void LLToolBrushLand::onIdle( void* brush_tool )
535{
536 LLToolBrushLand* self = reinterpret_cast<LLToolBrushLand*>(brush_tool);
537
538 if( gToolMgr->getCurrentTool( gKeyboard->currentMask(TRUE) ) == self )
539 {
540 self->brush();
541 }
542 else
543 {
544 gIdleCallbacks.deleteFunction( &LLToolBrushLand::onIdle, self );
545 }
546}
547
548void LLToolBrushLand::onMouseCaptureLost()
549{
550 gIdleCallbacks.deleteFunction(&LLToolBrushLand::onIdle, this);
551}
552
553// static
554void LLToolBrushLand::undo()
555{
556 for(LLViewerRegion* regionp = mLastAffectedRegions.getFirstData();
557 regionp != NULL;
558 regionp = mLastAffectedRegions.getNextData())
559 {
560 gMessageSystem->newMessageFast(_PREHASH_UndoLand);
561 gMessageSystem->nextBlockFast(_PREHASH_AgentData);
562 gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID() );
563 gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
564 gMessageSystem->sendMessage(regionp->getHost());
565 }
566}
567
568// static
569void LLToolBrushLand::redo()
570{
571 for(LLViewerRegion* regionp = mLastAffectedRegions.getFirstData();
572 regionp != NULL;
573 regionp = mLastAffectedRegions.getNextData())
574 {
575 gMessageSystem->newMessageFast(_PREHASH_RedoLand);
576 gMessageSystem->nextBlockFast(_PREHASH_AgentData);
577 gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID() );
578 gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
579 gMessageSystem->sendMessage(regionp->getHost());
580 }
581}
582
583// static
584bool LLToolBrushLand::canTerraform(LLViewerRegion* regionp) const
585{
586 if (!regionp) return false;
587 if (regionp->canManageEstate()) return true;
588 return !(regionp->getRegionFlags() & REGION_FLAGS_BLOCK_TERRAFORM);
589}
590
591// static
592void LLToolBrushLand::alertNoTerraform(LLViewerRegion* regionp)
593{
594 if (!regionp) return;
595
596 LLStringBase<char>::format_map_t args;
597 args["[REGION]"] = regionp->getName();
598 gViewerWindow->alertXml("RegionNoTerraforming", args);
599
600}
601
602///============================================================================
603/// Local function definitions
604///============================================================================