2 * Copyright (C) 2005-2013 MaNGOS <http://getmangos.com/>
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 #include "WorldModel.h"
20 #include "VMapDefinitions.h"
26 template<> struct BoundsTrait
<VMAP::GroupModel
>
28 static void getBounds(const VMAP::GroupModel
& obj
, G3D::AABox
& out
) { out
= obj
.GetBound(); }
33 bool IntersectTriangle(const MeshTriangle
& tri
, std::vector
<Vector3
>::const_iterator points
, const G3D::Ray
& ray
, float& distance
)
35 static const float EPS
= 1e-5f
;
37 // See RTR2 ch. 13.7 for the algorithm.
39 const Vector3 e1
= points
[tri
.idx1
] - points
[tri
.idx0
];
40 const Vector3 e2
= points
[tri
.idx2
] - points
[tri
.idx0
];
41 const Vector3
p(ray
.direction().cross(e2
));
42 const float a
= e1
.dot(p
);
46 // Determinant is ill-conditioned; abort early
50 const float f
= 1.0f
/ a
;
51 const Vector3
s(ray
.origin() - points
[tri
.idx0
]);
52 const float u
= f
* s
.dot(p
);
54 if ((u
< 0.0f
) || (u
> 1.0f
))
56 // We hit the plane of the m_geometry, but outside the m_geometry
60 const Vector3
q(s
.cross(e1
));
61 const float v
= f
* ray
.direction().dot(q
);
63 if ((v
< 0.0f
) || ((u
+ v
) > 1.0f
))
65 // We hit the plane of the triangle, but outside the triangle
69 const float t
= f
* e2
.dot(q
);
71 if ((t
> 0.0f
) && (t
< distance
))
73 // This is a new hit, closer than the previous one
76 /* baryCoord[0] = 1.0 - u - v;
82 // This hit is after the previous hit, so ignore it
89 TriBoundFunc(std::vector
<Vector3
>& vert
): vertices(vert
.begin()) {}
90 void operator()(const MeshTriangle
& tri
, G3D::AABox
& out
) const
92 G3D::Vector3 lo
= vertices
[tri
.idx0
];
95 lo
= (lo
.min(vertices
[tri
.idx1
])).min(vertices
[tri
.idx2
]);
96 hi
= (hi
.max(vertices
[tri
.idx1
])).max(vertices
[tri
.idx2
]);
98 out
= G3D::AABox(lo
, hi
);
101 const std::vector
<Vector3
>::const_iterator vertices
;
104 // ===================== WmoLiquid ==================================
106 WmoLiquid::WmoLiquid(uint32 width
, uint32 height
, const Vector3
& corner
, uint32 type
):
107 iTilesX(width
), iTilesY(height
), iCorner(corner
), iType(type
)
109 iHeight
= new float[(width
+ 1) * (height
+ 1)];
110 iFlags
= new uint8
[width
* height
];
113 WmoLiquid::WmoLiquid(const WmoLiquid
& other
): iHeight(0), iFlags(0)
115 *this = other
; // use assignment operator...
118 WmoLiquid::~WmoLiquid()
124 WmoLiquid
& WmoLiquid::operator=(const WmoLiquid
& other
)
128 iTilesX
= other
.iTilesX
;
129 iTilesY
= other
.iTilesY
;
130 iCorner
= other
.iCorner
;
136 iHeight
= new float[(iTilesX
+ 1) * (iTilesY
+ 1)];
137 memcpy(iHeight
, other
.iHeight
, (iTilesX
+ 1) * (iTilesY
+ 1)*sizeof(float));
143 iFlags
= new uint8
[iTilesX
* iTilesY
];
144 memcpy(iFlags
, other
.iFlags
, iTilesX
* iTilesY
);
151 bool WmoLiquid::GetLiquidHeight(const Vector3
& pos
, float& liqHeight
) const
153 float tx_f
= (pos
.x
- iCorner
.x
) / LIQUID_TILE_SIZE
;
154 uint32 tx
= uint32(tx_f
);
155 if (tx_f
< 0.0f
|| tx
>= iTilesX
)
157 float ty_f
= (pos
.y
- iCorner
.y
) / LIQUID_TILE_SIZE
;
158 uint32 ty
= uint32(ty_f
);
159 if (ty_f
< 0.0f
|| ty
>= iTilesY
)
162 // check if tile shall be used for liquid level
163 // checking for 0x08 *might* be enough, but disabled tiles always are 0x?F:
164 if ((iFlags
[tx
+ ty
* iTilesX
] & 0x0F) == 0x0F)
167 // (dx, dy) coordinates inside tile, in [0,1]^2
168 float dx
= tx_f
- (float)tx
;
169 float dy
= ty_f
- (float)ty
;
171 /* Tesselate tile to two triangles (not sure if client does it exactly like this)
183 const uint32 rowOffset
= iTilesX
+ 1;
184 if (dx
> dy
) // case (a)
186 float sx
= iHeight
[tx
+ 1 + ty
* rowOffset
] - iHeight
[tx
+ ty
* rowOffset
];
187 float sy
= iHeight
[tx
+ 1 + (ty
+ 1) * rowOffset
] - iHeight
[tx
+ 1 + ty
* rowOffset
];
188 liqHeight
= iHeight
[tx
+ ty
* rowOffset
] + dx
* sx
+ dy
* sy
;
192 float sx
= iHeight
[tx
+ 1 + (ty
+ 1) * rowOffset
] - iHeight
[tx
+ (ty
+ 1) * rowOffset
];
193 float sy
= iHeight
[tx
+ (ty
+ 1) * rowOffset
] - iHeight
[tx
+ ty
* rowOffset
];
194 liqHeight
= iHeight
[tx
+ ty
* rowOffset
] + dx
* sx
+ dy
* sy
;
199 uint32
WmoLiquid::GetFileSize()
201 return 2 * sizeof(uint32
) +
203 (iTilesX
+ 1) * (iTilesY
+ 1) * sizeof(float) +
207 bool WmoLiquid::writeToFile(FILE* wf
)
210 if (result
&& fwrite(&iTilesX
, sizeof(uint32
), 1, wf
) != 1) result
= false;
211 if (result
&& fwrite(&iTilesY
, sizeof(uint32
), 1, wf
) != 1) result
= false;
212 if (result
&& fwrite(&iCorner
, sizeof(Vector3
), 1, wf
) != 1) result
= false;
213 if (result
&& fwrite(&iType
, sizeof(uint32
), 1, wf
) != 1) result
= false;
214 uint32 size
= (iTilesX
+ 1) * (iTilesY
+ 1);
215 if (result
&& fwrite(iHeight
, sizeof(float), size
, wf
) != size
) result
= false;
216 size
= iTilesX
* iTilesY
;
217 if (result
&& fwrite(iFlags
, sizeof(uint8
), size
, wf
) != size
) result
= false;
221 bool WmoLiquid::readFromFile(FILE* rf
, WmoLiquid
*& out
)
224 WmoLiquid
* liquid
= new WmoLiquid();
225 if (result
&& fread(&liquid
->iTilesX
, sizeof(uint32
), 1, rf
) != 1) result
= false;
226 if (result
&& fread(&liquid
->iTilesY
, sizeof(uint32
), 1, rf
) != 1) result
= false;
227 if (result
&& fread(&liquid
->iCorner
, sizeof(Vector3
), 1, rf
) != 1) result
= false;
228 if (result
&& fread(&liquid
->iType
, sizeof(uint32
), 1, rf
) != 1) result
= false;
229 uint32 size
= (liquid
->iTilesX
+ 1) * (liquid
->iTilesY
+ 1);
230 liquid
->iHeight
= new float[size
];
231 if (result
&& fread(liquid
->iHeight
, sizeof(float), size
, rf
) != size
) result
= false;
232 size
= liquid
->iTilesX
* liquid
->iTilesY
;
233 liquid
->iFlags
= new uint8
[size
];
234 if (result
&& fread(liquid
->iFlags
, sizeof(uint8
), size
, rf
) != size
) result
= false;
241 // ===================== GroupModel ==================================
243 GroupModel::GroupModel(const GroupModel
& other
):
244 iBound(other
.iBound
), iMogpFlags(other
.iMogpFlags
), iGroupWMOID(other
.iGroupWMOID
),
245 vertices(other
.vertices
), triangles(other
.triangles
), meshTree(other
.meshTree
), iLiquid(0)
248 iLiquid
= new WmoLiquid(*other
.iLiquid
);
251 void GroupModel::setMeshData(std::vector
<Vector3
>& vert
, std::vector
<MeshTriangle
>& tri
)
255 TriBoundFunc
bFunc(vertices
);
256 meshTree
.build(triangles
, bFunc
);
259 bool GroupModel::writeToFile(FILE* wf
)
262 uint32 chunkSize
, count
;
264 if (result
&& fwrite(&iBound
, sizeof(G3D::AABox
), 1, wf
) != 1) result
= false;
265 if (result
&& fwrite(&iMogpFlags
, sizeof(uint32
), 1, wf
) != 1) result
= false;
266 if (result
&& fwrite(&iGroupWMOID
, sizeof(uint32
), 1, wf
) != 1) result
= false;
269 if (result
&& fwrite("VERT", 1, 4, wf
) != 4) result
= false;
270 count
= vertices
.size();
271 chunkSize
= sizeof(uint32
) + sizeof(Vector3
) * count
;
272 if (result
&& fwrite(&chunkSize
, sizeof(uint32
), 1, wf
) != 1) result
= false;
273 if (result
&& fwrite(&count
, sizeof(uint32
), 1, wf
) != 1) result
= false;
274 if (!count
) // models without (collision) geometry end here, unsure if they are useful
276 if (result
&& fwrite(&vertices
[0], sizeof(Vector3
), count
, wf
) != count
) result
= false;
278 // write triangle mesh
279 if (result
&& fwrite("TRIM", 1, 4, wf
) != 4) result
= false;
280 count
= triangles
.size();
281 chunkSize
= sizeof(uint32
) + sizeof(MeshTriangle
) * count
;
282 if (result
&& fwrite(&chunkSize
, sizeof(uint32
), 1, wf
) != 1) result
= false;
283 if (result
&& fwrite(&count
, sizeof(uint32
), 1, wf
) != 1) result
= false;
284 if (result
&& fwrite(&triangles
[0], sizeof(MeshTriangle
), count
, wf
) != count
) result
= false;
287 if (result
&& fwrite("MBIH", 1, 4, wf
) != 4) result
= false;
288 if (result
) result
= meshTree
.writeToFile(wf
);
291 if (result
&& fwrite("LIQU", 1, 4, wf
) != 4) result
= false;
295 if (result
&& fwrite(&chunkSize
, sizeof(uint32
), 1, wf
) != 1) result
= false;
298 chunkSize
= iLiquid
->GetFileSize();
299 if (result
&& fwrite(&chunkSize
, sizeof(uint32
), 1, wf
) != 1) result
= false;
300 if (result
) result
= iLiquid
->writeToFile(wf
);
305 bool GroupModel::readFromFile(FILE* rf
)
309 uint32 chunkSize
, count
;
315 if (result
&& fread(&iBound
, sizeof(G3D::AABox
), 1, rf
) != 1) result
= false;
316 if (result
&& fread(&iMogpFlags
, sizeof(uint32
), 1, rf
) != 1) result
= false;
317 if (result
&& fread(&iGroupWMOID
, sizeof(uint32
), 1, rf
) != 1) result
= false;
320 if (result
&& !readChunk(rf
, chunk
, "VERT", 4)) result
= false;
321 if (result
&& fread(&chunkSize
, sizeof(uint32
), 1, rf
) != 1) result
= false;
322 if (result
&& fread(&count
, sizeof(uint32
), 1, rf
) != 1) result
= false;
323 if (!count
) // models without (collision) geometry end here, unsure if they are useful
325 if (result
) vertices
.resize(count
);
326 if (result
&& fread(&vertices
[0], sizeof(Vector3
), count
, rf
) != count
) result
= false;
328 // read triangle mesh
329 if (result
&& !readChunk(rf
, chunk
, "TRIM", 4)) result
= false;
330 if (result
&& fread(&chunkSize
, sizeof(uint32
), 1, rf
) != 1) result
= false;
331 if (result
&& fread(&count
, sizeof(uint32
), 1, rf
) != 1) result
= false;
332 if (result
) triangles
.resize(count
);
333 if (result
&& fread(&triangles
[0], sizeof(MeshTriangle
), count
, rf
) != count
) result
= false;
336 if (result
&& !readChunk(rf
, chunk
, "MBIH", 4)) result
= false;
337 if (result
) result
= meshTree
.readFromFile(rf
);
340 if (result
&& !readChunk(rf
, chunk
, "LIQU", 4)) result
= false;
341 if (result
&& fread(&chunkSize
, sizeof(uint32
), 1, rf
) != 1) result
= false;
342 if (result
&& chunkSize
> 0)
343 result
= WmoLiquid::readFromFile(rf
, iLiquid
);
347 struct GModelRayCallback
349 GModelRayCallback(const std::vector
<MeshTriangle
>& tris
, const std::vector
<Vector3
>& vert
):
350 vertices(vert
.begin()), triangles(tris
.begin()), hit(false) {}
351 bool operator()(const G3D::Ray
& ray
, uint32 entry
, float& distance
, bool pStopAtFirstHit
)
353 bool result
= IntersectTriangle(triangles
[entry
], vertices
, ray
, distance
);
354 if (result
) hit
= true;
357 std::vector
<Vector3
>::const_iterator vertices
;
358 std::vector
<MeshTriangle
>::const_iterator triangles
;
362 bool GroupModel::IntersectRay(const G3D::Ray
& ray
, float& distance
, bool stopAtFirstHit
) const
364 if (!triangles
.size())
366 GModelRayCallback
callback(triangles
, vertices
);
367 meshTree
.intersectRay(ray
, callback
, distance
, stopAtFirstHit
);
371 bool GroupModel::IsInsideObject(const Vector3
& pos
, const Vector3
& down
, float& z_dist
) const
373 if (!triangles
.size() || !iBound
.contains(pos
))
375 GModelRayCallback
callback(triangles
, vertices
);
376 Vector3 rPos
= pos
- 0.1f
* down
;
377 float dist
= G3D::inf();
378 G3D::Ray
ray(rPos
, down
);
379 bool hit
= IntersectRay(ray
, dist
, false);
381 z_dist
= dist
- 0.1f
;
385 bool GroupModel::GetLiquidLevel(const Vector3
& pos
, float& liqHeight
) const
388 return iLiquid
->GetLiquidHeight(pos
, liqHeight
);
392 uint32
GroupModel::GetLiquidType() const
394 // convert to type mask, matching MAP_LIQUID_TYPE_* defines in Map.h
396 return (1 << iLiquid
->GetType());
400 // ===================== WorldModel ==================================
402 void WorldModel::setGroupModels(std::vector
<GroupModel
>& models
)
404 groupModels
.swap(models
);
405 groupTree
.build(groupModels
, BoundsTrait
<GroupModel
>::getBounds
, 1);
408 struct WModelRayCallBack
410 WModelRayCallBack(const std::vector
<GroupModel
>& mod
): models(mod
.begin()), hit(false) {}
411 bool operator()(const G3D::Ray
& ray
, uint32 entry
, float& distance
, bool pStopAtFirstHit
)
413 bool result
= models
[entry
].IntersectRay(ray
, distance
, pStopAtFirstHit
);
414 if (result
) hit
= true;
417 std::vector
<GroupModel
>::const_iterator models
;
421 bool WorldModel::IntersectRay(const G3D::Ray
& ray
, float& distance
, bool stopAtFirstHit
) const
423 // small M2 workaround, maybe better make separate class with virtual intersection funcs
424 // in any case, there's no need to use a bound tree if we only have one submodel
425 if (groupModels
.size() == 1)
426 return groupModels
[0].IntersectRay(ray
, distance
, stopAtFirstHit
);
428 WModelRayCallBack
isc(groupModels
);
429 groupTree
.intersectRay(ray
, isc
, distance
, stopAtFirstHit
);
433 class WModelAreaCallback
436 WModelAreaCallback(const std::vector
<GroupModel
>& vals
, const Vector3
& down
):
437 prims(vals
.begin()), hit(vals
.end()), minVol(G3D::inf()), zDist(G3D::inf()), zVec(down
) {}
438 std::vector
<GroupModel
>::const_iterator prims
;
439 std::vector
<GroupModel
>::const_iterator hit
;
443 void operator()(const Vector3
& point
, uint32 entry
)
446 // float pVol = prims[entry].GetBound().volume();
449 /* if (prims[entry].iBound.contains(point)) */
450 if (prims
[entry
].IsInsideObject(point
, zVec
, group_Z
))
453 // hit = prims + entry;
460 const GroupModel
& gm
= prims
[entry
];
461 printf("%10u %8X %7.3f,%7.3f,%7.3f | %7.3f,%7.3f,%7.3f | z=%f, p_z=%f\n", gm
.GetWmoID(), gm
.GetMogpFlags(),
462 gm
.GetBound().low().x
, gm
.GetBound().low().y
, gm
.GetBound().low().z
,
463 gm
.GetBound().high().x
, gm
.GetBound().high().y
, gm
.GetBound().high().z
, group_Z
, point
.z
);
467 // std::cout << "trying to intersect '" << prims[entry].name << "'\n";
471 bool WorldModel::IntersectPoint(const G3D::Vector3
& p
, const G3D::Vector3
& down
, float& dist
, AreaInfo
& info
) const
473 if (!groupModels
.size())
475 WModelAreaCallback
callback(groupModels
, down
);
476 groupTree
.intersectPoint(p
, callback
);
477 if (callback
.hit
!= groupModels
.end())
479 info
.rootId
= RootWMOID
;
480 info
.groupId
= callback
.hit
->GetWmoID();
481 info
.flags
= callback
.hit
->GetMogpFlags();
483 dist
= callback
.zDist
;
489 bool WorldModel::GetLocationInfo(const G3D::Vector3
& p
, const G3D::Vector3
& down
, float& dist
, LocationInfo
& info
) const
491 if (!groupModels
.size())
493 WModelAreaCallback
callback(groupModels
, down
);
494 groupTree
.intersectPoint(p
, callback
);
495 if (callback
.hit
!= groupModels
.end())
497 info
.hitModel
= &(*callback
.hit
);
498 dist
= callback
.zDist
;
504 bool WorldModel::writeFile(const std::string
& filename
)
506 FILE* wf
= fopen(filename
.c_str(), "wb");
510 uint32 chunkSize
, count
;
511 bool result
= fwrite(VMAP_MAGIC
, 1, 8, wf
) == 8;
512 if (result
&& fwrite("WMOD", 1, 4, wf
) != 4) result
= false;
513 chunkSize
= sizeof(uint32
) + sizeof(uint32
);
514 if (result
&& fwrite(&chunkSize
, sizeof(uint32
), 1, wf
) != 1) result
= false;
515 if (result
&& fwrite(&RootWMOID
, sizeof(uint32
), 1, wf
) != 1) result
= false;
517 // write group models
518 count
= groupModels
.size();
521 if (result
&& fwrite("GMOD", 1, 4, wf
) != 4) result
= false;
522 // chunkSize = sizeof(uint32)+ sizeof(GroupModel)*count;
523 // if (result && fwrite(&chunkSize, sizeof(uint32), 1, wf) != 1) result = false;
524 if (result
&& fwrite(&count
, sizeof(uint32
), 1, wf
) != 1) result
= false;
525 for (uint32 i
= 0; i
< groupModels
.size() && result
; ++i
)
526 result
= groupModels
[i
].writeToFile(wf
);
529 if (result
&& fwrite("GBIH", 1, 4, wf
) != 4) result
= false;
530 if (result
) result
= groupTree
.writeToFile(wf
);
537 bool WorldModel::readFile(const std::string
& filename
)
539 FILE* rf
= fopen(filename
.c_str(), "rb");
544 uint32 chunkSize
, count
;
545 char chunk
[8]; // Ignore the added magic header
546 if (!readChunk(rf
, chunk
, VMAP_MAGIC
, 8)) result
= false;
548 if (result
&& !readChunk(rf
, chunk
, "WMOD", 4)) result
= false;
549 if (result
&& fread(&chunkSize
, sizeof(uint32
), 1, rf
) != 1) result
= false;
550 if (result
&& fread(&RootWMOID
, sizeof(uint32
), 1, rf
) != 1) result
= false;
553 if (result
&& readChunk(rf
, chunk
, "GMOD", 4))
555 // if (fread(&chunkSize, sizeof(uint32), 1, rf) != 1) result = false;
557 if (result
&& fread(&count
, sizeof(uint32
), 1, rf
) != 1) result
= false;
558 if (result
) groupModels
.resize(count
);
559 // if (result && fread(&groupModels[0], sizeof(GroupModel), count, rf) != count) result = false;
560 for (uint32 i
= 0; i
< count
&& result
; ++i
)
561 result
= groupModels
[i
].readFromFile(rf
);
564 if (result
&& !readChunk(rf
, chunk
, "GBIH", 4)) result
= false;
565 if (result
) result
= groupTree
.readFromFile(rf
);