2017-07-11 08:10:57 -07:00
/ *
2019-01-31 07:33:42 -08:00
Copyright ( c ) 2019 , Lars Brubaker , John Lewin
2017-07-11 08:10:57 -07:00
All rights reserved .
Redistribution and use in source and binary forms , with or without
modification , are permitted provided that the following conditions are met :
1. Redistributions of source code must retain the above copyright notice , this
list of conditions and the following disclaimer .
2. Redistributions in binary form must reproduce the above copyright notice ,
this list of conditions and the following disclaimer in the documentation
and / or other materials provided with the distribution .
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED . IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT , INDIRECT , INCIDENTAL , SPECIAL , EXEMPLARY , OR CONSEQUENTIAL DAMAGES
( INCLUDING , BUT NOT LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES ;
LOSS OF USE , DATA , OR PROFITS ; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY , WHETHER IN CONTRACT , STRICT LIABILITY , OR TORT
( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE .
The views and conclusions contained in the software and documentation are those
of the authors and should not be interpreted as representing official policies ,
either expressed or implied , of the FreeBSD Project .
* /
2017-08-18 08:34:05 -07:00
2017-07-11 08:10:57 -07:00
using System ;
using System.Collections.Generic ;
2019-02-01 11:59:29 -08:00
using System.Linq ;
2017-08-18 08:34:05 -07:00
using MatterHackers.Agg ;
2019-01-31 07:33:42 -08:00
using MatterHackers.Agg.Image ;
2017-07-11 08:10:57 -07:00
using MatterHackers.Agg.UI ;
2017-12-19 16:57:59 -08:00
using MatterHackers.DataConverters3D ;
2019-02-02 19:43:50 -08:00
using MatterHackers.MatterControl.DesignTools ;
2017-07-11 08:10:57 -07:00
using MatterHackers.MeshVisualizer ;
using MatterHackers.RayTracer ;
using MatterHackers.RayTracer.Traceable ;
2018-03-23 16:42:25 -07:00
using MatterHackers.RenderOpenGl ;
2017-07-11 08:10:57 -07:00
using MatterHackers.VectorMath ;
namespace MatterHackers.MatterControl.PartPreviewWindow
{
2019-01-31 07:33:42 -08:00
public partial class InteractionLayer : GuiWidget , IInteractionVolumeContext
2017-07-11 08:10:57 -07:00
{
2019-02-02 14:59:14 -08:00
private InteractionVolume mouseDownIAVolume = null ;
2017-07-11 08:10:57 -07:00
2019-02-02 19:43:50 -08:00
/// <summary>
/// Contains type to IAVolume mappings
/// </summary>
private Dictionary < Type , List < InteractionVolume > > iavMappings = new Dictionary < Type , List < InteractionVolume > > ( ) ;
2019-02-02 20:08:22 -08:00
/// <summary>
/// Interaction Volume Overrides for the selected scene item
/// </summary>
private List < InteractionVolume > iavOverrides = null ;
private Type selectedItemType ;
2019-02-01 16:12:12 -08:00
public WorldView World = > sceneContext . World ;
2017-07-11 08:10:57 -07:00
2019-02-01 16:12:12 -08:00
public InteractiveScene Scene = > sceneContext . Scene ;
2017-07-11 08:10:57 -07:00
2019-02-04 16:17:31 -08:00
public bool DrawOpenGLContent { get ; set ; } = true ;
2017-08-18 08:34:05 -07:00
2019-02-02 19:43:50 -08:00
private List < InteractionVolume > registeredIAVolumes = new List < InteractionVolume > ( ) ;
public IEnumerable < InteractionVolume > InteractionVolumes
{
get
{
if ( selectedItemType = = null )
{
return Enumerable . Empty < InteractionVolume > ( ) ;
}
else
{
2019-02-02 20:08:22 -08:00
return iavOverrides ? ? registeredIAVolumes ;
2019-02-02 19:43:50 -08:00
}
}
}
2017-07-11 08:10:57 -07:00
2017-11-06 18:47:08 -08:00
private LightingData lighting = new LightingData ( ) ;
2018-02-20 15:23:55 -08:00
private GuiWidget renderSource ;
2017-08-18 08:34:05 -07:00
2019-02-01 16:12:12 -08:00
public InteractionLayer ( ISceneContext sceneContext , ThemeConfig theme , EditorType editorType = EditorType . Part )
2017-07-11 08:10:57 -07:00
{
2019-01-31 07:33:42 -08:00
this . sceneContext = sceneContext ;
this . EditorMode = editorType ;
this . theme = theme ;
2019-02-01 16:12:12 -08:00
scene = sceneContext . Scene ;
2019-01-31 07:33:42 -08:00
gCodeMeshColor = new Color ( theme . PrimaryAccentColor , 35 ) ;
BuildVolumeColor = new ColorF ( . 2 , . 8 , . 3 , . 2 ) . ToColor ( ) ;
2019-01-31 08:07:19 -08:00
floorDrawable = new FloorDrawable ( editorType , sceneContext , this . BuildVolumeColor , theme ) ;
2019-01-31 09:09:24 -08:00
2019-01-31 07:33:42 -08:00
if ( ViewOnlyTexture = = null )
{
// TODO: What is the ViewOnlyTexture???
UiThread . RunOnIdle ( ( ) = >
{
ViewOnlyTexture = new ImageBuffer ( 32 , 32 , 32 ) ;
var graphics2D = ViewOnlyTexture . NewGraphics2D ( ) ;
graphics2D . Clear ( Color . White ) ;
graphics2D . FillRectangle ( 0 , 0 , ViewOnlyTexture . Width / 2 , ViewOnlyTexture . Height , Color . LightGray ) ;
// request the texture so we can set it to repeat
var plugin = ImageGlPlugin . GetImageGlPlugin ( ViewOnlyTexture , true , true , false ) ;
} ) ;
}
2019-02-02 19:43:50 -08:00
2019-02-04 15:50:33 -08:00
iavMappings . Add ( typeof ( ImageObject3D ) , new List < InteractionVolume > { new MoveInZControl ( this ) } ) ;
2019-02-02 20:08:22 -08:00
// Register listeners
sceneContext . Scene . SelectionChanged + = this . Scene_SelectionChanged ;
2017-11-07 18:28:16 -08:00
}
2017-11-06 18:47:08 -08:00
2019-02-01 08:58:44 -08:00
public void RegisterDrawable ( IDrawable drawable )
{
2019-02-02 19:43:50 -08:00
drawables . Add ( drawable ) ;
}
public void RegisterIAVolume ( InteractionVolume interactionVolume )
{
registeredIAVolumes . Add ( interactionVolume ) ;
2019-02-01 08:58:44 -08:00
}
2019-02-04 15:50:33 -08:00
public void RegisterIAVolumes ( IEnumerable < InteractionVolume > interactionVolumes )
{
registeredIAVolumes . AddRange ( interactionVolumes ) ;
}
2019-02-01 12:45:04 -08:00
public IEnumerable < IDrawable > Drawables = > drawables ;
2019-02-02 13:51:02 -08:00
public IEnumerable < IDrawableItem > ItemDrawables = > itemDrawables ;
2017-08-18 08:34:05 -07:00
internal void SetRenderTarget ( GuiWidget renderSource )
{
2019-02-02 20:08:22 -08:00
// Unregister listener
if ( renderSource ! = null )
{
renderSource . BeforeDraw - = RenderSource_BeforeDraw ;
}
2018-02-20 15:23:55 -08:00
this . renderSource = renderSource ;
2019-01-31 07:33:42 -08:00
// Hook our drawing operation to the renderSource so we can draw unclipped in the source objects bounds. At the time of writing,
// this mechanism is needed to draw bed items under the semi-transparent right treeview/editor panel
2019-02-02 20:08:22 -08:00
renderSource . BeforeDraw + = RenderSource_BeforeDraw ;
2017-08-18 08:34:05 -07:00
}
2019-01-31 07:17:51 -08:00
// The primary draw hook. Kick off our draw operation when the renderSource fires AfterDraw
2019-02-02 20:08:22 -08:00
private void RenderSource_BeforeDraw ( object sender , DrawEventArgs e )
2017-08-18 08:34:05 -07:00
{
2019-02-04 16:17:31 -08:00
if ( this . DrawOpenGLContent )
2017-08-18 08:34:05 -07:00
{
2019-02-01 11:59:29 -08:00
this . DrawGlContent ( e ) ;
2017-08-18 08:34:05 -07:00
}
}
2019-02-02 20:08:22 -08:00
private void Scene_SelectionChanged ( object sender , EventArgs e )
{
// On selection change, update state for mappings
selectedItemType = scene . SelectedItem ? . GetType ( ) ;
iavOverrides = null ;
if ( selectedItemType ! = null )
{
iavMappings . TryGetValue ( selectedItemType , out iavOverrides ) ;
}
}
2019-02-02 14:03:13 -08:00
public static void RenderBounds ( DrawEventArgs e , WorldView world , IEnumerable < BvhIterator > allResults )
2018-01-02 15:25:40 -08:00
{
2019-02-02 14:03:13 -08:00
foreach ( var bvhIterator in allResults )
2018-01-02 15:25:40 -08:00
{
2019-02-02 14:03:13 -08:00
InteractionLayer . RenderBounds ( e , world , bvhIterator . TransformToWorld , bvhIterator . Bvh , bvhIterator . Depth ) ;
}
}
2018-01-02 15:25:40 -08:00
2019-02-02 14:03:13 -08:00
public static void RenderBounds ( DrawEventArgs e , WorldView world , Matrix4X4 transformToWorld , IBvhItem bvh , int depth = int . MinValue )
{
for ( int i = 0 ; i < 4 ; i + + )
{
Vector3 bottomStartPosition = Vector3Ex . Transform ( bvh . GetAxisAlignedBoundingBox ( ) . GetBottomCorner ( i ) , transformToWorld ) ;
var bottomStartScreenPos = world . GetScreenPosition ( bottomStartPosition ) ;
2018-01-02 15:25:40 -08:00
2019-02-02 14:03:13 -08:00
Vector3 bottomEndPosition = Vector3Ex . Transform ( bvh . GetAxisAlignedBoundingBox ( ) . GetBottomCorner ( ( i + 1 ) % 4 ) , transformToWorld ) ;
var bottomEndScreenPos = world . GetScreenPosition ( bottomEndPosition ) ;
2018-01-02 15:25:40 -08:00
2019-02-02 14:03:13 -08:00
Vector3 topStartPosition = Vector3Ex . Transform ( bvh . GetAxisAlignedBoundingBox ( ) . GetTopCorner ( i ) , transformToWorld ) ;
var topStartScreenPos = world . GetScreenPosition ( topStartPosition ) ;
2018-01-02 15:25:40 -08:00
2019-02-02 14:03:13 -08:00
Vector3 topEndPosition = Vector3Ex . Transform ( bvh . GetAxisAlignedBoundingBox ( ) . GetTopCorner ( ( i + 1 ) % 4 ) , transformToWorld ) ;
var topEndScreenPos = world . GetScreenPosition ( topEndPosition ) ;
2018-01-02 15:25:40 -08:00
2019-02-02 14:03:13 -08:00
e . Graphics2D . Line ( bottomStartScreenPos , bottomEndScreenPos , Color . Black ) ;
e . Graphics2D . Line ( topStartScreenPos , topEndScreenPos , Color . Black ) ;
e . Graphics2D . Line ( topStartScreenPos , bottomStartScreenPos , Color . Black ) ;
}
if ( bvh is ITriangle tri )
{
for ( int i = 0 ; i < 3 ; i + + )
2018-01-02 15:25:40 -08:00
{
2019-02-02 14:03:13 -08:00
var vertexPos = tri . GetVertex ( i ) ;
var screenCenter = Vector3Ex . Transform ( vertexPos , transformToWorld ) ;
var screenPos = world . GetScreenPosition ( screenCenter ) ;
e . Graphics2D . Circle ( screenPos , 3 , Color . Red ) ;
2018-01-02 15:25:40 -08:00
}
2019-02-02 14:03:13 -08:00
}
else
{
var center = bvh . GetCenter ( ) ;
var worldCenter = Vector3Ex . Transform ( center , transformToWorld ) ;
var screenPos2 = world . GetScreenPosition ( worldCenter ) ;
if ( depth ! = int . MinValue )
2018-01-02 15:25:40 -08:00
{
2018-04-11 10:22:06 -07:00
e . Graphics2D . Circle ( screenPos2 , 3 , Color . Yellow ) ;
2019-02-02 14:03:13 -08:00
e . Graphics2D . DrawString ( $"{depth}," , screenPos2 . X + 12 * depth , screenPos2 . Y ) ;
2018-01-02 15:25:40 -08:00
}
}
}
2017-07-11 08:10:57 -07:00
public override void OnMouseDown ( MouseEventArgs mouseEvent )
{
base . OnMouseDown ( mouseEvent ) ;
Ray ray = this . World . GetRayForLocalBounds ( mouseEvent . Position ) ;
IntersectInfo info ;
2018-07-13 06:55:00 -07:00
if ( this . Scene . SelectedItem ! = null
2017-07-11 08:10:57 -07:00
& & ! SuppressUiVolumes
2019-02-02 14:59:14 -08:00
& & FindInteractionVolumeHit ( ray , out mouseDownIAVolume , out info ) )
2017-07-11 08:10:57 -07:00
{
2019-02-02 14:59:14 -08:00
mouseDownIAVolume . OnMouseDown ( new MouseEvent3DArgs ( mouseEvent , ray , info ) ) ;
SelectedInteractionVolume = mouseDownIAVolume ;
2017-07-11 08:10:57 -07:00
}
else
{
SelectedInteractionVolume = null ;
}
}
public override void OnMouseMove ( MouseEventArgs mouseEvent )
{
base . OnMouseMove ( mouseEvent ) ;
2018-03-23 15:07:20 -07:00
if ( SuppressUiVolumes
2017-08-19 11:14:09 -07:00
| | ! this . PositionWithinLocalBounds ( mouseEvent . X , mouseEvent . Y ) )
2017-07-11 08:10:57 -07:00
{
return ;
}
Ray ray = this . World . GetRayForLocalBounds ( mouseEvent . Position ) ;
IntersectInfo info = null ;
2019-02-01 14:56:42 -08:00
var mouseEvent3D = new MouseEvent3DArgs ( mouseEvent , ray , info ) ;
2019-02-02 14:59:14 -08:00
if ( MouseDownOnInteractionVolume & & mouseDownIAVolume ! = null )
2017-07-11 08:10:57 -07:00
{
2019-02-02 14:59:14 -08:00
mouseDownIAVolume . OnMouseMove ( mouseEvent3D ) ;
2017-07-11 08:10:57 -07:00
}
else
{
2019-02-02 14:59:14 -08:00
this . FindInteractionVolumeHit ( ray , out InteractionVolume hitIAVolume , out info ) ;
2017-07-11 08:10:57 -07:00
2019-02-02 14:59:14 -08:00
var iaVolumes = this . InteractionVolumes ;
foreach ( var iaVolume in iaVolumes )
2017-07-11 08:10:57 -07:00
{
2019-02-02 14:59:14 -08:00
if ( hitIAVolume = = iaVolume )
2017-07-11 08:10:57 -07:00
{
2019-02-02 14:59:14 -08:00
iaVolume . MouseOver = true ;
iaVolume . MouseMoveInfo = info ;
HoveredInteractionVolume = iaVolume ;
2017-07-11 08:10:57 -07:00
}
else
{
2019-02-02 14:59:14 -08:00
iaVolume . MouseOver = false ;
iaVolume . MouseMoveInfo = null ;
2017-07-11 08:10:57 -07:00
}
2019-02-02 14:59:14 -08:00
// TODO: Why do non-hit volumes get mouse move?
iaVolume . OnMouseMove ( mouseEvent3D ) ;
2017-07-11 08:10:57 -07:00
}
}
}
public override void OnMouseUp ( MouseEventArgs mouseEvent )
{
Invalidate ( ) ;
if ( SuppressUiVolumes )
{
return ;
}
Ray ray = this . World . GetRayForLocalBounds ( mouseEvent . Position ) ;
IntersectInfo info ;
2019-02-02 14:59:14 -08:00
bool anyInteractionVolumeHit = FindInteractionVolumeHit ( ray , out InteractionVolume iaVolume , out info ) ;
2017-07-11 08:10:57 -07:00
MouseEvent3DArgs mouseEvent3D = new MouseEvent3DArgs ( mouseEvent , ray , info ) ;
2019-02-02 14:59:14 -08:00
if ( MouseDownOnInteractionVolume & & mouseDownIAVolume ! = null )
2017-07-11 08:10:57 -07:00
{
2019-02-02 14:59:14 -08:00
mouseDownIAVolume . OnMouseUp ( mouseEvent3D ) ;
2017-07-11 08:10:57 -07:00
SelectedInteractionVolume = null ;
2019-02-02 14:59:14 -08:00
mouseDownIAVolume = null ;
2017-07-11 08:10:57 -07:00
}
else
{
2019-02-02 14:59:14 -08:00
mouseDownIAVolume = null ;
2017-07-11 08:10:57 -07:00
if ( anyInteractionVolumeHit )
{
2019-02-02 14:59:14 -08:00
iaVolume . OnMouseUp ( mouseEvent3D ) ;
2017-07-11 08:10:57 -07:00
}
SelectedInteractionVolume = null ;
}
base . OnMouseUp ( mouseEvent ) ;
}
2019-02-02 14:13:18 -08:00
public override void OnClosed ( EventArgs e )
{
2019-02-02 20:08:22 -08:00
// Unregister listeners
sceneContext . Scene . SelectionChanged - = this . Scene_SelectionChanged ;
if ( renderSource ! = null )
{
renderSource . BeforeDraw - = RenderSource_BeforeDraw ;
}
2019-02-02 14:13:18 -08:00
// If implemented, invoke Dispose on Drawables
2019-02-02 20:08:22 -08:00
foreach ( var item in drawables )
2019-02-02 14:13:18 -08:00
{
if ( item is IDisposable disposable )
{
disposable . Dispose ( ) ;
}
}
foreach ( var item in itemDrawables )
{
if ( item is IDisposable disposable )
{
disposable . Dispose ( ) ;
}
}
base . OnClosed ( e ) ;
}
2019-02-02 14:59:14 -08:00
private bool FindInteractionVolumeHit ( Ray ray , out InteractionVolume hitIAVolume , out IntersectInfo info )
2017-07-11 08:10:57 -07:00
{
2019-02-02 14:59:14 -08:00
var iaVolumes = this . InteractionVolumes ;
hitIAVolume = null ;
if ( ! iaVolumes . Any ( ) )
2017-07-11 08:10:57 -07:00
{
info = null ;
return false ;
}
// TODO: Rewrite as projection without extra list
2019-02-02 14:59:14 -08:00
// - Looks like the extra list is always required as CreateNewHierachy requires a List and we can only produce an IEnumerable without the list overhead
// - var uiTraceables = iaVolumes.Where(ia => ia.CollisionVolume != null).Select(ia => new Transform(ia.CollisionVolume, ia.TotalTransform)).ToList<IPrimitive>();
var uiTraceables = new List < IPrimitive > ( ) ;
foreach ( InteractionVolume interactionVolume in iaVolumes )
2017-07-11 08:10:57 -07:00
{
if ( interactionVolume . CollisionVolume ! = null )
{
IPrimitive traceData = interactionVolume . CollisionVolume ;
uiTraceables . Add ( new Transform ( traceData , interactionVolume . TotalTransform ) ) ;
}
}
IPrimitive allUiObjects = BoundingVolumeHierarchy . CreateNewHierachy ( uiTraceables ) ;
info = allUiObjects . GetClosestIntersection ( ray ) ;
if ( info ! = null )
{
2019-02-02 14:59:14 -08:00
foreach ( var iaVolume in iaVolumes )
2017-07-11 08:10:57 -07:00
{
2019-02-02 14:59:14 -08:00
var insideBounds = new List < IBvhItem > ( ) ;
if ( iaVolume . CollisionVolume ! = null )
2017-07-11 08:10:57 -07:00
{
2019-02-02 14:59:14 -08:00
iaVolume . CollisionVolume . GetContained ( insideBounds , info . closestHitObject . GetAxisAlignedBoundingBox ( ) ) ;
2017-07-11 08:10:57 -07:00
if ( insideBounds . Contains ( info . closestHitObject ) )
{
2019-02-02 14:59:14 -08:00
hitIAVolume = iaVolume ;
2017-07-11 08:10:57 -07:00
return true ;
}
}
}
}
return false ;
}
public bool SuppressUiVolumes { get ; set ; } = false ;
public bool MouseDownOnInteractionVolume = > SelectedInteractionVolume ! = null ;
public InteractionVolume SelectedInteractionVolume { get ; set ; } = null ;
public InteractionVolume HoveredInteractionVolume { get ; set ; } = null ;
2018-09-17 17:07:42 -07:00
public double SnapGridDistance
{
get
{
if ( string . IsNullOrEmpty ( UserSettings . Instance . get ( UserSettingsKey . SnapGridDistance ) ) )
{
return 1 ;
}
return UserSettings . Instance . GetValue < double > ( UserSettingsKey . SnapGridDistance ) ;
}
set
{
UserSettings . Instance . set ( UserSettingsKey . SnapGridDistance , value . ToString ( ) ) ;
}
}
2017-07-11 08:10:57 -07:00
public GuiWidget GuiSurface = > this ;
}
}