diff --git a/changes.txt b/changes.txt index d28b962c..9e5bcc86 100644 --- a/changes.txt +++ b/changes.txt @@ -289,6 +289,16 @@ The following names can be queried for the given types: ----------------------------- Changes in 1.7.3 (??.??.2011) + - editbox no longer moves text into next line when it fails wrapping creating senseless empty lines which mess up scrolling. + + - Fix crash in editbox when scrolling up with empty first lines caused by textwrapping. + + - triangle3d::isPointInside can now work with larger integers, old version failed already with values in the 3-digit range. It got also faster. (thx @ Eigen for report + testcase and REDDemon for patch proposal). + + - Fix focus problem when removing an unfocused modal dialog reported by Reiko here: http://irrlicht.sourceforge.net/forum/viewtopic.php?f=7&t=44358 + + - Add integer template specialization for vector3d::getSphericalCoordinateAngles which rounds angles to nearest integer now. + - Recalculate FrameRect and ScrollPos in CGUIEditBox when AbsoluteRect gets changed (thx @ serengeor for report + testcase) - Fix 'k' in bigfont.png (thx @ Scrappi for reporting) diff --git a/include/ISceneManager.h b/include/ISceneManager.h index f4b700c3..13b2125a 100644 --- a/include/ISceneManager.h +++ b/include/ISceneManager.h @@ -90,7 +90,7 @@ namespace scene //! Transparent effect scene nodes, drawn after Transparent nodes. They are sorted from back to front and drawn in that order. ESNRP_TRANSPARENT_EFFECT =32, - //! Drawn after the transparent nodes, the time for drawing shadow volumes + //! Drawn after the solid nodes, before the transparent nodes, the time for drawing shadow volumes ESNRP_SHADOW =64 }; diff --git a/include/triangle3d.h b/include/triangle3d.h index 289ebd67..e80b53d2 100644 --- a/include/triangle3d.h +++ b/include/triangle3d.h @@ -81,21 +81,39 @@ namespace core return d2 < d3 ? rbc : rca; } - //! Check if a point is inside the triangle + //! Check if a point is inside the triangle (border-points count also as inside) /** \param p Point to test. Assumes that this point is already on the plane of the triangle. \return True if the point is inside the triangle, otherwise false. */ bool isPointInside(const vector3d& p) const { - return (isOnSameSide(p, pointA, pointB, pointC) && - isOnSameSide(p, pointB, pointA, pointC) && - isOnSameSide(p, pointC, pointA, pointB)); + const vector3d a = pointC - pointA; + const vector3d b = pointB - pointA; + const vector3d c = p - pointA; + + const f64 dotAA = a.dotProduct( a); + const f64 dotAB = a.dotProduct( b); + const f64 dotAC = a.dotProduct( c); + const f64 dotBB = b.dotProduct( b); + const f64 dotBC = b.dotProduct( c); + + // get coordinates in barycentric coordinate system + const f64 invDenom = 1/(dotAA * dotBB - dotAB * dotAB); + const f64 u = (dotBB * dotAC - dotAB * dotBC) * invDenom; + const f64 v = (dotAA * dotBC - dotAB * dotAC ) * invDenom; + + // We count border-points as inside to keep downward compatibility. + // That's why we use >= and <= instead of > and < as more commonly seen on the web. + return (u >= 0) && (v >= 0) && (u + v <= 1); } //! Check if a point is inside the triangle. /** This method is an implementation of the example used in a paper by Kasper Fauerby original written by Keidy from Mr-Gamemaker. + This was once faster than an old isPointInside implementation, but the + current isPointInside is usualy as fast, sometimes even faster. + Border-points in isPointInsideFast are not defined, some are inside and some outside. \param p Point to test. Assumes that this point is already on the plane of the triangle. \return True if point is inside the triangle, otherwise false. */ diff --git a/include/vector2d.h b/include/vector2d.h index f0965833..8bd660c2 100644 --- a/include/vector2d.h +++ b/include/vector2d.h @@ -246,7 +246,7 @@ public: if (tmp < 0.0) tmp = -tmp; if ( tmp > 1.0 ) // avoid floating-point trouble - tmp = 1.0; + tmp = 1.0; return atan(sqrt(1 - tmp*tmp) / tmp) * RADTODEG64; } diff --git a/include/vector3d.h b/include/vector3d.h index 800cac24..33b8345d 100644 --- a/include/vector3d.h +++ b/include/vector3d.h @@ -421,6 +421,26 @@ namespace core template <> inline vector3d& vector3d::operator /=(s32 val) {X/=val;Y/=val;Z/=val; return *this;} + template <> + inline vector3d vector3d::getSphericalCoordinateAngles() + { + vector3d angle; + const f64 length = X*X + Y*Y + Z*Z; + + if (length) + { + if (X!=0) + { + angle.Y = round32((f32)(atan2((f64)Z,(f64)X) * RADTODEG64)); + } + else if (Z<0) + angle.Y=180; + + angle.X = round32((f32)(acos(Y * core::reciprocal_squareroot(length)) * RADTODEG64)); + } + return angle; + } + //! Typedef for a f32 3d vector. typedef vector3d vector3df; diff --git a/source/Irrlicht/CAnimatedMeshMD2.cpp b/source/Irrlicht/CAnimatedMeshMD2.cpp index 16d4b345..cb055dea 100644 --- a/source/Irrlicht/CAnimatedMeshMD2.cpp +++ b/source/Irrlicht/CAnimatedMeshMD2.cpp @@ -15,8 +15,7 @@ namespace scene { const s32 MD2_FRAME_SHIFT = 2; -const f32 MD2_FRAME_SHIFT_RECIPROCAL = 1.f / ( 1 << MD2_FRAME_SHIFT ); - +const f32 MD2_FRAME_SHIFT_RECIPROCAL = 1.f / (1 << MD2_FRAME_SHIFT); const s32 Q2_VERTEX_NORMAL_TABLE_SIZE = 162; @@ -194,27 +193,27 @@ struct SMD2AnimationType static const SMD2AnimationType MD2AnimationTypeList[21] = { - { 0, 39, 9 }, // STAND - { 40, 45, 10 }, // RUN - { 46, 53, 10 }, // ATTACK - { 54, 57, 7 }, // PAIN_A - { 58, 61, 7 }, // PAIN_B - { 62, 65, 7 }, // PAIN_C - { 66, 71, 7 }, // JUMP - { 72, 83, 7 }, // FLIP - { 84, 94, 7 }, // SALUTE - { 95, 111, 10 }, // FALLBACK - { 112, 122, 7 }, // WAVE - { 123, 134, 6 }, // POINT - { 135, 153, 10 }, // CROUCH_STAND - { 154, 159, 7 }, // CROUCH_WALK - { 160, 168, 10 }, // CROUCH_ATTACK - { 169, 172, 7 }, // CROUCH_PAIN - { 173, 177, 5 }, // CROUCH_DEATH - { 178, 183, 7 }, // DEATH_FALLBACK - { 184, 189, 7 }, // DEATH_FALLFORWARD - { 190, 197, 7 }, // DEATH_FALLBACKSLOW - { 198, 198, 5 }, // BOOM + { 0, 39, 9}, // STAND + { 40, 45, 10}, // RUN + { 46, 53, 10}, // ATTACK + { 54, 57, 7}, // PAIN_A + { 58, 61, 7}, // PAIN_B + { 62, 65, 7}, // PAIN_C + { 66, 71, 7}, // JUMP + { 72, 83, 7}, // FLIP + { 84, 94, 7}, // SALUTE + { 95, 111, 10}, // FALLBACK + {112, 122, 7}, // WAVE + {123, 134, 6}, // POINT + {135, 153, 10}, // CROUCH_STAND + {154, 159, 7}, // CROUCH_WALK + {160, 168, 10}, // CROUCH_ATTACK + {169, 172, 7}, // CROUCH_PAIN + {173, 177, 5}, // CROUCH_DEATH + {178, 183, 7}, // DEATH_FALLBACK + {184, 189, 7}, // DEATH_FALLFORWARD + {190, 197, 7}, // DEATH_FALLBACKSLOW + {198, 198, 5}, // BOOM }; @@ -238,6 +237,7 @@ CAnimatedMeshMD2::~CAnimatedMeshMD2() InterpolationBuffer->drop(); } + //! returns the amount of frames in milliseconds. If the amount is 1, it is a static (=non animated) mesh. u32 CAnimatedMeshMD2::getFrameCount() const { @@ -294,7 +294,6 @@ void CAnimatedMeshMD2::updateInterpolationBuffer(s32 frame, s32 startFrameLoop, { u32 firstFrame, secondFrame; f32 div; - core::vector3df* NormalTable = (core::vector3df*)&Q2_VERTEX_NORMAL_TABLE; // TA: resolve missing ipol in loop between end-start @@ -311,10 +310,10 @@ void CAnimatedMeshMD2::updateInterpolationBuffer(s32 frame, s32 startFrameLoop, u32 e = endFrameLoop >> MD2_FRAME_SHIFT; firstFrame = frame >> MD2_FRAME_SHIFT; - secondFrame = core::if_c_a_else_b ( firstFrame + 1 > e, s, firstFrame + 1 ); + secondFrame = core::if_c_a_else_b(firstFrame + 1 > e, s, firstFrame + 1); - firstFrame = core::s32_min ( FrameCount - 1, firstFrame ); - secondFrame = core::s32_min ( FrameCount - 1, secondFrame ); + firstFrame = core::s32_min(FrameCount - 1, firstFrame); + secondFrame = core::s32_min(FrameCount - 1, secondFrame); //div = (frame % (1<(InterpolationBuffer->getVertices()); - SMD2Vert* first = FrameList[firstFrame].pointer(); - SMD2Vert* second = FrameList[secondFrame].pointer(); + SMD2Vert* first = FrameList[firstFrame].pointer(); + SMD2Vert* second = FrameList[secondFrame].pointer(); // interpolate both frames const u32 count = FrameList[firstFrame].size(); for (u32 i=0; iPos.X) * FrameTransforms[firstFrame].scale.X + FrameTransforms[firstFrame].translate.X; - one.Y = f32(first->Pos.Y) * FrameTransforms[firstFrame].scale.Y + FrameTransforms[firstFrame].translate.Y; - one.Z = f32(first->Pos.Z) * FrameTransforms[firstFrame].scale.Z + FrameTransforms[firstFrame].translate.Z; - two.X = f32(second->Pos.X) * FrameTransforms[secondFrame].scale.X + FrameTransforms[secondFrame].translate.X; - two.Y = f32(second->Pos.Y) * FrameTransforms[secondFrame].scale.Y + FrameTransforms[secondFrame].translate.Y; - two.Z = f32(second->Pos.Z) * FrameTransforms[secondFrame].scale.Z + FrameTransforms[secondFrame].translate.Z; - target->Pos = (two - one) * div + one; - - target->Normal = (NormalTable[second->NormalIdx] - NormalTable[first->NormalIdx]) * div - + NormalTable[first->NormalIdx]; + const core::vector3df one = core::vector3df(f32(first->Pos.X) * FrameTransforms[firstFrame].scale.X + FrameTransforms[firstFrame].translate.X, + f32(first->Pos.Y) * FrameTransforms[firstFrame].scale.Y + FrameTransforms[firstFrame].translate.Y, + f32(first->Pos.Z) * FrameTransforms[firstFrame].scale.Z + FrameTransforms[firstFrame].translate.Z); + const core::vector3df two = core::vector3df(f32(second->Pos.X) * FrameTransforms[secondFrame].scale.X + FrameTransforms[secondFrame].translate.X, + f32(second->Pos.Y) * FrameTransforms[secondFrame].scale.Y + FrameTransforms[secondFrame].translate.Y, + f32(second->Pos.Z) * FrameTransforms[secondFrame].scale.Z + FrameTransforms[secondFrame].translate.Z); + target->Pos = two.getInterpolated(one, div); + const core::vector3df n1( + Q2_VERTEX_NORMAL_TABLE[first->NormalIdx][0], + Q2_VERTEX_NORMAL_TABLE[first->NormalIdx][2], + Q2_VERTEX_NORMAL_TABLE[first->NormalIdx][1]); + const core::vector3df n2( + Q2_VERTEX_NORMAL_TABLE[second->NormalIdx][0], + Q2_VERTEX_NORMAL_TABLE[second->NormalIdx][2], + Q2_VERTEX_NORMAL_TABLE[second->NormalIdx][1]); + target->Normal = n2.getInterpolated(n1, div); ++target; ++first; ++second; @@ -381,7 +385,7 @@ const core::aabbox3d& CAnimatedMeshMD2::getBoundingBox() const //! set user axis aligned bounding box -void CAnimatedMeshMD2::setBoundingBox( const core::aabbox3df& box) +void CAnimatedMeshMD2::setBoundingBox(const core::aabbox3df& box) { InterpolationBuffer->BoundingBox = box; } @@ -396,7 +400,7 @@ E_ANIMATED_MESH_TYPE CAnimatedMeshMD2::getMeshType() const //! Returns frame loop data for a special MD2 animation type. void CAnimatedMeshMD2::getFrameLoop(EMD2_ANIMATION_TYPE l, - s32& outBegin, s32& outEnd, s32& outFPS) const + s32& outBegin, s32& outEnd, s32& outFPS) const { if (l < 0 || l >= EMAT_COUNT) return; @@ -405,7 +409,7 @@ void CAnimatedMeshMD2::getFrameLoop(EMD2_ANIMATION_TYPE l, outEnd = MD2AnimationTypeList[l].end << MD2_FRAME_SHIFT; // correct to anim between last->first frame - outEnd += MD2_FRAME_SHIFT == 0 ? 1 : ( 1 << MD2_FRAME_SHIFT ) - 1; + outEnd += MD2_FRAME_SHIFT == 0 ? 1 : (1 << MD2_FRAME_SHIFT) - 1; outFPS = MD2AnimationTypeList[l].fps << MD2_FRAME_SHIFT; } @@ -420,7 +424,7 @@ bool CAnimatedMeshMD2::getFrameLoop(const c8* name, { outBegin = AnimationData[i].begin << MD2_FRAME_SHIFT; outEnd = AnimationData[i].end << MD2_FRAME_SHIFT; - outEnd += MD2_FRAME_SHIFT == 0 ? 1 : ( 1 << MD2_FRAME_SHIFT ) - 1; + outEnd += MD2_FRAME_SHIFT == 0 ? 1 : (1 << MD2_FRAME_SHIFT) - 1; outFPS = AnimationData[i].fps << MD2_FRAME_SHIFT; return true; } diff --git a/source/Irrlicht/CGUIEditBox.cpp b/source/Irrlicht/CGUIEditBox.cpp index 988d07b8..9c8eea7c 100644 --- a/source/Irrlicht/CGUIEditBox.cpp +++ b/source/Irrlicht/CGUIEditBox.cpp @@ -520,7 +520,7 @@ bool CGUIEditBox::processKey(const SEvent& event) { s32 cp = CursorPos - BrokenTextPositions[lineNo]; if ((s32)BrokenText[lineNo-1].size() < cp) - CursorPos = BrokenTextPositions[lineNo-1] + (s32)BrokenText[lineNo-1].size()-1; + CursorPos = BrokenTextPositions[lineNo-1] + core::max_((u32)1, BrokenText[lineNo-1].size())-1; else CursorPos = BrokenTextPositions[lineNo-1] + cp; } @@ -551,7 +551,7 @@ bool CGUIEditBox::processKey(const SEvent& event) { s32 cp = CursorPos - BrokenTextPositions[lineNo]; if ((s32)BrokenText[lineNo+1].size() < cp) - CursorPos = BrokenTextPositions[lineNo+1] + BrokenText[lineNo+1].size()-1; + CursorPos = BrokenTextPositions[lineNo+1] + core::max_((u32)1, BrokenText[lineNo+1].size())-1; else CursorPos = BrokenTextPositions[lineNo+1] + cp; } @@ -1135,7 +1135,7 @@ void CGUIEditBox::breakText() s32 whitelgth = font->getDimension(whitespace.c_str()).Width; s32 worldlgth = font->getDimension(word.c_str()).Width; - if (WordWrap && length + worldlgth + whitelgth > elWidth) + if (WordWrap && length + worldlgth + whitelgth > elWidth && line.size() > 0) { // break to next line length = worldlgth; diff --git a/source/Irrlicht/CGUIModalScreen.cpp b/source/Irrlicht/CGUIModalScreen.cpp index 895471ed..ca352277 100644 --- a/source/Irrlicht/CGUIModalScreen.cpp +++ b/source/Irrlicht/CGUIModalScreen.cpp @@ -33,7 +33,7 @@ bool CGUIModalScreen::canTakeFocus(IGUIElement* target) const { return (target && ((const IGUIElement*)target == this // this element can take it || isMyChild(target) // own children also - || (target->getType() == EGUIET_MODAL_SCREEN )// other modals also fine + || (target->getType() == EGUIET_MODAL_SCREEN ) // other modals also fine (is now on top or explicitely requested) || (target->getParent() && target->getParent()->getType() == EGUIET_MODAL_SCREEN ))) // children of other modals will do ; } @@ -86,6 +86,13 @@ bool CGUIModalScreen::OnEvent(const SEvent& event) switch(event.GUIEvent.EventType) { case EGET_ELEMENT_FOCUSED: + if ( event.GUIEvent.Caller == this && isMyChild(event.GUIEvent.Element) ) + { + Environment->removeFocus(0); // can't setFocus otherwise at it still has focus here + Environment->setFocus(event.GUIEvent.Element); + MouseDownTime = os::Timer::getTime(); + return true; + } if ( !canTakeFocus(event.GUIEvent.Caller)) { if ( !Children.empty() ) diff --git a/source/Irrlicht/CZipReader.cpp b/source/Irrlicht/CZipReader.cpp index 676edf51..2e096648 100644 --- a/source/Irrlicht/CZipReader.cpp +++ b/source/Irrlicht/CZipReader.cpp @@ -104,7 +104,7 @@ IFileArchive* CArchiveLoaderZIP::createArchive(io::IReadFile* file, bool ignoreC file->read(&sig, 2); #ifdef __BIG_ENDIAN__ - os::Byteswap::byteswap(sig); + sig = os::Byteswap::byteswap(sig); #endif file->seek(0); @@ -126,7 +126,7 @@ bool CArchiveLoaderZIP::isALoadableFileFormat(io::IReadFile* file) const file->read( &header.Sig, 4 ); #ifdef __BIG_ENDIAN__ - os::Byteswap::byteswap(header.Sig); + header.Sig = os::Byteswap::byteswap(header.Sig); #endif return header.Sig == 0x04034b50 || // ZIP @@ -192,8 +192,8 @@ bool CZipReader::scanGZipHeader() { #ifdef __BIG_ENDIAN__ - os::Byteswap::byteswap(header.sig); - os::Byteswap::byteswap(header.time); + header.sig = os::Byteswap::byteswap(header.sig); + header.time = os::Byteswap::byteswap(header.time); #endif // check header value @@ -209,7 +209,7 @@ bool CZipReader::scanGZipHeader() File->read(&dataLen, 2); #ifdef __BIG_ENDIAN__ - os::Byteswap::byteswap(dataLen); + dataLen = os::Byteswap::byteswap(dataLen); #endif // skip it @@ -274,8 +274,8 @@ bool CZipReader::scanGZipHeader() File->read(&entry.header.DataDescriptor.UncompressedSize, 4); #ifdef __BIG_ENDIAN__ - os::Byteswap::byteswap(entry.header.DataDescriptor.CRC32); - os::Byteswap::byteswap(entry.header.DataDescriptor.UncompressedSize); + entry.header.DataDescriptor.CRC32 = os::Byteswap::byteswap(entry.header.DataDescriptor.CRC32); + entry.header.DataDescriptor.UncompressedSize = os::Byteswap::byteswap(entry.header.DataDescriptor.UncompressedSize); #endif // now we've filled all the fields, this is just a standard deflate block diff --git a/tests/md2Animation.cpp b/tests/md2Animation.cpp index 3f858bac..3fac32bc 100644 --- a/tests/md2Animation.cpp +++ b/tests/md2Animation.cpp @@ -5,49 +5,43 @@ using namespace irr; using namespace core; -using namespace scene; -using namespace video; -using namespace io; -using namespace gui; +namespace +{ // Tests MD2 animations. /** At the moment, this just verifies that the last frame of the animation produces the expected bitmap. */ -bool md2Animation(void) +bool testLastFrame() { // Use EDT_BURNINGSVIDEO since it is not dependent on (e.g.) OpenGL driver versions. - IrrlichtDevice *device = createDevice( EDT_BURNINGSVIDEO, dimension2d(160, 120), 32); - assert(device); + IrrlichtDevice *device = createDevice(video::EDT_BURNINGSVIDEO, dimension2d(160, 120), 32); if (!device) return false; - IVideoDriver* driver = device->getVideoDriver(); - ISceneManager * smgr = device->getSceneManager(); + video::IVideoDriver* driver = device->getVideoDriver(); + scene::ISceneManager * smgr = device->getSceneManager(); - IAnimatedMesh* mesh = smgr->getMesh("../media/sydney.md2"); - IAnimatedMeshSceneNode* node; - assert(mesh); + scene::IAnimatedMesh* mesh = smgr->getMesh("../media/sydney.md2"); bool result = (mesh != 0); - if(mesh) + if (mesh) { - node = smgr->addAnimatedMeshSceneNode(mesh); - assert(node); + scene::IAnimatedMeshSceneNode* node = smgr->addAnimatedMeshSceneNode(mesh); - if(node) + if (node) { node->setPosition(vector3df(20, 0, 30)); - node->setMaterialFlag(EMF_LIGHTING, false); + node->setMaterialFlag(video::EMF_LIGHTING, false); node->setMaterialTexture(0, driver->getTexture("../media/sydney.bmp")); node->setLoopMode(false); (void)smgr->addCameraSceneNode(); // Just jump to the last frame since that's all we're interested in. - node->setMD2Animation(EMAT_DEATH_FALLBACK); + node->setMD2Animation(scene::EMAT_DEATH_FALLBACK); node->setCurrentFrame((f32)(node->getEndFrame())); node->setAnimationSpeed(0); device->run(); - driver->beginScene(true, true, SColor(255, 255, 255, 0)); + driver->beginScene(true, true, video::SColor(255, 255, 255, 0)); smgr->drawAll(); driver->endScene(); if (mesh->getBoundingBox() != mesh->getMesh(node->getEndFrame())->getBoundingBox()) @@ -79,3 +73,56 @@ bool md2Animation(void) return result; } +// Tests MD2 normals. +bool testNormals() +{ + // Use EDT_BURNINGSVIDEO since it is not dependent on (e.g.) OpenGL driver versions. + IrrlichtDevice *device = createDevice(video::EDT_BURNINGSVIDEO, dimension2d(160, 120), 32); + if (!device) + return false; + + video::IVideoDriver* driver = device->getVideoDriver(); + scene::ISceneManager * smgr = device->getSceneManager(); + + scene::IAnimatedMesh* mesh = smgr->getMesh("../media/sydney.md2"); + + bool result = (mesh != 0); + if (mesh) + { + scene::IAnimatedMeshSceneNode* node = smgr->addAnimatedMeshSceneNode(mesh); + if (node) + { + node->setPosition(vector3df(20, 0, 30)); + node->setMaterialFlag(video::EMF_LIGHTING, false); + node->setDebugDataVisible(scene::EDS_NORMALS); + node->setMaterialTexture(0, driver->getTexture("../media/sydney.bmp")); + node->setLoopMode(false); + + (void)smgr->addCameraSceneNode(); + + node->setMD2Animation(scene::EMAT_STAND); + node->setAnimationSpeed(0); + device->run(); + driver->beginScene(true, true, video::SColor(255, 255, 255, 0)); + smgr->drawAll(); + driver->endScene(); + } + } + + result &= takeScreenshotAndCompareAgainstReference(driver, "-md2Normals.png"); + device->closeDevice(); + device->run(); + device->drop(); + + return result; +} + +} + +// test md2 features +bool md2Animation(void) +{ + bool result = testLastFrame(); + result &= testNormals(); + return result; +} diff --git a/tests/media/Burning's Video-md2Normals.png b/tests/media/Burning's Video-md2Normals.png new file mode 100644 index 00000000..709b97e9 Binary files /dev/null and b/tests/media/Burning's Video-md2Normals.png differ diff --git a/tests/triangle3d.cpp b/tests/triangle3d.cpp index c1cafc61..f13694d0 100644 --- a/tests/triangle3d.cpp +++ b/tests/triangle3d.cpp @@ -59,6 +59,164 @@ static bool testGetIntersectionWithLine(core::triangle3d& triangle, const cor return allExpected; } +// modifying the same triangle in diverse ways get some more test-cases automatically +template +static bool stageModifications(int stage, triangle3d& triangle) +{ + switch ( stage ) + { + case 0: + return true; + case 1: + swap(triangle.pointB, triangle.pointC); + return true; + case 2: + swap(triangle.pointA, triangle.pointC); + return true; + case 3: + triangle.pointA.Z += 1000; + triangle.pointB.Z += 1000; + triangle.pointC.Z += 1000; + return true; + case 4: + swap(triangle.pointA.Y, triangle.pointA.Z); + swap(triangle.pointB.Y, triangle.pointB.Z); + swap(triangle.pointC.Y, triangle.pointC.Z); + return true; + } + return false; +} + +template +static void stageModifications(int stage, vector3d& point) +{ + switch ( stage ) + { + case 3: + point.Z += 1000; + break; + case 4: + swap(point.Y, point.Z); + break; + } +} + +template +static bool isPointInside(triangle3d triangleOrig) +{ + bool allExpected=true; + + array< vector3d > pointsInside; + pointsInside.push_back( vector3d(0,0,0) ); + pointsInside.push_back( (triangleOrig.pointA + triangleOrig.pointB + triangleOrig.pointC) / 3 ); + pointsInside.push_back( (triangleOrig.pointA + triangleOrig.pointB)/2 + vector3d(0,1,0) ); + pointsInside.push_back( (triangleOrig.pointA + triangleOrig.pointC)/2 + vector3d(1,0,0) ); + pointsInside.push_back( (triangleOrig.pointB + triangleOrig.pointC)/2 - vector3d(1,0,0) ); + + for (u32 stage=0; ; ++stage) + { + triangle3d triangle = triangleOrig; + if ( !stageModifications(stage, triangle) ) + break; + + for ( u32 i=0; i < pointsInside.size(); ++i ) + { + vector3d point = pointsInside[i]; + stageModifications(stage, point); + + allExpected &= triangle.isPointInside( point ); + if ( !allExpected ) + { + logTestString("triangle3d::isPointInside pointsInside test failed in stage %d point %d\n", stage, i); + return false; + } + + allExpected &= triangle.isPointInsideFast( point ); + if ( !allExpected ) + { + logTestString("triangle3d::isPointInsideFast pointsInside test failed in stage %d point %d\n", stage, i); + return false; + } + } + } + + array< vector3d > pointsOutside; + pointsOutside.push_back( triangleOrig.pointA - vector3d(1,0,0) ); + pointsOutside.push_back( triangleOrig.pointA - vector3d(0,1,0) ); + pointsOutside.push_back( triangleOrig.pointB + vector3d(1,0,0) ); + pointsOutside.push_back( triangleOrig.pointB - vector3d(0,1,0) ); + pointsOutside.push_back( triangleOrig.pointC - vector3d(1,0,0) ); + pointsOutside.push_back( triangleOrig.pointC + vector3d(1,0,0) ); + pointsOutside.push_back( triangleOrig.pointC + vector3d(0,1,0) ); + pointsOutside.push_back( (triangleOrig.pointA + triangleOrig.pointB)/2 - vector3d(0,1,0) ); + pointsOutside.push_back( (triangleOrig.pointA + triangleOrig.pointC)/2 - vector3d(1,0,0) ); + pointsOutside.push_back( (triangleOrig.pointB + triangleOrig.pointC)/2 + vector3d(1,0,0) ); + + for (u32 stage=0; ; ++stage) + { + triangle3d triangle = triangleOrig; + if ( !stageModifications(stage, triangle) ) + break; + + for ( u32 i=0; i < pointsOutside.size(); ++i ) + { + vector3d point = pointsOutside[i]; + stageModifications(stage, point); + + allExpected &= !triangle.isPointInside( point ); + if ( !allExpected ) + { + logTestString("triangle3d::isPointInside pointsOutside test failed in stage %d point %d\n", stage, i); + return false; + } + + allExpected &= !triangle.isPointInsideFast( point ); + if ( !allExpected ) + { + logTestString("triangle3d::isPointInsideFast pointsOutside test failed in stage %d point %d\n", stage, i); + return false; + } + } + } + + array< vector3d > pointsBorder; + pointsBorder.push_back( triangleOrig.pointA ); + pointsBorder.push_back( triangleOrig.pointB ); + pointsBorder.push_back( triangleOrig.pointC ); + pointsBorder.push_back( (triangleOrig.pointA + triangleOrig.pointB)/2 ); + pointsBorder.push_back( (triangleOrig.pointA + triangleOrig.pointC)/2 ); + pointsBorder.push_back( (triangleOrig.pointB + triangleOrig.pointC)/2 ); + + for (u32 stage=0; ; ++stage) + { + triangle3d triangle = triangleOrig; + if ( !stageModifications(stage, triangle) ) + break; + + for ( u32 i=0; i < pointsBorder.size(); ++i ) + { + vector3d point = pointsBorder[i]; + stageModifications(stage, point); + + allExpected &= triangle.isPointInside( point ); + if ( !allExpected ) + { + logTestString("triangle3d::isPointInside pointsBorder test failed in stage %d point %d\n", stage, i); + return false; + } + + /* results for isPointInsideFast are mixed for border cases, but I guess that's fine. + if ( triangle.isPointInsideFast( point ) ) + logTestString("+ triangle3d::isPointInsideFast pointsBorder stage %d point %d is INSIDE\n", stage, i); + else + logTestString("- triangle3d::isPointInsideFast pointsBorder stage %d point %d is NOT inside\n", stage, i); + */ + } + } + + return allExpected; +} + // Test the functionality of triangle3d /** Validation is done with asserts() against expected results. */ @@ -89,6 +247,29 @@ bool testTriangle3d(void) allExpected &= testGetIntersectionWithLine(triangle, ray); } + bool testEigen = triangle3di(vector3di(250, 0, 0), vector3di(0, 0, 500), vector3di(500, 0, 500)).isPointInside(vector3di(300,0,300)); + if ( !testEigen ) // test from Eigen from here: http://irrlicht.sourceforge.net/forum/viewtopic.php?f=7&t=44372&p=254331#p254331 + logTestString("Test isPointInside fails with integers\n"); + allExpected &= testEigen; + + logTestString("Test isPointInside with f32\n"); + { + triangle3d t(vector3d(-1000,-1000,0), vector3d(1000,-1000,0), vector3d(0,1000,0)); + allExpected &= isPointInside(t); + } + + logTestString("Test isPointInside with f64\n"); + { + triangle3d t(vector3d(-1000,-1000,0), vector3d(1000,-1000,0), vector3d(0,1000,0)); + allExpected &= isPointInside(t); + } + + logTestString("Test isPointInside with s32\n"); + { + triangle3d t(vector3d(-1000,-1000,0), vector3d(1000,-1000,0), vector3d(0,1000,0)); + allExpected &= isPointInside(t); + } + if(allExpected) logTestString("\nAll tests passed\n"); else