///
/// This file is part of ILNumerics Community Edition.
///
/// ILNumerics Community Edition - high performance computing for applications.
/// Copyright (C) 2006 - 2012 Haymo Kutschbach, http://ilnumerics.net
///
/// ILNumerics Community Edition is free software: you can redistribute it and/or modify
/// it under the terms of the GNU General Public License version 3 as published by
/// the Free Software Foundation.
///
/// ILNumerics Community Edition is distributed in the hope that it will be useful,
/// but WITHOUT ANY WARRANTY; without even the implied warranty of
/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
/// GNU General Public License for more details.
///
/// You should have received a copy of the GNU General Public License
/// along with ILNumerics Community Edition. See the file License.txt in the root
/// of your distribution package. If not, see .
///
/// In addition this software uses the following components and/or licenses:
///
/// =================================================================================
/// The Open Toolkit Library License
///
/// Copyright (c) 2006 - 2009 the Open Toolkit library.
///
/// Permission is hereby granted, free of charge, to any person obtaining a copy
/// of this software and associated documentation files (the "Software"), to deal
/// in the Software without restriction, including without limitation the rights to
/// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
/// the Software, and to permit persons to whom the Software is furnished to do
/// so, subject to the following conditions:
///
/// The above copyright notice and this permission notice shall be included in all
/// copies or substantial portions of the Software.
///
/// =================================================================================
///
using System;
using System.Collections.Generic;
using System.Text;
using ILNumerics.Drawing.Misc;
namespace ILNumerics.Drawing {
///
/// Class holding and managing limits for a 3 dimensional cube
///
public sealed class ILClippingData {
#region eventing
///
/// fires if the data range have changed
///
public event ILClippingDataChangedEvent Changed;
///
/// called if the limits have changed
///
private void OnChange() {
if (m_eventingActive && Changed != null) {
Changed ( this, new ClippingChangedEventArgs(this));
}
m_isDirty = false;
}
#endregion
#region attributes
private bool m_eventingActive = true;
private float m_xMin = float.MaxValue;
private float m_yMin = float.MaxValue;
private float m_zMin = float.MaxValue;
private float m_xMax = float.MinValue;
private float m_yMax = float.MinValue;
private float m_zMax = float.MinValue;
private float m_sphereRadius;
private bool m_isDirty = false;
private bool m_allowZeroVolume = true;
#endregion
#region properties
///
/// the radius of a sphere tightly enclosing the box determined by this clipping data limits (readonly)
///
public float SphereRadius {
get {
return m_sphereRadius;
}
}
///
/// minimum value for x axis
///
public float XMin {
get {
return m_xMin;
}
set {
if (m_xMin != value) {
m_isDirty = true;
m_xMin = value;
if (!m_allowZeroVolume) ensureVolumeNotZero();
m_sphereRadius = getSphereRadius();
if (m_eventingActive && Changed != null)
OnChange();
}
}
}
///
/// minimum value for y axis
///
public float YMin {
get {
return m_yMin;
}
set {
if (m_yMin != value) {
m_isDirty = true;
m_yMin = value;
if (!m_allowZeroVolume) ensureVolumeNotZero();
m_sphereRadius = getSphereRadius();
if (m_eventingActive && Changed != null)
OnChange();
}
}
}
///
/// minimum value for z axis
///
public float ZMin {
get {
return m_zMin;
}
set {
if (m_zMin != value) {
m_isDirty = true;
m_zMin = value;
if (!m_allowZeroVolume) ensureVolumeNotZero();
m_sphereRadius = getSphereRadius();
if (m_eventingActive && Changed != null)
OnChange();
}
}
}
///
/// maximum value for x axis
///
public float XMax {
get {
return m_xMax;
}
set {
if (m_xMax != value) {
m_isDirty = true;
m_xMax = value;
if (!m_allowZeroVolume) ensureVolumeNotZero();
m_sphereRadius = getSphereRadius();
if (m_eventingActive && Changed != null)
OnChange();
}
}
}
///
/// maximum value for y axis
///
public float YMax {
get {
return m_yMax;
}
set {
if (m_yMax != value) {
m_isDirty = true;
m_yMax = value;
if (!m_allowZeroVolume) ensureVolumeNotZero();
m_sphereRadius = getSphereRadius();
if (m_eventingActive && Changed != null)
OnChange();
}
}
}
///
/// maximum value for z axis
///
public float ZMax {
get {
return m_zMax;
}
set {
if (m_zMax != value) {
m_isDirty = true;
m_zMax = value;
if (!m_allowZeroVolume) ensureVolumeNotZero();
m_sphereRadius = getSphereRadius();
if (m_eventingActive && Changed != null)
OnChange();
}
}
}
///
/// minimum (coordinate)
///
public ILPoint3Df Min {
get {
return new ILPoint3Df(XMin,YMin,ZMin);
}
}
///
/// maximum (coordinate)
///
public ILPoint3Df Max {
get {
return new ILPoint3Df(XMax,YMax,ZMax);
}
}
///
/// get center of this clipping range
///
public ILPoint3Df CenterF {
get {
ILPoint3Df ret;
ret.X = (XMax + XMin) / 2.0f;
ret.Y = (YMax + YMin) / 2.0f;
ret.Z = (ZMax + ZMin) / 2.0f;
return ret;
}
}
///
/// get width (x-direction) of this clipping range
///
public float WidthF {
get {
return (XMax - XMin);
}
}
///
/// get height (y-direction) of this clipping range
///
public float HeightF {
get {
return (YMax - YMin);
}
}
///
/// get depth (z-direction) of this clipping range
///
public float DepthF {
get {
return (ZMax - ZMin);
}
}
///
/// marks the limits as altered, without having fired a changed event yet
///
public bool IsDirty {
get {
return m_isDirty;
}
}
///
/// true: this clipping data always ensures a non-zero volume
///
/// 'NonZeroVolumne' means, non of Depth,Width nor Heigth are allowed to be zero. If some edge of the cube is set to zero, the class expands this edge by 1 in each direction.
public bool AllowZeroVolume {
get {
return m_allowZeroVolume;
}
set {
m_allowZeroVolume = value;
}
}
#endregion
#region public interface
///
/// suspend the firing of events until EventingResume has been called
///
public void EventingSuspend() {
m_eventingActive = false;
m_isDirty = false;
}
///
/// Resume previously suspended eventing. Start sending events again.
///
public void EventingResume() {
m_eventingActive = true;
if (m_isDirty && Changed != null) {
OnChange();
}
}
///
/// enable eventing without sending pending events
///
public void EventingStart() {
m_eventingActive = true;
m_isDirty = false;
}
///
/// update ranges for this object with union of both ranges.
///
/// clipping ranges to create union with
public void Update (ILClippingData clipData) {
if (clipData.XMin < XMin) { m_isDirty = true; m_xMin = clipData.XMin; }
if (clipData.YMin < YMin) { m_isDirty = true; m_yMin = clipData.YMin; }
if (clipData.ZMin < ZMin) { m_isDirty = true; m_zMin = clipData.ZMin; }
if (clipData.XMax > XMax) { m_isDirty = true; m_xMax = clipData.XMax; }
if (clipData.YMax > YMax) { m_isDirty = true; m_yMax = clipData.YMax; }
if (clipData.ZMax > ZMax) { m_isDirty = true; m_zMax = clipData.ZMax; }
if (!m_allowZeroVolume) ensureVolumeNotZero();
m_sphereRadius = getSphereRadius();
if (m_isDirty && m_eventingActive && Changed != null)
OnChange();
}
///
/// update ranges for this object with point coords for specific axes
///
/// point with coords to update ranges with
/// bitflag combination to specify axis to be recognized: 1,2,4 -> x,y,z
public void Update (ILPoint3Df point, int updateBitFlags) {
if ((updateBitFlags & 1) != 0) {
if (point.X < XMin) { m_isDirty = true; m_xMin = point.X; }
if (point.X > XMax) { m_isDirty = true; m_xMax = point.X; }
}
if ((updateBitFlags & 2) != 0) {
if (point.Y < YMin) { m_isDirty = true; m_yMin = point.Y; }
if (point.Y > YMax) { m_isDirty = true; m_yMax = point.Y; }
}
if ((updateBitFlags & 4) != 0) {
if (point.Z < ZMin) { m_isDirty = true; m_zMin = point.Z; }
if (point.Z > ZMax) { m_isDirty = true; m_zMax = point.Z; }
}
if (!m_allowZeroVolume) ensureVolumeNotZero();
m_sphereRadius = getSphereRadius();
if (m_isDirty && m_eventingActive && Changed != null)
OnChange();
}
///
/// update clipping data for this object with union of this and rectangle specified
///
/// left upper corner
/// right lower corner
public void Update (ILPoint3Df luCorner, ILPoint3Df rbCorner) {
bool oldeventState = m_eventingActive;
m_eventingActive = false;
Update(luCorner,7);
m_eventingActive = oldeventState;
Update(rbCorner,7);
}
public void Update (ILPoint3Df center, float zoomFactor) {
if (zoomFactor == 1.0f && center == CenterF) return;
m_isDirty = true;
float s = WidthF * zoomFactor / 2;
m_xMin = center.X - s;
m_xMax = center.X + s;
s = HeightF * zoomFactor / 2;
m_yMin = center.Y - s;
m_yMax = center.Y + s;
s = DepthF * zoomFactor / 2;
m_zMin = center.Z - s;
m_zMax = center.Z + s;
if (!m_allowZeroVolume) ensureVolumeNotZero();
m_sphereRadius = getSphereRadius();
if (m_eventingActive && Changed != null)
OnChange();
}
///
/// Set clipping limits to volume inside the box specified
///
/// left-upper-near corner of the volume box
/// right-bottom-far corner of the volume box
public void Set(ILPoint3Df lunCorner, ILPoint3Df rbfCorner) {
m_isDirty = true;
m_xMin = Math.Min(lunCorner.X,rbfCorner.X);
m_xMax = Math.Max(lunCorner.X,rbfCorner.X);
m_yMin = Math.Min(lunCorner.Y,rbfCorner.Y);
m_yMax = Math.Max(lunCorner.Y,rbfCorner.Y);
m_zMin = Math.Min(lunCorner.Z,rbfCorner.Z);
m_zMax = Math.Max(lunCorner.Z,rbfCorner.Z);
if (!m_allowZeroVolume) ensureVolumeNotZero();
m_sphereRadius = getSphereRadius();
if (m_eventingActive && Changed != null)
OnChange();
}
///
/// reset this clipping range to initial (all empty)
///
public void Reset() {
m_xMin = float.MaxValue;
m_yMin = float.MaxValue;
m_zMin = float.MaxValue;
m_xMax = float.MinValue;
m_yMax = float.MinValue;
m_zMax = float.MinValue;
m_isDirty = true;
if (m_eventingActive && Changed != null)
OnChange();
}
///
/// copy this from other clipping data
///
///
internal void CopyFrom(ILClippingData m_clippingData) {
m_xMin = m_clippingData.XMin;
m_yMin = m_clippingData.YMin;
m_zMin = m_clippingData.ZMin;
m_xMax = m_clippingData.XMax;
m_yMax = m_clippingData.YMax;
m_zMax = m_clippingData.ZMax;
if (!m_allowZeroVolume) ensureVolumeNotZero();
m_sphereRadius = getSphereRadius();
m_isDirty = true;
if (m_eventingActive && Changed != null)
OnChange();
}
///
/// stretch clipping region to unit cube [0 1][0 1][0 1]
///
///
public ILPoint3Df ScaleToUnitCube () {
ILPoint3Df ret = new ILPoint3Df();
float a = WidthF;
if (a != 0.0f) ret.X = 1 / a;
else ret.X = 0.0f;
a = HeightF;
if (a != 0.0f) ret.Y = 1 / a;
else ret.Y = 0.0f;
a = DepthF;
if (a != 0.0f) ret.Z = 1 / a;
else ret.Z = 0.0f;
return ret;
}
///
/// offset centering scaled unit cube to zero: [-0.5 0.5][-0.5 0.5][-0.5 0.5]
///
///
public ILPoint3Df CenterToUnitCube() {
ILPoint3Df a = ScaleToUnitCube();
ILPoint3Df b = new ILPoint3Df();
b.X = -0.5f - a.X * XMin;
b.Y = -0.5f - a.Y * YMin;
if (ZMin < ZMax)
b.Z = (-0.5f - a.Z * ZMin);
else b.Z = 0.0f;
return b;
}
///
/// creates clone of this clipping data
///
/// clone
public ILClippingData Clone() {
ILClippingData ret = new ILClippingData();
ret.Update(this);
return ret;
}
///
/// Map coordinat from unit cube space [-0.5..0.5] into the space limited by this clipping data
///
/// x coordinate (unit cube space: -0.5 ... 0.5)
/// y coordinate (unit cube space: -0.5 ... 0.5)
/// z coordinate (unit cube space: -0.5 ... 0.5)
///
public ILPoint3Df Map(float x, float y, float z) {
ILPoint3Df ret;
ret.X = (x + 0.5f) * (m_xMax-m_xMin) + m_xMin;
ret.Y = (y + 0.5f) * (m_yMax-m_yMin) + m_yMin;
ret.Z = (z + 0.5f) * (m_zMax-m_zMin) + m_zMin;
//if (!m_allowZeroVolume) ensureVolumeNotZero();
return ret;
}
public override string ToString() {
return String.Format("Min:{0} Max:{1}",Min,Max);
}
///
/// Expand/shrink all those edges, not touched by the given line
///
/// near line point
/// far line point
/// multiplicator, shrink-/expand value
public void GetZoomParameter(ILPoint3Df nearLineEnd, ILPoint3Df farLineEnd, float offset,
out ILPoint3Df minCorner, out ILPoint3Df maxCorner) {
// we determine, if the side is touched by the line.
// if 'yes': the side is skipped (its limit/position are kept)
// if 'no': the side is moved towards/away from center
minCorner = this.Min;
maxCorner = this.Max;
float sX, sY, sZ;
float lamb;
float aX = nearLineEnd.X - farLineEnd.X;
float aY = nearLineEnd.Y - farLineEnd.Y;
float aZ = nearLineEnd.Z - farLineEnd.Z;
float offX = WidthF * (offset/2.0f);
float offY = HeightF * (offset/2.0f);
float offZ = DepthF * (offset/2.0f);
#region front side
lamb = (m_yMin - nearLineEnd.Y) / aY;
sX = nearLineEnd.X + lamb * aX;
sZ = nearLineEnd.Z + lamb * aZ;
if (sX > m_xMax || sX < m_xMin || sZ > m_zMax || sZ < m_zMin) {
minCorner.Y += offY;
}
#endregion
#region right side
lamb = (m_xMax - nearLineEnd.X) / aX;
sY = nearLineEnd.Y + lamb * aY;
sZ = nearLineEnd.Z + lamb * aZ;
if (sY > m_yMax || sY < m_yMin || sZ > m_zMax || sZ < m_zMin) {
maxCorner.X -= offX;
}
#endregion
#region back side
lamb = (m_yMax - nearLineEnd.Y) / aY;
sX = nearLineEnd.X + lamb * aX;
sZ = nearLineEnd.Z + lamb * aZ;
if (sX > m_xMax || sX < m_xMin || sZ > m_zMax || sZ < m_zMin) {
maxCorner.Y -= offY;
}
#endregion
#region left side
lamb = (m_xMin - nearLineEnd.X) / aX;
sY = nearLineEnd.Y + lamb * aY;
sZ = nearLineEnd.Z + lamb * aZ;
if (sY > m_yMax || sY < m_yMin || sZ > m_zMax || sZ < m_zMin) {
minCorner.X += offX;
}
#endregion
#region top side
lamb = (m_zMax - nearLineEnd.Z) / aZ;
sX = nearLineEnd.X + lamb * aX;
sY = nearLineEnd.Y + lamb * aY;
if (sY > m_yMax || sY < m_yMin || sX > m_xMax || sX < m_xMin) {
maxCorner.Z -= offZ;
}
#endregion
#region bottom side
lamb = (m_zMin - nearLineEnd.Z) / aZ;
sX = nearLineEnd.X + lamb * aX;
sY = nearLineEnd.Y + lamb * aY;
if (sY > m_yMax || sY < m_yMin || sX > m_xMax || sX < m_xMin) {
minCorner.Z += offZ;
}
#endregion
#region translate: move line to cross middle of view cube
ILPoint3Df rQ = CenterF;
sX = rQ.X - nearLineEnd.X;
sY = rQ.Y - nearLineEnd.Y;
sZ = rQ.Z - nearLineEnd.Z;
lamb = (aX * sX + aY * sY + aZ * sZ) / (aX * aX + aY * aY + aZ * aZ);
offX = rQ.X - (nearLineEnd.X + lamb * aX);
offY = rQ.Y - (nearLineEnd.Y + lamb * aY);
offZ = rQ.Z - (nearLineEnd.Z + lamb * aZ);
minCorner.X -= offX;
minCorner.Y -= offY;
minCorner.Z -= offZ;
maxCorner.X -= offX;
maxCorner.Y -= offY;
maxCorner.Z -= offZ;
#endregion
}
#endregion
#region private helper
private float getSphereRadius() {
return ((Max - Min) / 2f).GetLength();
}
///
/// Ensure that this clipping data has valid length for all dimensions [deprecated]
///
/// If the length of the clipping cube is infinity for any dimension,
/// that dimension is set to a range of -0.5...0.5.
/// This function is to be called by custom graphs which create their
/// size relative to the size of the clipping container. Those
/// graphs will need a valid container size and may call this function in the
/// constructor.
/// true if the length in any dimension had to be corrected (set to 1.0), false otherwise.
private bool EnsureValidSize() {
bool change = false;
if (float.IsInfinity(WidthF)) {
change = true;
m_xMin = -0.5f;
m_xMax = 0.5f;
}
if (float.IsInfinity(HeightF)) {
change = true;
m_yMin = -0.5f;
m_yMax = 0.5f;
}
if (float.IsInfinity(DepthF)) {
change = true;
m_zMin = -0.5f;
m_zMax = 0.5f;
}
if (change && m_eventingActive && Changed != null) {
OnChange();
}
return change;
}
private void ensureVolumeNotZero() {
if (HeightF == 0) {
m_isDirty = true;
m_yMin = m_yMin - 1f;
m_yMax = m_yMin + 2f;
}
if (WidthF == 0) {
m_isDirty = true;
m_xMin = m_xMin - 1f;
m_xMax = m_xMin + 2f;
}
if (DepthF == 0) {
m_isDirty = true;
m_zMin = m_zMin - 1f;
m_zMax = m_zMin + 2f;
}
}
#endregion
#region operator overloads
///
/// Equalty operator overload, true if both cubes span the same region in 3D space
///
/// cube 1
/// cube 2
/// true if both cubes span the same 3D space, false otherwise
public static bool operator == (ILClippingData limit1, ILClippingData limit2) {
return (limit1.m_xMax == limit2.m_xMax &&
limit1.m_yMax == limit2.m_yMax &&
limit1.m_zMax == limit2.m_zMax &&
limit1.m_xMin == limit2.m_xMin &&
limit1.m_yMin == limit2.m_yMin &&
limit1.m_zMin == limit2.m_zMin);
}
///
/// unequalty operator
///
/// cube 1
/// cube 2
/// false if both cubes span the same 3D space, true otherwise
public static bool operator !=(ILClippingData limit1, ILClippingData limit2) {
return (limit1.m_xMax != limit2.m_xMax ||
limit1.m_yMax != limit2.m_yMax ||
limit1.m_zMax != limit2.m_zMax ||
limit1.m_xMin != limit2.m_xMin ||
limit1.m_yMin != limit2.m_yMin ||
limit1.m_zMin != limit2.m_zMin);
}
///
/// Returns hash code for this ILClippingData
///
/// hash code
public override int GetHashCode() {
return base.GetHashCode();
}
///
/// Compares to cube objects
///
///
/// true if obj references this class instance, false otherwise
public override bool Equals(object obj) {
return base.Equals(obj);
}
#endregion
}
}