InputGeom.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617
  1. //
  2. // Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
  3. //
  4. // This software is provided 'as-is', without any express or implied
  5. // warranty. In no event will the authors be held liable for any damages
  6. // arising from the use of this software.
  7. // Permission is granted to anyone to use this software for any purpose,
  8. // including commercial applications, and to alter it and redistribute it
  9. // freely, subject to the following restrictions:
  10. // 1. The origin of this software must not be misrepresented; you must not
  11. // claim that you wrote the original software. If you use this software
  12. // in a product, an acknowledgment in the product documentation would be
  13. // appreciated but is not required.
  14. // 2. Altered source versions must be plainly marked as such, and must not be
  15. // misrepresented as being the original software.
  16. // 3. This notice may not be removed or altered from any source distribution.
  17. //
  18. #define _USE_MATH_DEFINES
  19. #include <math.h>
  20. #include <stdio.h>
  21. #include <ctype.h>
  22. #include <string.h>
  23. #include <algorithm>
  24. #include "Recast.h"
  25. #include "InputGeom.h"
  26. #include "ChunkyTriMesh.h"
  27. #include "MeshLoaderObj.h"
  28. #include "DebugDraw.h"
  29. #include "RecastDebugDraw.h"
  30. #include "DetourNavMesh.h"
  31. #include "Sample.h"
  32. static bool intersectSegmentTriangle(const float* sp, const float* sq,
  33. const float* a, const float* b, const float* c,
  34. float &t)
  35. {
  36. float v, w;
  37. float ab[3], ac[3], qp[3], ap[3], norm[3], e[3];
  38. rcVsub(ab, b, a);
  39. rcVsub(ac, c, a);
  40. rcVsub(qp, sp, sq);
  41. // Compute triangle normal. Can be precalculated or cached if
  42. // intersecting multiple segments against the same triangle
  43. rcVcross(norm, ab, ac);
  44. // Compute denominator d. If d <= 0, segment is parallel to or points
  45. // away from triangle, so exit early
  46. float d = rcVdot(qp, norm);
  47. if (d <= 0.0f) return false;
  48. // Compute intersection t value of pq with plane of triangle. A ray
  49. // intersects iff 0 <= t. Segment intersects iff 0 <= t <= 1. Delay
  50. // dividing by d until intersection has been found to pierce triangle
  51. rcVsub(ap, sp, a);
  52. t = rcVdot(ap, norm);
  53. if (t < 0.0f) return false;
  54. if (t > d) return false; // For segment; exclude this code line for a ray test
  55. // Compute barycentric coordinate components and test if within bounds
  56. rcVcross(e, qp, ap);
  57. v = rcVdot(ac, e);
  58. if (v < 0.0f || v > d) return false;
  59. w = -rcVdot(ab, e);
  60. if (w < 0.0f || v + w > d) return false;
  61. // Segment/ray intersects triangle. Perform delayed division
  62. t /= d;
  63. return true;
  64. }
  65. static char* parseRow(char* buf, char* bufEnd, char* row, int len)
  66. {
  67. bool start = true;
  68. bool done = false;
  69. int n = 0;
  70. while (!done && buf < bufEnd)
  71. {
  72. char c = *buf;
  73. buf++;
  74. // multirow
  75. switch (c)
  76. {
  77. case '\n':
  78. if (start) break;
  79. done = true;
  80. break;
  81. case '\r':
  82. break;
  83. case '\t':
  84. case ' ':
  85. if (start) break;
  86. // else falls through
  87. default:
  88. start = false;
  89. row[n++] = c;
  90. if (n >= len-1)
  91. done = true;
  92. break;
  93. }
  94. }
  95. row[n] = '\0';
  96. return buf;
  97. }
  98. InputGeom::InputGeom() :
  99. m_chunkyMesh(0),
  100. m_mesh(0),
  101. m_hasBuildSettings(false),
  102. m_offMeshConCount(0),
  103. m_volumeCount(0)
  104. {
  105. }
  106. InputGeom::~InputGeom()
  107. {
  108. delete m_chunkyMesh;
  109. delete m_mesh;
  110. }
  111. bool InputGeom::loadMesh(rcContext* ctx, const std::string& filepath)
  112. {
  113. if (m_mesh)
  114. {
  115. delete m_chunkyMesh;
  116. m_chunkyMesh = 0;
  117. delete m_mesh;
  118. m_mesh = 0;
  119. }
  120. m_offMeshConCount = 0;
  121. m_volumeCount = 0;
  122. m_mesh = new rcMeshLoaderObj;
  123. if (!m_mesh)
  124. {
  125. ctx->log(RC_LOG_ERROR, "loadMesh: Out of memory 'm_mesh'.");
  126. return false;
  127. }
  128. if (!m_mesh->load(filepath))
  129. {
  130. ctx->log(RC_LOG_ERROR, "buildTiledNavigation: Could not load '%s'", filepath.c_str());
  131. return false;
  132. }
  133. rcCalcBounds(m_mesh->getVerts(), m_mesh->getVertCount(), m_meshBMin, m_meshBMax);
  134. m_chunkyMesh = new rcChunkyTriMesh;
  135. if (!m_chunkyMesh)
  136. {
  137. ctx->log(RC_LOG_ERROR, "buildTiledNavigation: Out of memory 'm_chunkyMesh'.");
  138. return false;
  139. }
  140. if (!rcCreateChunkyTriMesh(m_mesh->getVerts(), m_mesh->getTris(), m_mesh->getTriCount(), 256, m_chunkyMesh))
  141. {
  142. ctx->log(RC_LOG_ERROR, "buildTiledNavigation: Failed to build chunky mesh.");
  143. return false;
  144. }
  145. return true;
  146. }
  147. bool InputGeom::loadGeomSet(rcContext* ctx, const std::string& filepath)
  148. {
  149. char* buf = 0;
  150. FILE* fp = fopen(filepath.c_str(), "rb");
  151. if (!fp)
  152. {
  153. return false;
  154. }
  155. if (fseek(fp, 0, SEEK_END) != 0)
  156. {
  157. fclose(fp);
  158. return false;
  159. }
  160. long bufSize = ftell(fp);
  161. if (bufSize < 0)
  162. {
  163. fclose(fp);
  164. return false;
  165. }
  166. if (fseek(fp, 0, SEEK_SET) != 0)
  167. {
  168. fclose(fp);
  169. return false;
  170. }
  171. buf = new char[bufSize];
  172. if (!buf)
  173. {
  174. fclose(fp);
  175. return false;
  176. }
  177. size_t readLen = fread(buf, bufSize, 1, fp);
  178. fclose(fp);
  179. if (readLen != 1)
  180. {
  181. delete[] buf;
  182. return false;
  183. }
  184. m_offMeshConCount = 0;
  185. m_volumeCount = 0;
  186. delete m_mesh;
  187. m_mesh = 0;
  188. char* src = buf;
  189. char* srcEnd = buf + bufSize;
  190. char row[512];
  191. while (src < srcEnd)
  192. {
  193. // Parse one row
  194. row[0] = '\0';
  195. src = parseRow(src, srcEnd, row, sizeof(row)/sizeof(char));
  196. if (row[0] == 'f')
  197. {
  198. // File name.
  199. const char* name = row+1;
  200. // Skip white spaces
  201. while (*name && isspace(*name))
  202. name++;
  203. if (*name)
  204. {
  205. if (!loadMesh(ctx, name))
  206. {
  207. delete [] buf;
  208. return false;
  209. }
  210. }
  211. }
  212. else if (row[0] == 'c')
  213. {
  214. // Off-mesh connection
  215. if (m_offMeshConCount < MAX_OFFMESH_CONNECTIONS)
  216. {
  217. float* v = &m_offMeshConVerts[m_offMeshConCount*3*2];
  218. int bidir, area = 0, flags = 0;
  219. float rad;
  220. sscanf(row+1, "%f %f %f %f %f %f %f %d %d %d",
  221. &v[0], &v[1], &v[2], &v[3], &v[4], &v[5], &rad, &bidir, &area, &flags);
  222. m_offMeshConRads[m_offMeshConCount] = rad;
  223. m_offMeshConDirs[m_offMeshConCount] = (unsigned char)bidir;
  224. m_offMeshConAreas[m_offMeshConCount] = (unsigned char)area;
  225. m_offMeshConFlags[m_offMeshConCount] = (unsigned short)flags;
  226. m_offMeshConCount++;
  227. }
  228. }
  229. else if (row[0] == 'v')
  230. {
  231. // Convex volumes
  232. if (m_volumeCount < MAX_VOLUMES)
  233. {
  234. ConvexVolume* vol = &m_volumes[m_volumeCount++];
  235. sscanf(row+1, "%d %d %f %f", &vol->nverts, &vol->area, &vol->hmin, &vol->hmax);
  236. for (int i = 0; i < vol->nverts; ++i)
  237. {
  238. row[0] = '\0';
  239. src = parseRow(src, srcEnd, row, sizeof(row)/sizeof(char));
  240. sscanf(row, "%f %f %f", &vol->verts[i*3+0], &vol->verts[i*3+1], &vol->verts[i*3+2]);
  241. }
  242. }
  243. }
  244. else if (row[0] == 's')
  245. {
  246. // Settings
  247. m_hasBuildSettings = true;
  248. sscanf(row + 1, "%f %f %f %f %f %f %f %f %f %f %f %f %f %d %f %f %f %f %f %f %f",
  249. &m_buildSettings.cellSize,
  250. &m_buildSettings.cellHeight,
  251. &m_buildSettings.agentHeight,
  252. &m_buildSettings.agentRadius,
  253. &m_buildSettings.agentMaxClimb,
  254. &m_buildSettings.agentMaxSlope,
  255. &m_buildSettings.regionMinSize,
  256. &m_buildSettings.regionMergeSize,
  257. &m_buildSettings.edgeMaxLen,
  258. &m_buildSettings.edgeMaxError,
  259. &m_buildSettings.vertsPerPoly,
  260. &m_buildSettings.detailSampleDist,
  261. &m_buildSettings.detailSampleMaxError,
  262. &m_buildSettings.partitionType,
  263. &m_buildSettings.navMeshBMin[0],
  264. &m_buildSettings.navMeshBMin[1],
  265. &m_buildSettings.navMeshBMin[2],
  266. &m_buildSettings.navMeshBMax[0],
  267. &m_buildSettings.navMeshBMax[1],
  268. &m_buildSettings.navMeshBMax[2],
  269. &m_buildSettings.tileSize);
  270. }
  271. }
  272. delete [] buf;
  273. return true;
  274. }
  275. bool InputGeom::load(rcContext* ctx, const std::string& filepath)
  276. {
  277. size_t extensionPos = filepath.find_last_of('.');
  278. if (extensionPos == std::string::npos)
  279. return false;
  280. std::string extension = filepath.substr(extensionPos);
  281. std::transform(extension.begin(), extension.end(), extension.begin(), tolower);
  282. if (extension == ".gset")
  283. return loadGeomSet(ctx, filepath);
  284. if (extension == ".obj")
  285. return loadMesh(ctx, filepath);
  286. return false;
  287. }
  288. bool InputGeom::saveGeomSet(const BuildSettings* settings)
  289. {
  290. if (!m_mesh) return false;
  291. // Change extension
  292. std::string filepath = m_mesh->getFileName();
  293. size_t extPos = filepath.find_last_of('.');
  294. if (extPos != std::string::npos)
  295. filepath = filepath.substr(0, extPos);
  296. filepath += ".gset";
  297. FILE* fp = fopen(filepath.c_str(), "w");
  298. if (!fp) return false;
  299. // Store mesh filename.
  300. fprintf(fp, "f %s\n", m_mesh->getFileName().c_str());
  301. // Store settings if any
  302. if (settings)
  303. {
  304. fprintf(fp,
  305. "s %f %f %f %f %f %f %f %f %f %f %f %f %f %d %f %f %f %f %f %f %f\n",
  306. settings->cellSize,
  307. settings->cellHeight,
  308. settings->agentHeight,
  309. settings->agentRadius,
  310. settings->agentMaxClimb,
  311. settings->agentMaxSlope,
  312. settings->regionMinSize,
  313. settings->regionMergeSize,
  314. settings->edgeMaxLen,
  315. settings->edgeMaxError,
  316. settings->vertsPerPoly,
  317. settings->detailSampleDist,
  318. settings->detailSampleMaxError,
  319. settings->partitionType,
  320. settings->navMeshBMin[0],
  321. settings->navMeshBMin[1],
  322. settings->navMeshBMin[2],
  323. settings->navMeshBMax[0],
  324. settings->navMeshBMax[1],
  325. settings->navMeshBMax[2],
  326. settings->tileSize);
  327. }
  328. // Store off-mesh links.
  329. for (int i = 0; i < m_offMeshConCount; ++i)
  330. {
  331. const float* v = &m_offMeshConVerts[i*3*2];
  332. const float rad = m_offMeshConRads[i];
  333. const int bidir = m_offMeshConDirs[i];
  334. const int area = m_offMeshConAreas[i];
  335. const int flags = m_offMeshConFlags[i];
  336. fprintf(fp, "c %f %f %f %f %f %f %f %d %d %d\n",
  337. v[0], v[1], v[2], v[3], v[4], v[5], rad, bidir, area, flags);
  338. }
  339. // Convex volumes
  340. for (int i = 0; i < m_volumeCount; ++i)
  341. {
  342. ConvexVolume* vol = &m_volumes[i];
  343. fprintf(fp, "v %d %d %f %f\n", vol->nverts, vol->area, vol->hmin, vol->hmax);
  344. for (int j = 0; j < vol->nverts; ++j)
  345. fprintf(fp, "%f %f %f\n", vol->verts[j*3+0], vol->verts[j*3+1], vol->verts[j*3+2]);
  346. }
  347. fclose(fp);
  348. return true;
  349. }
  350. static bool isectSegAABB(const float* sp, const float* sq,
  351. const float* amin, const float* amax,
  352. float& tmin, float& tmax)
  353. {
  354. static const float EPS = 1e-6f;
  355. float d[3];
  356. d[0] = sq[0] - sp[0];
  357. d[1] = sq[1] - sp[1];
  358. d[2] = sq[2] - sp[2];
  359. tmin = 0.0;
  360. tmax = 1.0f;
  361. for (int i = 0; i < 3; i++)
  362. {
  363. if (fabsf(d[i]) < EPS)
  364. {
  365. if (sp[i] < amin[i] || sp[i] > amax[i])
  366. return false;
  367. }
  368. else
  369. {
  370. const float ood = 1.0f / d[i];
  371. float t1 = (amin[i] - sp[i]) * ood;
  372. float t2 = (amax[i] - sp[i]) * ood;
  373. if (t1 > t2) { float tmp = t1; t1 = t2; t2 = tmp; }
  374. if (t1 > tmin) tmin = t1;
  375. if (t2 < tmax) tmax = t2;
  376. if (tmin > tmax) return false;
  377. }
  378. }
  379. return true;
  380. }
  381. bool InputGeom::raycastMesh(float* src, float* dst, float& tmin)
  382. {
  383. float dir[3];
  384. rcVsub(dir, dst, src);
  385. // Prune hit ray.
  386. float btmin, btmax;
  387. if (!isectSegAABB(src, dst, m_meshBMin, m_meshBMax, btmin, btmax))
  388. return false;
  389. float p[2], q[2];
  390. p[0] = src[0] + (dst[0]-src[0])*btmin;
  391. p[1] = src[2] + (dst[2]-src[2])*btmin;
  392. q[0] = src[0] + (dst[0]-src[0])*btmax;
  393. q[1] = src[2] + (dst[2]-src[2])*btmax;
  394. int cid[512];
  395. const int ncid = rcGetChunksOverlappingSegment(m_chunkyMesh, p, q, cid, 512);
  396. if (!ncid)
  397. return false;
  398. tmin = 1.0f;
  399. bool hit = false;
  400. const float* verts = m_mesh->getVerts();
  401. for (int i = 0; i < ncid; ++i)
  402. {
  403. const rcChunkyTriMeshNode& node = m_chunkyMesh->nodes[cid[i]];
  404. const int* tris = &m_chunkyMesh->tris[node.i*3];
  405. const int ntris = node.n;
  406. for (int j = 0; j < ntris*3; j += 3)
  407. {
  408. float t = 1;
  409. if (intersectSegmentTriangle(src, dst,
  410. &verts[tris[j]*3],
  411. &verts[tris[j+1]*3],
  412. &verts[tris[j+2]*3], t))
  413. {
  414. if (t < tmin)
  415. tmin = t;
  416. hit = true;
  417. }
  418. }
  419. }
  420. return hit;
  421. }
  422. void InputGeom::addOffMeshConnection(const float* spos, const float* epos, const float rad,
  423. unsigned char bidir, unsigned char area, unsigned short flags)
  424. {
  425. if (m_offMeshConCount >= MAX_OFFMESH_CONNECTIONS) return;
  426. float* v = &m_offMeshConVerts[m_offMeshConCount*3*2];
  427. m_offMeshConRads[m_offMeshConCount] = rad;
  428. m_offMeshConDirs[m_offMeshConCount] = bidir;
  429. m_offMeshConAreas[m_offMeshConCount] = area;
  430. m_offMeshConFlags[m_offMeshConCount] = flags;
  431. m_offMeshConId[m_offMeshConCount] = 1000 + m_offMeshConCount;
  432. rcVcopy(&v[0], spos);
  433. rcVcopy(&v[3], epos);
  434. m_offMeshConCount++;
  435. }
  436. void InputGeom::deleteOffMeshConnection(int i)
  437. {
  438. m_offMeshConCount--;
  439. float* src = &m_offMeshConVerts[m_offMeshConCount*3*2];
  440. float* dst = &m_offMeshConVerts[i*3*2];
  441. rcVcopy(&dst[0], &src[0]);
  442. rcVcopy(&dst[3], &src[3]);
  443. m_offMeshConRads[i] = m_offMeshConRads[m_offMeshConCount];
  444. m_offMeshConDirs[i] = m_offMeshConDirs[m_offMeshConCount];
  445. m_offMeshConAreas[i] = m_offMeshConAreas[m_offMeshConCount];
  446. m_offMeshConFlags[i] = m_offMeshConFlags[m_offMeshConCount];
  447. }
  448. void InputGeom::drawOffMeshConnections(duDebugDraw* dd, bool hilight)
  449. {
  450. unsigned int conColor = duRGBA(192,0,128,192);
  451. unsigned int baseColor = duRGBA(0,0,0,64);
  452. dd->depthMask(false);
  453. dd->begin(DU_DRAW_LINES, 2.0f);
  454. for (int i = 0; i < m_offMeshConCount; ++i)
  455. {
  456. float* v = &m_offMeshConVerts[i*3*2];
  457. dd->vertex(v[0],v[1],v[2], baseColor);
  458. dd->vertex(v[0],v[1]+0.2f,v[2], baseColor);
  459. dd->vertex(v[3],v[4],v[5], baseColor);
  460. dd->vertex(v[3],v[4]+0.2f,v[5], baseColor);
  461. duAppendCircle(dd, v[0],v[1]+0.1f,v[2], m_offMeshConRads[i], baseColor);
  462. duAppendCircle(dd, v[3],v[4]+0.1f,v[5], m_offMeshConRads[i], baseColor);
  463. if (hilight)
  464. {
  465. duAppendArc(dd, v[0],v[1],v[2], v[3],v[4],v[5], 0.25f,
  466. (m_offMeshConDirs[i]&1) ? 0.6f : 0.0f, 0.6f, conColor);
  467. }
  468. }
  469. dd->end();
  470. dd->depthMask(true);
  471. }
  472. void InputGeom::addConvexVolume(const float* verts, const int nverts,
  473. const float minh, const float maxh, unsigned char area)
  474. {
  475. if (m_volumeCount >= MAX_VOLUMES) return;
  476. ConvexVolume* vol = &m_volumes[m_volumeCount++];
  477. memset(vol, 0, sizeof(ConvexVolume));
  478. memcpy(vol->verts, verts, sizeof(float)*3*nverts);
  479. vol->hmin = minh;
  480. vol->hmax = maxh;
  481. vol->nverts = nverts;
  482. vol->area = area;
  483. }
  484. void InputGeom::deleteConvexVolume(int i)
  485. {
  486. m_volumeCount--;
  487. m_volumes[i] = m_volumes[m_volumeCount];
  488. }
  489. void InputGeom::drawConvexVolumes(struct duDebugDraw* dd, bool /*hilight*/)
  490. {
  491. dd->depthMask(false);
  492. dd->begin(DU_DRAW_TRIS);
  493. for (int i = 0; i < m_volumeCount; ++i)
  494. {
  495. const ConvexVolume* vol = &m_volumes[i];
  496. unsigned int col = duTransCol(dd->areaToCol(vol->area), 32);
  497. for (int j = 0, k = vol->nverts-1; j < vol->nverts; k = j++)
  498. {
  499. const float* va = &vol->verts[k*3];
  500. const float* vb = &vol->verts[j*3];
  501. dd->vertex(vol->verts[0],vol->hmax,vol->verts[2], col);
  502. dd->vertex(vb[0],vol->hmax,vb[2], col);
  503. dd->vertex(va[0],vol->hmax,va[2], col);
  504. dd->vertex(va[0],vol->hmin,va[2], duDarkenCol(col));
  505. dd->vertex(va[0],vol->hmax,va[2], col);
  506. dd->vertex(vb[0],vol->hmax,vb[2], col);
  507. dd->vertex(va[0],vol->hmin,va[2], duDarkenCol(col));
  508. dd->vertex(vb[0],vol->hmax,vb[2], col);
  509. dd->vertex(vb[0],vol->hmin,vb[2], duDarkenCol(col));
  510. }
  511. }
  512. dd->end();
  513. dd->begin(DU_DRAW_LINES, 2.0f);
  514. for (int i = 0; i < m_volumeCount; ++i)
  515. {
  516. const ConvexVolume* vol = &m_volumes[i];
  517. unsigned int col = duTransCol(dd->areaToCol(vol->area), 220);
  518. for (int j = 0, k = vol->nverts-1; j < vol->nverts; k = j++)
  519. {
  520. const float* va = &vol->verts[k*3];
  521. const float* vb = &vol->verts[j*3];
  522. dd->vertex(va[0],vol->hmin,va[2], duDarkenCol(col));
  523. dd->vertex(vb[0],vol->hmin,vb[2], duDarkenCol(col));
  524. dd->vertex(va[0],vol->hmax,va[2], col);
  525. dd->vertex(vb[0],vol->hmax,vb[2], col);
  526. dd->vertex(va[0],vol->hmin,va[2], duDarkenCol(col));
  527. dd->vertex(va[0],vol->hmax,va[2], col);
  528. }
  529. }
  530. dd->end();
  531. dd->begin(DU_DRAW_POINTS, 3.0f);
  532. for (int i = 0; i < m_volumeCount; ++i)
  533. {
  534. const ConvexVolume* vol = &m_volumes[i];
  535. unsigned int col = duDarkenCol(duTransCol(dd->areaToCol(vol->area), 220));
  536. for (int j = 0; j < vol->nverts; ++j)
  537. {
  538. dd->vertex(vol->verts[j*3+0],vol->verts[j*3+1]+0.1f,vol->verts[j*3+2], col);
  539. dd->vertex(vol->verts[j*3+0],vol->hmin,vol->verts[j*3+2], col);
  540. dd->vertex(vol->verts[j*3+0],vol->hmax,vol->verts[j*3+2], col);
  541. }
  542. }
  543. dd->end();
  544. dd->depthMask(true);
  545. }