portal.cpp

Engine/source/T3D/portal.cpp

More...

Public Functions

ConsoleDocClass(Portal , "@brief An object that provides <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> \"window\" into <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> zone, allowing <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> viewer " "<a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> see what 's rendered in the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">zone.\n\n</a>" "A portal is an object that connects zones such that the content of one zone becomes " "visible in the other when looking through the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">portal.\n\n</a>" "Each portal is <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> full zone which is divided into two sides by the portal plane that " "intersects it. This intersection polygon is shown in red in the editor. Either of the " "sides of <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> portal can be connected <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> one or more <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">zones.\n\n</a>" "A connection from <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> specific portal side <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> zone is made in either of two <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">ways:\n\n</a>" "< ol >\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "< li >By moving <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> <a href="/coding/class/classzone/">Zone</a> object <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> intersect with the portal at the respective side. While usually it makes " "sense <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> this overlap <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> be small, the connection is established correctly as long as the center of the <a href="/coding/class/classzone/">Zone</a> " "object that should connect is on the correct side of the portal plane.</li >\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "< li >By the respective side of the portal free of <a href="/coding/class/classzone/">Zone</a> objects that would connect <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> it. In this case, given " "that the other side is connected <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> one or more Zones, the portal will automatically connect itself <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the " "outdoor \"zone\" which implicitly is present in any level.</li>\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "</ol>\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" "From this, it follows that there are two types of <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">portals:\n\n</a>" "< dl >\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "< dt >Exterior Portals</dt >" "< dd >An exterior portal is one that is connected <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> one or more <a href="/coding/class/classzone/">Zone</a> objects on one side and <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the outdoor " "zone at the other side. This kind of portal is most useful <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> covering transitions from outdoor spaces <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> " "indoor spaces.</dd >" "< dt >Interior Portals</dt >" "< dd >An interior portal is one that is connected <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> one or more <a href="/coding/class/classzone/">Zone</a> objects on both sides. This kind of portal " "is most useful <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> covering transitions between indoor spaces./ dd, " "</dl >\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" "Strictly speaking, there is <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> third type of portal called an \"invalid portal\". This is <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> portal that is not " "connected <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> <a href="/coding/class/classzone/">Zone</a> object on either side in which case the portal serves no <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">use.\n\n</a>" "Portals in Torque are bidirectional meaning that they connect zones both ways and " "you can look through the portal's front side as well as through its back-<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">side.\n\n</a>" "Like Zones, Portals can either be box-shaped or use custom convex polyhedral <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">shapes.\n\n</a>" "Portals will usually be created in the editor but can, of course, also be created " "in script code as <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">such:\n\n</a>" " @<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "//Example declaration of <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> Portal. This will create <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> box-shaped <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">portal.\n</a>" "<a href="/coding/file/tmm__on_8h/#tmm__on_8h_1a1ac41480eb2e4aadd52252ee550b630a">new</a> <a href="/coding/class/classportal/">Portal</a>(PortalToTestZone)\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "{\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " position=\"12.8467 -4.02246 14.8017\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "	 rotation = \"0 0 -1 97.5085\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "	 scale = \"1 0.25 1\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "	 canSave = \"1\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "	 canSaveDynamicFields = \"1\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "};\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">endtsexample\n\n</a>" "@note Keep in mind that zones and portals are more or less strictly <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> scene optimization mechanism meant <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> " "improve <a href="/coding/file/editortool_8cpp/#editortool_8cpp_1a4cb041169a32ea3d4cacadbb955e06b4">render</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">times.\n\n</a>" "@see <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Zone\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">enviroMisc\n</a>" )
DefineEngineMethod(Portal , isExteriorPortal , bool , () , "Test whether the portal connects interior zones <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the outdoor <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">zone.\n\n</a>" "@return True <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> the portal is an exterior portal." )
DefineEngineMethod(Portal , isInteriorPortal , bool , () , "Test whether the portal connects interior zones <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">only.\n\n</a>" "@return True <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> the portal is an interior portal." )

Detailed Description

Public Functions

ConsoleDocClass(Portal , "@brief An object that provides <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> \"window\" into <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> zone, allowing <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> viewer " "<a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> see what 's rendered in the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">zone.\n\n</a>" "A portal is an object that connects zones such that the content of one zone becomes " "visible in the other when looking through the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">portal.\n\n</a>" "Each portal is <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> full zone which is divided into two sides by the portal plane that " "intersects it. This intersection polygon is shown in red in the editor. Either of the " "sides of <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> portal can be connected <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> one or more <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">zones.\n\n</a>" "A connection from <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> specific portal side <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> zone is made in either of two <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">ways:\n\n</a>" "< ol >\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "< li >By moving <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> <a href="/coding/class/classzone/">Zone</a> object <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> intersect with the portal at the respective side. While usually it makes " "sense <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> this overlap <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> be small, the connection is established correctly as long as the center of the <a href="/coding/class/classzone/">Zone</a> " "object that should connect is on the correct side of the portal plane.</li >\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "< li >By the respective side of the portal free of <a href="/coding/class/classzone/">Zone</a> objects that would connect <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> it. In this case, given " "that the other side is connected <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> one or more Zones, the portal will automatically connect itself <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the " "outdoor \"zone\" which implicitly is present in any level.</li>\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "</ol>\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" "From this, it follows that there are two types of <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">portals:\n\n</a>" "< dl >\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "< dt >Exterior Portals</dt >" "< dd >An exterior portal is one that is connected <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> one or more <a href="/coding/class/classzone/">Zone</a> objects on one side and <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the outdoor " "zone at the other side. This kind of portal is most useful <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> covering transitions from outdoor spaces <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> " "indoor spaces.</dd >" "< dt >Interior Portals</dt >" "< dd >An interior portal is one that is connected <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> one or more <a href="/coding/class/classzone/">Zone</a> objects on both sides. This kind of portal " "is most useful <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> covering transitions between indoor spaces./ dd, " "</dl >\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" "Strictly speaking, there is <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> third type of portal called an \"invalid portal\". This is <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> portal that is not " "connected <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> <a href="/coding/class/classzone/">Zone</a> object on either side in which case the portal serves no <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">use.\n\n</a>" "Portals in Torque are bidirectional meaning that they connect zones both ways and " "you can look through the portal's front side as well as through its back-<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">side.\n\n</a>" "Like Zones, Portals can either be box-shaped or use custom convex polyhedral <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">shapes.\n\n</a>" "Portals will usually be created in the editor but can, of course, also be created " "in script code as <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">such:\n\n</a>" " @<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "//Example declaration of <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> Portal. This will create <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> box-shaped <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">portal.\n</a>" "<a href="/coding/file/tmm__on_8h/#tmm__on_8h_1a1ac41480eb2e4aadd52252ee550b630a">new</a> <a href="/coding/class/classportal/">Portal</a>(PortalToTestZone)\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "{\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " position=\"12.8467 -4.02246 14.8017\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "	 rotation = \"0 0 -1 97.5085\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "	 scale = \"1 0.25 1\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "	 canSave = \"1\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "	 canSaveDynamicFields = \"1\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "};\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">endtsexample\n\n</a>" "@note Keep in mind that zones and portals are more or less strictly <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> scene optimization mechanism meant <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> " "improve <a href="/coding/file/editortool_8cpp/#editortool_8cpp_1a4cb041169a32ea3d4cacadbb955e06b4">render</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">times.\n\n</a>" "@see <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Zone\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">enviroMisc\n</a>" )

DefineEngineMethod(Portal , isExteriorPortal , bool , () , "Test whether the portal connects interior zones <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the outdoor <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">zone.\n\n</a>" "@return True <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> the portal is an exterior portal." )

DefineEngineMethod(Portal , isInteriorPortal , bool , () , "Test whether the portal connects interior zones <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">only.\n\n</a>" "@return True <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> the portal is an interior portal." )

IMPLEMENT_CO_NETOBJECT_V1(Portal )

  1
  2//-----------------------------------------------------------------------------
  3// Copyright (c) 2012 GarageGames, LLC
  4//
  5// Permission is hereby granted, free of charge, to any person obtaining a copy
  6// of this software and associated documentation files (the "Software"), to
  7// deal in the Software without restriction, including without limitation the
  8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  9// sell copies of the Software, and to permit persons to whom the Software is
 10// furnished to do so, subject to the following conditions:
 11//
 12// The above copyright notice and this permission notice shall be included in
 13// all copies or substantial portions of the Software.
 14//
 15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 21// IN THE SOFTWARE.
 22//-----------------------------------------------------------------------------
 23
 24#include "platform/platform.h"
 25#include "T3D/portal.h"
 26
 27#include "core/stream/bitStream.h"
 28#include "console/consoleTypes.h"
 29#include "console/engineAPI.h"
 30#include "scene/sceneManager.h"
 31#include "scene/sceneRenderState.h"
 32#include "scene/zones/sceneRootZone.h"
 33#include "scene/culling/sceneCullingState.h"
 34#include "scene/zones/sceneTraversalState.h"
 35#include "math/mPlaneSet.h"
 36#include "gfx/gfxDrawUtil.h"
 37#include "gfx/gfxTransformSaver.h"
 38
 39#include "scene/mixin/sceneAmbientSoundObject.impl.h"
 40#include "scene/mixin/scenePolyhedralObject.impl.h"
 41#include "math/mPolyhedron.impl.h"
 42
 43
 44IMPLEMENT_CO_NETOBJECT_V1( Portal );
 45
 46ConsoleDocClass( Portal,
 47   "@brief An object that provides a \"window\" into a zone, allowing a viewer "
 48      "to see what's rendered in the zone.\n\n"
 49
 50   "A portal is an object that connects zones such that the content of one zone becomes "
 51   "visible in the other when looking through the portal.\n\n"
 52
 53   "Each portal is a full zone which is divided into two sides by the portal plane that "
 54   "intersects it.  This intersection polygon is shown in red in the editor.  Either of the "
 55   "sides of a portal can be connected to one or more zones.\n\n"
 56
 57   "A connection from a specific portal side to a zone is made in either of two ways:\n\n"
 58
 59   "<ol>\n"
 60   "<li>By moving a Zone object to intersect with the portal at the respective side.  While usually it makes "
 61   "sense for this overlap to be small, the connection is established correctly as long as the center of the Zone "
 62   "object that should connect is on the correct side of the portal plane.</li>\n"
 63   "<li>By the respective side of the portal free of Zone objects that would connect to it.  In this case, given "
 64   "that the other side is connected to one or more Zones, the portal will automatically connect itself to the "
 65   "outdoor \"zone\" which implicitly is present in any level.</li>\n"
 66   "</ol>\n\n"
 67
 68   "From this, it follows that there are two types of portals:\n\n"
 69
 70   "<dl>\n"
 71   "<dt>Exterior Portals</dt>"
 72   "<dd>An exterior portal is one that is connected to one or more Zone objects on one side and to the outdoor "
 73   "zone at the other side.  This kind of portal is most useful for covering transitions from outdoor spaces to "
 74   "indoor spaces.</dd>"
 75   "<dt>Interior Portals</dt>"
 76   "<dd>An interior portal is one that is connected to one or more Zone objects on both sides.  This kind of portal "
 77   "is most useful for covering transitions between indoor spaces./dd>"
 78   "</dl>\n\n"
 79
 80   "Strictly speaking, there is a third type of portal called an \"invalid portal\".  This is a portal that is not "
 81   "connected to a Zone object on either side in which case the portal serves no use.\n\n"
 82
 83   "Portals in Torque are bidirectional meaning that they connect zones both ways and "
 84   "you can look through the portal's front side as well as through its back-side.\n\n"
 85
 86   "Like Zones, Portals can either be box-shaped or use custom convex polyhedral shapes.\n\n"
 87
 88   "Portals will usually be created in the editor but can, of course, also be created "
 89   "in script code as such:\n\n"
 90
 91   "@tsexample\n"
 92      "// Example declaration of a Portal.  This will create a box-shaped portal.\n"
 93      "new Portal( PortalToTestZone )\n"
 94      "{\n"
 95      "   position = \"12.8467 -4.02246 14.8017\";\n"
 96      "   rotation = \"0 0 -1 97.5085\";\n"
 97      "   scale = \"1 0.25 1\";\n"
 98      "   canSave = \"1\";\n"
 99      "   canSaveDynamicFields = \"1\";\n"
100      "};\n"
101   "@endtsexample\n\n"
102
103   "@note Keep in mind that zones and portals are more or less strictly a scene optimization mechanism meant to "
104      "improve render times.\n\n"
105
106   "@see Zone\n"
107   "@ingroup enviroMisc\n"
108);
109
110
111// Notes:
112// - This class implements portal spaces as single zones.  A different, interesting take
113//   on this is to turn portal spaces into two zones with the portal plane acting as
114//   the separator.
115// - One downside to our treatment of portals as full zones in their own right is that
116//   in certain cases we end up including space in the traversal that is clearly not visible.
117//   Take a situation where you are in the outside zone and you are looking straight into
118//   the wall of a house.  On the other side of that house is a portal leading into the house.
119//   While the traversal will not step through that portal into the house since that would
120//   be leading towards the camera rather than away from it, it will still add the frustum
121//   to the visible space of the portal zone and thus make everything in the portal zone
122//   visible.  It has to do this since, while it can easily tell whether to step through the
123//   portal or not, it cannot easily tell whether the whole portal zone is visible or not as
124//   that depends on the occlusion situation in the outdoor zone.
125
126
127//-----------------------------------------------------------------------------
128
129Portal::Portal()
130   : mClassification( InvalidPortal ),
131     mIsGeometryDirty( true )
132{
133   VECTOR_SET_ASSOCIATION( mPortalPolygonWS );
134
135   mNetFlags.set( Ghostable | ScopeAlways );
136   mTypeMask |= StaticObjectType;
137
138   mPassableSides[ FrontSide ] = true;
139   mPassableSides[ BackSide ] = true;
140
141   mObjScale.set( 1.0f, 0.25f, 1.0f );
142
143   // We're not closed off.
144   mZoneFlags.clear( ZoneFlag_IsClosedOffSpace );
145}
146
147//-----------------------------------------------------------------------------
148
149void Portal::initPersistFields()
150{
151   addGroup( "Zoning" );
152
153      addProtectedField( "frontSidePassable", TypeBool, Offset( mPassableSides[ FrontSide ], Portal ),
154         &_setFrontSidePassable, &defaultProtectedGetFn,
155         "Whether one can view through the front-side of the portal." );
156      addProtectedField( "backSidePassable", TypeBool, Offset( mPassableSides[ BackSide ], Portal ),
157         &_setBackSidePassable, &defaultProtectedGetFn,
158         "Whether one can view through the back-side of the portal." );
159
160   endGroup( "Zoning" );
161
162   Parent::initPersistFields();
163}
164
165//-----------------------------------------------------------------------------
166
167void Portal::consoleInit()
168{
169   // Disable rendering of portals by default.
170   getStaticClassRep()->mIsRenderEnabled = false;
171}
172
173//-----------------------------------------------------------------------------
174
175String Portal::describeSelf() const
176{
177   String str = Parent::describeSelf();
178   
179   switch( getClassification() )
180   {
181      case InvalidPortal:     str += "|InvalidPortal"; break;
182      case ExteriorPortal:    str += "|ExteriorPortal"; break;
183      case InteriorPortal:    str += "|InteriorPortal"; break;
184   }
185
186   return str;
187}
188
189//-----------------------------------------------------------------------------
190
191bool Portal::writeField( StringTableEntry fieldName, const char* value )
192{
193   static StringTableEntry sFrontSidePassable = StringTable->insert( "frontSidePassable" );
194   static StringTableEntry sBackSidePassable = StringTable->insert( "backSidePassable" );
195
196   // Don't write passable flags if at default.
197   if( ( fieldName == sFrontSidePassable || fieldName == sBackSidePassable ) &&
198       dAtob( value ) )
199      return false;
200
201   return Parent::writeField( fieldName, value );
202}
203
204//-----------------------------------------------------------------------------
205
206void Portal::onSceneRemove()
207{
208   // Disconnect from root zone, if it's an exterior portal.
209
210   if( mClassification == ExteriorPortal )
211   {
212      AssertFatal( getSceneManager()->getZoneManager(), "Portal::onSceneRemove - Portal classified as exterior without having a zone manager!" );
213      getSceneManager()->getZoneManager()->getRootZone()->disconnectZoneSpace( this );
214   }
215
216   Parent::onSceneRemove();
217}
218
219//-----------------------------------------------------------------------------
220
221void Portal::setTransform( const MatrixF& mat )
222{
223   // Portal geometry needs updating.  Set this before calling
224   // parent because the transform change will cause an immediate
225   // update of the portal's zoning state.
226   mIsGeometryDirty = true;
227
228   Parent::setTransform( mat );
229}
230
231//-----------------------------------------------------------------------------
232
233void Portal::setSidePassable( Side side, bool value )
234{
235   if( mPassableSides[ side ] == value )
236      return;
237
238   mPassableSides[ side ] = value;
239
240   if( isServerObject() )
241      setMaskBits( PassableMask );
242}
243
244//-----------------------------------------------------------------------------
245
246U32 Portal::packUpdate( NetConnection *con, U32 mask, BitStream *stream )
247{
248   U32 retMask = Parent::packUpdate( con, mask, stream );
249
250   stream->writeFlag( mPassableSides[ FrontSide ] );
251   stream->writeFlag( mPassableSides[ BackSide ] );
252
253   return retMask;
254}
255
256//-----------------------------------------------------------------------------
257
258void Portal::unpackUpdate( NetConnection *con, BitStream *stream )
259{
260   Parent::unpackUpdate( con, stream );
261
262   mPassableSides[ FrontSide ] = stream->readFlag();
263   mPassableSides[ BackSide ] = stream->readFlag();
264}
265
266//-----------------------------------------------------------------------------
267
268void Portal::_renderObject( ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance* overrideMat )
269{
270   if( overrideMat )
271      return;
272
273   // Update geometry if necessary.
274
275   if( mIsGeometryDirty )
276      _updateGeometry();
277
278   // Render portal polygon.
279
280   GFXStateBlockDesc desc;
281   desc.setBlend( true );
282   desc.setZReadWrite( true, false );
283   desc.setCullMode( GFXCullNone );
284
285   PlaneF::Side viewSide = mPortalPlane.whichSide( state->getCameraPosition() );
286
287   ColorI color;
288   switch( mClassification )
289   {
290      case InvalidPortal:  color = ColorI( 255, 255, 255, 45 ); break;
291      case ExteriorPortal: color = viewSide == PlaneF::Front ? ColorI( 0, 128, 128, 45 ) : ColorI( 0, 255, 255, 45 ); break;
292      case InteriorPortal: color = viewSide == PlaneF::Front ? ColorI( 128, 128, 0, 45 ) : ColorI( 255, 255, 0, 45 ); break;
293   }
294
295   GFX->getDrawUtil()->drawPolygon( desc, mPortalPolygonWS.address(), mPortalPolygonWS.size(), color );
296
297   desc.setFillModeWireframe();
298   GFX->getDrawUtil()->drawPolygon( desc, mPortalPolygonWS.address(), mPortalPolygonWS.size(), ColorI::RED );
299
300   // Render rest.
301
302   Parent::_renderObject( ri, state, overrideMat );
303}
304
305//-----------------------------------------------------------------------------
306
307void Portal::traverseZones( SceneTraversalState* state, U32 startZoneId )
308{
309   // Check whether the portal is occluded.
310
311   if( state->getTraversalDepth() > 0 &&
312       state->getCullingState()->isOccluded( this ) )
313      return;
314
315   Parent::traverseZones( state, startZoneId );
316}
317
318//-----------------------------------------------------------------------------
319
320void Portal::_traverseConnectedZoneSpaces( SceneTraversalState* state )
321{
322   PROFILE_SCOPE( Portal_traverseConnectedZoneSpaces );
323
324   // Don't traverse out from the portal if it is invalid.
325
326   if( mClassification == InvalidPortal )
327      return;
328
329   AssertFatal( !mIsGeometryDirty, "Portal::_traverseConnectedZoneSpaces - Geometry not up-to-date!" );
330
331   // When starting traversal within a portal zone, we cannot really use the portal
332   // plane itself to direct our visibility queries.  For example, the camera might
333   // actually be located in front of the portal plane and thus cannot actually look
334   // through the portal, though it will still see what lies on front of where the
335   // portal leads.
336   //
337   // So if we're the start of the traversal chain, i.e. the traversal has started
338   // out in the portal zone, then just put the traversal through to SceneZoneSpace
339   // so it can hand it over to all connected zone managers.
340   //
341   // Otherwise, just do a normal traversal by stepping through the portal.
342
343   if( state->getTraversalDepth() == 1 )
344   {
345      Parent::_traverseConnectedZoneSpaces( state );
346      return;
347   }
348   
349   SceneCullingState* cullingState = state->getCullingState();
350   const SceneCameraState& cameraState = cullingState->getCameraState();
351
352   // Get the data of the zone we're coming from.  Note that at this point
353   // the portal zone itself is already on top of the traversal stack, so
354   // we skip over the bottom-most entry.
355
356   const U32 sourceZoneId = state->getZoneIdFromStack( 1 );
357   const SceneZoneSpace* sourceZoneSpace = state->getZoneFromStack( 1 );
358
359   // Find out which side of the portal we are on given the
360   // source zone.
361
362   const Portal::Side currentZoneSide =
363      sourceZoneId == SceneZoneSpaceManager::RootZoneId
364      ? ( getInteriorSideOfExteriorPortal() == FrontSide ? BackSide : FrontSide )
365      : getSideRelativeToPortalPlane( sourceZoneSpace->getPosition() );
366
367   // Don't step through portal if the side we're interested in isn't passable.
368
369   if( !isSidePassable( currentZoneSide ) )
370      return;
371
372   // If the viewpoint isn't on the same side of the portal as the source zone,
373   // then stepping through the portal would mean we are stepping back towards
374   // the viewpoint which doesn't make sense; so, skip the portal.
375
376   const Point3F& viewPos = cameraState.getViewPosition();
377   const F32 viewPosDistToPortal = mFabs( getPortalPlane().distToPlane( viewPos ) );
378   if( !mIsZero( viewPosDistToPortal ) && getSideRelativeToPortalPlane( viewPos ) != currentZoneSide )
379      return;
380
381   // Before we go ahead and do the real work, try to find out whether
382   // the portal is at a perpendicular or near-perpendicular angle to the view
383   // direction.  If so, there's no point in going further since we can't really
384   // see much through the portal anyway.  It also prevents us from stepping
385   // over front/back side ambiguities.
386
387   Point3F viewDirection = cameraState.getViewDirection();
388   const F32 dotProduct = mDot( viewDirection, getPortalPlane() );
389   if( mIsZero( dotProduct ) )
390      return;
391
392   // Finally, if we have come through a portal to the current zone, check if the target
393   // portal we are trying to step through now completely lies on the "backside"--i.e.
394   // the side of portal on which our current zone lies--of the source portal.  If so,
395   // we can be sure this portal leads us in the wrong direction.  This prevents the
396   // outdoor zone from having just arrived through a window on one side of a house just
397   // to go round the house and re-enter it from the other side.
398
399   Portal* sourcePortal = state->getTraversalDepth() > 2 ? dynamic_cast< Portal* >( state->getZoneFromStack( 2 ) ) : NULL;
400   if( sourcePortal != NULL )
401   {
402      const Side sourcePortalFrontSide =
403         sourceZoneId == SceneZoneSpaceManager::RootZoneId
404         ? sourcePortal->getInteriorSideOfExteriorPortal()
405         : sourcePortal->getSideRelativeToPortalPlane( sourceZoneSpace->getPosition() );
406      const PlaneF::Side sourcePortalPlaneFrontSide =
407         sourcePortalFrontSide == FrontSide ? PlaneF::Front : PlaneF::Back;
408
409      bool allPortalVerticesOnBackside = true;
410      const U32 numVertices = mPortalPolygonWS.size();
411      for( U32 i = 0; i < numVertices; ++ i )
412      {
413         // Not using getSideRelativeToPortalPlane here since we want PlaneF::On to be
414         // counted as backside here.
415         if( sourcePortal->mPortalPlane.whichSide( mPortalPolygonWS[ i ] ) == sourcePortalPlaneFrontSide )
416         {
417            allPortalVerticesOnBackside = false;
418            break;
419         }
420      }
421
422      if( allPortalVerticesOnBackside )
423         return;
424   }
425
426   // If we come from the outdoor zone, then we don't want to step through any portal
427   // where the interior zones are actually on the same side as our camera since that
428   // would mean we are stepping into an interior through the backside of a portal.
429
430   if( sourceZoneId == SceneZoneSpaceManager::RootZoneId )
431   {
432      const Portal::Side cameraSide = getSideRelativeToPortalPlane( viewPos );
433      if( cameraSide == getInteriorSideOfExteriorPortal() )
434         return;
435   }
436
437   // Clip the current culling volume against the portal's polygon.  If the polygon
438   // lies completely outside the volume or for some other reason there's no good resulting
439   // volume, _generateCullingVolume() will return false and we terminate this portal sequence
440   // here.
441   //
442   // However, don't attempt to clip the portal if we are standing really close to or
443   // even below the near distance away from the portal plane.  In that case, trying to
444   // clip the portal will only result in trouble so we stick to the original culling volume
445   // in that case.
446
447   bool haveClipped = false;
448   if( viewPosDistToPortal > ( cameraState.getFrustum().getNearDist() + 0.1f ) )
449   {
450      SceneCullingVolume volume;
451      if( !_generateCullingVolume( state, volume ) )
452         return;
453
454      state->pushCullingVolume( volume );
455      haveClipped = true;
456   }
457
458   // Short-circuit things if we are stepping from an interior zone outside.  In this
459   // case we know that the only zone we care about is the outdoor zone so head straight
460   // into it.
461
462   if( isExteriorPortal() && sourceZoneId != SceneZoneSpaceManager::RootZoneId )
463      getSceneManager()->getZoneManager()->getRootZone()->traverseZones( state );
464   else
465   {
466      // Go through the zones that the portal connects to and
467      // traverse into them.
468
469      for( ZoneSpaceRef* ref = mConnectedZoneSpaces; ref != NULL; ref = ref->mNext )
470      {
471         SceneZoneSpace* targetSpace = ref->mZoneSpace;
472         if( targetSpace == sourceZoneSpace )
473            continue; // Skip space we originated from.
474
475         // We have handled the case of stepping into the outdoor zone above and
476         // by skipping the zone we originated from, we have implicitly handled the
477         // case of stepping out of the outdoor zone.  Thus, we should not see the
478         // outdoor zone here.  Important as getPosition() is meaningless for it.
479         AssertFatal( targetSpace->getZoneRangeStart() != SceneZoneSpaceManager::RootZoneId,
480            "Portal::_traverseConnectedZoneSpaces - Outdoor zone must have been handled already" );
481
482         // Skip zones that lie on the same side as the zone
483         // we originated from.
484
485         if( getSideRelativeToPortalPlane( targetSpace->getPosition() ) == currentZoneSide )
486            continue;
487
488         // Traverse into the space.
489
490         targetSpace->traverseZones( state );
491      }
492   }
493
494   // If we have pushed our own clipping volume,
495   // remove that from the stack now.
496
497   if( haveClipped )
498      state->popCullingVolume();
499}
500
501//-----------------------------------------------------------------------------
502
503bool Portal::_generateCullingVolume( SceneTraversalState* state, SceneCullingVolume& outVolume ) const
504{
505   PROFILE_SCOPE( Portal_generateCullingVolume );
506
507   SceneCullingState* cullingState = state->getCullingState();
508   const SceneCullingVolume& currentVolume = state->getCurrentCullingVolume();
509
510   // Clip the portal polygon against the current culling volume.
511
512   Point3F vertices[ 64 ];
513   U32 numVertices = 0;
514
515   numVertices = currentVolume.getPlanes().clipPolygon(
516      mPortalPolygonWS.address(),
517      mPortalPolygonWS.size(),
518      vertices,
519      sizeof( vertices ) /sizeof( vertices[ 0 ] )
520   );
521
522   AssertFatal( numVertices == 0 || numVertices >= 3,
523      "Portal::_generateCullingVolume - Clipping produced degenerate polygon" );
524
525   if( !numVertices )
526      return false;
527
528   // Create a culling volume.
529
530   return cullingState->createCullingVolume(
531      vertices, numVertices,
532      SceneCullingVolume::Includer,
533      outVolume
534   );
535}
536
537//-----------------------------------------------------------------------------
538
539void Portal::connectZoneSpace( SceneZoneSpace* zoneSpace )
540{
541   Parent::connectZoneSpace( zoneSpace );
542
543   // Update portal state.  Unfortunately, we can't do that on demand
544   // easily since everything must be in place before a traversal starts.
545
546   _update();
547}
548
549//-----------------------------------------------------------------------------
550
551void Portal::disconnectZoneSpace( SceneZoneSpace* zoneSpace )
552{
553   Parent::disconnectZoneSpace( zoneSpace );
554
555   // Update portal state.
556
557   _update();
558}
559
560//-----------------------------------------------------------------------------
561
562void Portal::_disconnectAllZoneSpaces()
563{
564   Parent::_disconnectAllZoneSpaces();
565
566   // Update portal state.
567
568   _update();
569}
570
571//-----------------------------------------------------------------------------
572
573void Portal::_update()
574{
575   if( mIsGeometryDirty )
576      _updateGeometry();
577
578   _updateConnectivity();
579}
580
581//-----------------------------------------------------------------------------
582
583void Portal::_updateGeometry()
584{
585   const F32 boxHalfWidth = getScale().x * 0.5f;
586   const F32 boxHalfHeight = getScale().z * 0.5f;
587
588   const Point3F center = getTransform().getPosition();
589   const Point3F up = getTransform().getUpVector() * boxHalfHeight;
590   const Point3F right = getTransform().getRightVector() * boxHalfWidth;
591
592   // Update the portal polygon and plane.
593
594   if( mIsBox )
595   {
596      // It's a box so the portal polygon is a rectangle.
597      // Simply compute the corner points by stepping from the
598      // center to the corners using the up and right vector.
599
600      mPortalPolygonWS.setSize( 4 );
601
602      mPortalPolygonWS[ 0 ] = center + right - up; // bottom right
603      mPortalPolygonWS[ 1 ] = center - right - up; // bottom left
604      mPortalPolygonWS[ 2 ] = center - right + up; // top left
605      mPortalPolygonWS[ 3 ] = center + right + up; // top right
606
607      // Update the plane by going through three of the points.
608
609      mPortalPlane = PlaneF(
610         mPortalPolygonWS[ 0 ],
611         mPortalPolygonWS[ 1 ],
612         mPortalPolygonWS[ 2 ]
613      );
614   }
615   else
616   {
617      // It's not necessarily a box so we must use the general
618      // routine.
619
620      // Update the portal plane by building a plane that cuts the
621      // OBB in half vertically along its Y axis.
622
623      mPortalPlane = PlaneF(
624         center + right - up,
625         center - right - up,
626         center - right + up
627      );
628
629      // Slice the polyhedron along the same plane in object space.
630
631      const PlaneF slicePlane = PlaneF( Point3F::Zero, Point3F( 0.f, 1.f, 0.f ) );
632
633      mPortalPolygonWS.setSize( mPolyhedron.getNumEdges() );
634      U32 numPoints = mPolyhedron.constructIntersection( slicePlane, mPortalPolygonWS.address(), mPortalPolygonWS.size() );
635      mPortalPolygonWS.setSize( numPoints );
636
637      // Transform the polygon to world space.
638
639      for( U32 i = 0; i < numPoints; ++ i )
640      {
641         mPortalPolygonWS[ i ].convolve( getScale() );
642         mObjToWorld.mulP( mPortalPolygonWS[ i ] );
643      }
644   }
645
646   mIsGeometryDirty = false;
647}
648
649//-----------------------------------------------------------------------------
650
651void Portal::_updateConnectivity()
652{
653   SceneZoneSpaceManager* zoneManager = getSceneManager()->getZoneManager();
654   if( !zoneManager )
655      return;
656
657   // Find out where our connected zones are in respect to the portal
658   // plane.
659
660   bool haveInteriorZonesOnFrontSide = false;
661   bool haveInteriorZonesOnBackSide = false;
662   bool isConnectedToRootZone = ( mClassification == ExteriorPortal );
663
664   for(  ZoneSpaceRef* ref = mConnectedZoneSpaces;
665         ref != NULL; ref = ref->mNext )
666   {
667      SceneZoneSpace* zone = dynamic_cast< SceneZoneSpace* >( ref->mZoneSpace );
668      if( !zone || zone->isRootZone() )
669         continue;
670
671      if( getSideRelativeToPortalPlane( zone->getPosition() ) == FrontSide )
672         haveInteriorZonesOnFrontSide = true;
673      else
674         haveInteriorZonesOnBackSide = true;
675   }
676
677   // If we have zones connected to us on only one side, we are an exterior
678   // portal.  Otherwise, we're an interior portal.
679
680   SceneRootZone* rootZone = zoneManager->getRootZone();
681   if( haveInteriorZonesOnFrontSide && haveInteriorZonesOnBackSide )
682   {
683      mClassification = InteriorPortal;
684   }
685   else if( haveInteriorZonesOnFrontSide || haveInteriorZonesOnBackSide )
686   {
687      mClassification = ExteriorPortal;
688
689      // Remember where our interior zones are.
690
691      if( haveInteriorZonesOnBackSide )
692         mInteriorSide = BackSide;
693      else
694         mInteriorSide = FrontSide;
695
696      // If we aren't currently connected to the root zone,
697      // establish the connection now.
698
699      if( !isConnectedToRootZone )
700      {
701         Parent::connectZoneSpace( rootZone );
702         rootZone->connectZoneSpace( this );
703      }
704   }
705   else
706      mClassification = InvalidPortal;
707
708   // If we have been connected to the outdoor zone already but the
709   // portal got classified as invalid or interior now, break the
710   // connection to the outdoor zone.
711
712   if( isConnectedToRootZone &&
713       ( mClassification == InvalidPortal || mClassification == InteriorPortal ) )
714   {
715      Parent::disconnectZoneSpace( rootZone );
716      rootZone->disconnectZoneSpace( this );
717   }
718}
719
720//-----------------------------------------------------------------------------
721
722Portal::Side Portal::getSideRelativeToPortalPlane( const Point3F& point ) const
723{
724   // For our purposes, we consider PlaneF::Front and PlaneF::On
725   // placement as FrontSide.
726
727   PlaneF::Side planeSide = getPortalPlane().whichSide( point );
728   if( planeSide == PlaneF::Front || planeSide == PlaneF::On )
729      return FrontSide;
730   else
731      return BackSide;
732}
733
734//-----------------------------------------------------------------------------
735
736bool Portal::_setFrontSidePassable( void* object, const char* index, const char* data )
737{
738   Portal* portal = reinterpret_cast< Portal* >( object );
739   portal->setSidePassable( Portal::FrontSide, EngineUnmarshallData< bool>()( data ) );
740   return false;
741}
742
743//-----------------------------------------------------------------------------
744
745bool Portal::_setBackSidePassable( void* object, const char* index, const char* data )
746{
747   Portal* portal = reinterpret_cast< Portal* >( object );
748   portal->setSidePassable( Portal::BackSide, EngineUnmarshallData< bool>()( data ) );
749   return false;
750}
751
752//=============================================================================
753//    Console API.
754//=============================================================================
755// MARK: ---- Console API ----
756
757//-----------------------------------------------------------------------------
758
759DefineEngineMethod( Portal, isInteriorPortal, bool, (),,
760   "Test whether the portal connects interior zones only.\n\n"
761   "@return True if the portal is an interior portal." )
762{
763   return object->isInteriorPortal();
764}
765
766//-----------------------------------------------------------------------------
767
768DefineEngineMethod( Portal, isExteriorPortal, bool, (),,
769   "Test whether the portal connects interior zones to the outdoor zone.\n\n"
770   "@return True if the portal is an exterior portal." )
771{
772   return object->isExteriorPortal();
773}
774