リード開発メモ

大阪のソフトウェア会社です。 技術的な事柄についてのメモとしてブログを始めます。

2013年09月

ショートカットにアイコンを設定する

最近、同じプログラムを複数立ち上げることが多い。プログラムは設定ファイルによって動作を変えてあるのだが、そのときタスクバーに同じアイコンが並び、見分けがつかなくて困っていた。

今さらながら、こういう場合はプログラムへのショートカットを作り、ショートカットにアイコンを設定すればいいことに気づいた。プログラムの起動は常にショートカット経由で行うことで、タスクバーには同じプログラムだが違うアイコンが並ぶ。

ショートカットのアイコンの設定は、ショートカットで右クリックし、[プロパティ] を選んで、[ショートカット]タブの[アイコンの変更]ボタンから行う。

アイコン画像は既存のものを利用してもいいが、自分で作ることもできる。
アイコン画像の作成方法は、
  1. Windows 標準のペイントなどでビットマップ画像を作成し、拡張子を ico に変える。
  2. 専用ツールでアイコンファイルを作成(拡張子 ico)。
のどちらか。

1. の方法はお手軽だが、透過色は設定できない。また、左上(座標0,0)に使った色が黒になってしまうようだ。気になるなら枠線を付けるなどうまくデザインで逃げるしかない。

2. は GIMP を使うのがおすすめ。画像作成とアイコンファイルの出力が GIMP  だけでできる。
GIMP で透過色を設定するのは、[色]メニュー - [色を透明度に] から。
また、アイコンファイルの出力は、[ファイル]メニュー - [エクスポート] をクリックし、右下にあるコンボボックスで[Microsoft Windows アイコン(*.ico) ] を選んで、[エクスポート]ボタンを押す。

以上。

WPF で 3D(その10)

今回は3次元空間でのインバース・キネマティクス(Inverse Kinematics、IK)を試してみる。
(ただし、3次元空間でのインバース・キネマティクスの考え方が正しいかどうかは分かりません。もし間違いがあればご指摘ください。)

インバース・キネマティクスとは、末端の位置を決めたあと、関節の位置を決める手順のこと。たとえば肩と手の位置を決めたあと、肘の位置を決めるなど。末端が目標に近づくように、各関節を微調整することを繰り返すというもの。

インバース・キネマッティクスのクラスを作成した。以下のとおり。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media.Media3D;
 
namespace sample1
{
    public class Node
    {
        public int NodeType;
        public Point3D Point;
        public double Length;
        public double Angle;
        public Vector3D OriginalRotateAxis;
        public Vector3D RotateAxis;
        public Quaternion Quaternion;
 
        public Node(int nodeType, Point3D position, double length, double initialAngle)
        {
            this.NodeType = nodeType;
            this.Point = position;
            this.Length = length;
            this.Angle = initialAngle;
            if (this.NodeType == 0)
            {
                this.OriginalRotateAxis = new Vector3D(0, 0, 1);
            }
            else if (this.NodeType == 1)
            {
                this.OriginalRotateAxis = new Vector3D(0, 1, 0);
            }
 
            this.RotateAxis = this.OriginalRotateAxis;
        }
    }
 
    public static class InverseKinematics
    {
        public static bool MoveNodes(Node[] nodes, double moveAngle, Point3D target)
        {
            // 先端ノード
            Node tipNode = nodes[nodes.Length - 1];
 
            for(int i = nodes.Length - 2; i >= 0; i--)
            {
                Node currentNode = nodes[i];
 
                // 目標ベクトル(自ノードから目標へ向かうベクトル)
                Vector3D targetVector = target - currentNode.Point;
 
                // 先端ベクトル(自ノードから先端ノードへ向かうベクトル)
                Vector3D tipVector = tipNode.Point - currentNode.Point;
 
                // 目標ベクトルと先端ベクトルのなす角を求める
                double deltaAngle = 0;
                {
                    var q2 = new Quaternion();
                    var axis = Vector3D.CrossProduct(currentNode.RotateAxis, currentNode.OriginalRotateAxis);
                    if (axis.Length > 0)
                    {
                        var angle = Vector3D.AngleBetween(currentNode.RotateAxis, currentNode.OriginalRotateAxis);
                        q2 = new Quaternion(axis, angle);
                    }
                    var m2 = new Matrix3D();
                    m2.Rotate(q2);
                    var targetVector2 = m2.Transform(targetVector);
                    var tipVector2 = m2.Transform(tipVector);
                    if (currentNode.NodeType == 0)
                    {
                        Vector3D a = new Vector3D(targetVector2.X, targetVector2.Y, 0);
                        Vector3D b = new Vector3D(tipVector2.X, tipVector2.Y, 0);
                        deltaAngle = Vector3D.AngleBetween(a, b);
                        var c = Vector3D.CrossProduct(a, b);
                        if (c.Z > 0)
                        {
                            deltaAngle = -deltaAngle;
                        }
                    }
                    else
                    {
                        Vector3D a = new Vector3D(targetVector2.X, 0, targetVector2.Z);
                        Vector3D b = new Vector3D(tipVector2.X, 0, tipVector2.Z);
                        deltaAngle = Vector3D.AngleBetween(a, b);
                        var c = Vector3D.CrossProduct(a, b);
                        if (c.Y > 0)
                        {
                            deltaAngle = -deltaAngle;
                        }
                    }
                }
 
                if (deltaAngle == 0)
                {
                    // 動かす必要なし
                    continue;
                }
 
                // 1ステップで動かす角を制限する
                if (deltaAngle > moveAngle)
                {
                    deltaAngle = moveAngle;
                }
                else if (deltaAngle < -moveAngle)
                {
                    deltaAngle = -moveAngle;
                }
 
                // 自ノードに回転角を足しこみ、先端ノードをすこし動かす
                currentNode.Angle += deltaAngle;
                var axisAngle = new AxisAngleRotation3D(currentNode.RotateAxis, deltaAngle);
                var rt = new RotateTransform3D(axisAngle, new Point3D(0, 0, 0));
                Vector3D deltaVector = rt.Transform(tipVector);
                tipNode.Point = currentNode.Point + deltaVector;
            }
 
            // 各ノードの回転角を適用し、新しい位置を求める
            for (int i = 0; i < nodes.Length; i++)
            {
                nodes[i].Quaternion = new Quaternion(nodes[i].OriginalRotateAxis, nodes[i].Angle);
                if (i > 0)
                {
                    nodes[i].Quaternion = Quaternion.Multiply(nodes[i - 1].Quaternion, nodes[i].Quaternion);
                }
 
                var m = new Matrix3D();
                m.Rotate(nodes[i].Quaternion);
                if (i < nodes.Length - 1)
                {
                    var deltaVector = m.Transform(new Vector3D(nodes[i].Length, 0, 0));
                    nodes[i + 1].Point = nodes[i].Point + deltaVector;
                }
 
                nodes[i].RotateAxis = m.Transform(nodes[i].OriginalRotateAxis);
            }
 
            // ゴールしたら true を返す
            bool isGoal = true;
            var v = target - nodes[nodes.Length - 1].Point;
            if (v.Length > 0.1)
            {
                isGoal = false;
            }
            return isGoal;
        }
    }
}

また、MainWindow.xaml.cs は以下のとおり。

描画するごとにこの繰り返しを行うことでアニメーションしている。末端が目標(赤いボール)に到達したら(目標のある範囲に末端が入ったら)、新たな目標をランダムな位置に設定するようにした。また、たまに目標に届かない場合があり、この場合もしばらくしたら、新たな目標をランダムな位置に設定するようにした。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Media.Media3D;
using System.Windows.Navigation;
using System.Windows.Shapes;
 
namespace sample1
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            Init();
 
            CompositionTarget.Rendering += CompositionTarget_Rendering;
        }
 
        private Node[] _nodes = null;
        private Sphere3D _target = null;
        private List<ModelVisual3D> _cubeModelList = new List<ModelVisual3D>();
        private List<ModelVisual3D> _cylinderModelList = new List<ModelVisual3D>();
        private List<ModelVisual3D> _rotateAxisModelList = new List<ModelVisual3D>();
        private Random _random = new Random();
        private int _goalCount = 0;
        private int _processCount = 0;
 
        private void CompositionTarget_Rendering(object sender, EventArgs e)
        {
            var target = _target as Sphere3D;
            var targettg = target.Transform as Transform3DGroup;
            var targettt = targettg.Children[1] as TranslateTransform3D;
            var targetPoint = new Point3D(targettt.OffsetX, targettt.OffsetY, targettt.OffsetZ);
 
            var isGoal = InverseKinematics.MoveNodes(_nodes, 1, 0, targetPoint);
 
            for (int i = 0; i < _nodes.Count(); ++i)
            {
                if (i < _cubeModelList.Count())
                {
                    var line = _cubeModelList[i] as ModelVisual3D;
                    var tg2 = line.Transform as Transform3DGroup;
                    var rt2 = tg2.Children[0] as RotateTransform3D;
                    Quaternion q2 = _nodes[i].Quaternion;
                    var r2 = new AxisAngleRotation3D(q2.Axis, q2.Angle);
                    rt2.Rotation = r2;
                    var tt2 = tg2.Children[1] as TranslateTransform3D;
                    tt2.OffsetX = _nodes[i].Point.X;
                    tt2.OffsetY = _nodes[i].Point.Y;
                    tt2.OffsetZ = _nodes[i].Point.Z;
                }
 
                if (i < _cylinderModelList.Count())
                {
                    var cylinder = _cylinderModelList[i] as Cylinder3D;
                    var tg = cylinder.Transform as Transform3DGroup;
                    var rt = tg.Children[2] as RotateTransform3D;
                    if (i > 0)
                    {
                        Quaternion q = _nodes[i].Quaternion;
                        var r = new AxisAngleRotation3D(q.Axis, q.Angle);
                        rt.Rotation = r;
                    }
                    var tt = tg.Children[3] as TranslateTransform3D;
                    tt.OffsetX = _nodes[i].Point.X;
                    tt.OffsetY = _nodes[i].Point.Y;
                    tt.OffsetZ = _nodes[i].Point.Z;
                }
 
                if (i < _rotateAxisModelList.Count())
                {
                    var rotateAxis = _rotateAxisModelList[i] as _3DTools.ScreenSpaceLines3D;
                    rotateAxis.Points[0] = _nodes[i].Point - _nodes[i].RotateAxis;
                    rotateAxis.Points[1] = _nodes[i].Point + _nodes[i].RotateAxis;
                }
            }
 
            if (_processCount++ > 300)
            {
                _processCount = 0;
            }
            if (isGoal)
            {
                if (_goalCount++ > 50)
                {
                    _goalCount = 0;
                    _processCount = 0;
                }
            }
            if (_processCount == 0)
            {
                targettt.OffsetX = (_random.NextDouble() - 0.5) * 4;
                targettt.OffsetY = (_random.NextDouble() - 0.5) * 4;
                targettt.OffsetZ = (_random.NextDouble() - 0.5) * 4;
                Trace.WriteLine(string.Format("({0},{1},{2})", targettt.OffsetX, targettt.OffsetY, targettt.OffsetZ));
            }
        }
 
        private void Init()
        {
            var root = this.Content as Grid;
 
            // trackball
            var td = new _3DTools.TrackballDecorator();
            root.Children.Add(td);
 
            // viewport
            var viewport = new Viewport3D();
            td.Content = viewport;
 
            // camera
            var camera = new PerspectiveCamera();
            camera.Position = new Point3D(14, 13, 12);
            camera.LookDirection = new Vector3D(-14, -13, -12);
            camera.UpDirection = new Vector3D(0, 1, 0);
            viewport.Camera = camera;
 
            // light
            var light = new DirectionalLight();
            light.Color = Colors.White;
            light.Direction = new Vector3D(-2, -3, -1);
            var lightModel = new ModelVisual3D();
            lightModel.Content = light;
            viewport.Children.Add(lightModel);
 
            // axis
            var xAxis = new _3DTools.ScreenSpaceLines3D();
            xAxis.Points.Add(new Point3D(-100, 0, 0));
            xAxis.Points.Add(new Point3D(100, 0, 0));
            xAxis.Color = Colors.Red;
            xAxis.Thickness = 1;
            var yAxis = new _3DTools.ScreenSpaceLines3D();
            yAxis.Points.Add(new Point3D(0, -100, 0));
            yAxis.Points.Add(new Point3D(0, 100, 0));
            yAxis.Color = Colors.Green;
            yAxis.Thickness = 1;
            var zAxis = new _3DTools.ScreenSpaceLines3D();
            zAxis.Points.Add(new Point3D(0, 0, -100));
            zAxis.Points.Add(new Point3D(0, 0, 100));
            zAxis.Color = Colors.Blue;
            zAxis.Thickness = 1;
            var axis = new ModelVisual3D();
            axis.Children.Add(xAxis);
            axis.Children.Add(yAxis);
            axis.Children.Add(zAxis);
            viewport.Children.Add(axis);
 
            // nodes
            var nodes = new Node[] {
                new Node(1, new Point3D(3, 0, 0), 2, 0),
                new Node(0, new Point3D(5, 0, 0), 3, 90),
                new Node(0, new Point3D(8, 0, 0), 2, -90),
                new Node(0, new Point3D(9, 0, 0), 1, 0),
            };
 
            for (var i = 0; i < nodes.Count(); ++i)
            {
                // cylinder
                var cylinderModel = new Cylinder3D();
                var cylinderMaterial = new MaterialGroup();
                cylinderMaterial.Children.Add(new DiffuseMaterial(new SolidColorBrush(Colors.Gold)));
                cylinderMaterial.Children.Add(new SpecularMaterial(new SolidColorBrush(Colors.Gold), 60));
                cylinderModel.Material = cylinderMaterial;
                var cylinderTrans = new Transform3DGroup();
                cylinderTrans.Children.Add(new ScaleTransform3D(0.3, 0.5, 0.3));
                if (nodes[i].NodeType == 0)
                {
                    var r = new AxisAngleRotation3D(new Vector3D(1, 0, 0), 90);
                    cylinderTrans.Children.Add(new RotateTransform3D(r));
                }
                else
                {
                    var r = new AxisAngleRotation3D(new Vector3D(0, 0, 1), 0);
                    cylinderTrans.Children.Add(new RotateTransform3D(r));
                }
                cylinderTrans.Children.Add(new RotateTransform3D());
                cylinderTrans.Children.Add(new TranslateTransform3D(nodes[i].Point.X, nodes[i].Point.Y, nodes[i].Point.Z));
                cylinderModel.Transform = cylinderTrans;
                viewport.Children.Add(cylinderModel);
                _cylinderModelList.Add(cylinderModel);
 
                // rotateAxis
                var rotateAxisModel = new _3DTools.ScreenSpaceLines3D();
                if (nodes[i].NodeType == 0)
                {
                    Vector3D axisVector = new Vector3D(0, 2, 0);
                    rotateAxisModel.Points.Add(nodes[i].Point - axisVector);
                    rotateAxisModel.Points.Add(nodes[i].Point + axisVector);
                }
                else
                {
                    Vector3D axisVector = new Vector3D(0, 0, 2);
                    rotateAxisModel.Points.Add(nodes[i].Point - axisVector);
                    rotateAxisModel.Points.Add(nodes[i].Point + axisVector);
                }
                rotateAxisModel.Color = Colors.Gray;
                viewport.Children.Add(rotateAxisModel);
                _rotateAxisModelList.Add(rotateAxisModel);
 
                // cube
                if (i < nodes.Count() - 1)
                {
                    double len = nodes[i].Length;
                    var point0 = new Point3D(0, -0.2, -0.2);    // bottom-back-left
                    var point1 = new Point3D(len, -0.2, -0.2);    // bottom-back-right
                    var point2 = new Point3D(len, -0.2, 0.2);    // bottom-front-right
                    var point3 = new Point3D(0, -0.2, 0.2);        // bottom-front-left
                    var point4 = new Point3D(0, 0.2, -0.2);        // top-back-left
                    var point5 = new Point3D(len, 0.2, -0.2);    // top-back-right
                    var point6 = new Point3D(len, 0.2, 0.2);    // top-front-right
                    var point7 = new Point3D(0, 0.2, 0.2);        // top-front-left
                    var cubeGroup = new Model3DGroup();
                    var cubeMaterial = new DiffuseMaterial(new SolidColorBrush(Colors.Blue));
                    cubeGroup.Children.Add(new GeometryModel3D(CreateMesh(point7, point6, point2, point3), cubeMaterial));    // front
                    cubeGroup.Children.Add(new GeometryModel3D(CreateMesh(point6, point5, point1, point2), cubeMaterial));    // right
                    cubeGroup.Children.Add(new GeometryModel3D(CreateMesh(point5, point4, point0, point1), cubeMaterial));    // back
                    cubeGroup.Children.Add(new GeometryModel3D(CreateMesh(point4, point7, point3, point0), cubeMaterial));    // left
                    cubeGroup.Children.Add(new GeometryModel3D(CreateMesh(point1, point0, point3, point2), cubeMaterial));    // bottom
                    cubeGroup.Children.Add(new GeometryModel3D(CreateMesh(point4, point5, point6, point7), cubeMaterial));    // top
                    var cubeModel = new ModelVisual3D();
                    cubeModel.Content = cubeGroup;
                    var cubeTrans = new Transform3DGroup();
                    cubeTrans.Children.Add(new RotateTransform3D());
                    cubeTrans.Children.Add(new TranslateTransform3D(nodes[i].Point.X, nodes[i].Point.Y, nodes[i].Point.Z));
                    cubeModel.Transform = cubeTrans;
                    viewport.Children.Add(cubeModel);
                    _cubeModelList.Add(cubeModel);
                }
            }
 
            // target
            var target = new Sphere3D();
            var mg2 = new MaterialGroup();
            mg2.Children.Add(new DiffuseMaterial(new SolidColorBrush(Colors.Red)));
            mg2.Children.Add(new SpecularMaterial(new SolidColorBrush(Colors.Red), 60));
            target.Material = mg2;
            var trans2 = new Transform3DGroup();
            trans2.Children.Add(new ScaleTransform3D(0.3, 0.3, 0.3));
            trans2.Children.Add(new TranslateTransform3D(-2, 3, 1));
            target.Transform = trans2;
            viewport.Children.Add(target);
 
            this._nodes = nodes;
            this._target = target;
        }
 
        private static MeshGeometry3D CreateMesh(Point3D p0, Point3D p1, Point3D p2, Point3D p3)
        {
            var mesh = new MeshGeometry3D();
            mesh.Positions = new Point3DCollection(new Point3D[] { p0, p1, p2, p3 });
            mesh.TriangleIndices = new Int32Collection(new int[] { 0, 2, 1, 0, 3, 2 });
            mesh.TextureCoordinates = new PointCollection(new Point[] { new Point(0, 0), new Point(1, 0), new Point(1, 1), new Point(0, 1) });
            return mesh;
        }
    }
}

実行結果は以下のとおり。ノードは4つ、末端は関節とならないので、関節は根元側の4つ。一番根元はY軸方向に回転する関節で、その他はZ軸方向に回転する関節とした。

wpf3d-10

以上。

WPF で 3D(その9)

今回は3次元空間にエルミート曲線を描いてみる。
(ただし、3次元空間でのエルミート曲線の考え方が正しいかどうかは分かりません。もし間違いがあればご指摘ください。)

image024

エルミート曲線は、始点、始点でのベクトル、終点、終点でのベクトルの4つの情報で決まる。プログラムでは短い線分で近似することになるので、いくつの線分に分けるかというパラメータも必要になる。

以下の例では、3つの点を与え、3つのエルミート曲線で閉じた曲線を構成した。

それだけでは面白くないので、この閉じた曲線上を金色のボールが等速で動くようにした。getHermiteCurvePoints メソッドは点の集合を返すが、その各点を結ぶ線分の長さは一定ではない。また、エルミート曲線上の移動距離を計算するのは容易ではない。したがって描画タイミングごとに近似した線分での移動距離を泥臭く計算している。

おまけで金色のボールには鏡面光を設定し、光沢を持たせている。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Media.Media3D;
using System.Windows.Navigation;
using System.Windows.Shapes;
 
namespace sample1
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            Init();
 
            CompositionTarget.Rendering += CompositionTarget_Rendering;
        }
 
        _3DTools.ScreenSpaceLines3D _hermiteCurve = null;
        Sphere3D _sphere = null;
        int _pathIndex = 0;
        double _pathDistance = 0;
 
        private void CompositionTarget_Rendering(object sender, EventArgs e)
        {
            double lenToMove = 0.1;
 
            while (true)
            {
                var point1 = _hermiteCurve.Points[_pathIndex];
                var point2 = _hermiteCurve.Points[_pathIndex + 1];
                var pathLen = Point3D.Subtract(point1, point2).Length;
                var rest = pathLen - this._pathDistance;
 
                if (lenToMove <= rest)
                {
                    _pathDistance += lenToMove;
 
                    var tg = _sphere.Transform as Transform3DGroup;
                    var tt = tg.Children[1] as TranslateTransform3D;
                    var s = _pathDistance / pathLen;
                    tt.OffsetX = point1.X * (1 - s) + point2.X * s;
                    tt.OffsetY = point1.Y * (1 - s) + point2.Y * s;
                    tt.OffsetZ = point1.Z * (1 - s) + point2.Z * s;
                    break;
                }
                else
                {
                    lenToMove -= rest;
 
                    _pathIndex += 2;
                    if (_pathIndex >= _hermiteCurve.Points.Count())
                    {
                        _pathIndex = 0;
                    }
                    _pathDistance = 0;
                }
            }
        }
 
        private void Init()
        {
            var root = this.Content as Grid;
 
            // trackball
            var td = new _3DTools.TrackballDecorator();
            root.Children.Add(td);
 
            // viewport
            var viewport = new Viewport3D();
            td.Content = viewport;
 
            // camera
            var camera = new PerspectiveCamera();
            camera.Position = new Point3D(14, 13, 12);
            camera.LookDirection = new Vector3D(-14, -13, -12);
            camera.UpDirection = new Vector3D(0, 1, 0);
            viewport.Camera = camera;
 
            // light
            var light = new DirectionalLight();
            light.Color = Colors.White;
            light.Direction = new Vector3D(-2, -3, -1);
            var lightModel = new ModelVisual3D();
            lightModel.Content = light;
            viewport.Children.Add(lightModel);
 
            // axis
            var xAxis = new _3DTools.ScreenSpaceLines3D();
            xAxis.Points.Add(new Point3D(-100, 0, 0));
            xAxis.Points.Add(new Point3D(100, 0, 0));
            xAxis.Color = Colors.Red;
            xAxis.Thickness = 1;
            var yAxis = new _3DTools.ScreenSpaceLines3D();
            yAxis.Points.Add(new Point3D(0, -100, 0));
            yAxis.Points.Add(new Point3D(0, 100, 0));
            yAxis.Color = Colors.Green;
            yAxis.Thickness = 1;
            var zAxis = new _3DTools.ScreenSpaceLines3D();
            zAxis.Points.Add(new Point3D(0, 0, -100));
            zAxis.Points.Add(new Point3D(0, 0, 100));
            zAxis.Color = Colors.Blue;
            zAxis.Thickness = 1;
            var axis = new ModelVisual3D();
            axis.Children.Add(xAxis);
            axis.Children.Add(yAxis);
            axis.Children.Add(zAxis);
            viewport.Children.Add(axis);
 
            // hermite curve
            var hermiteCurve = new _3DTools.ScreenSpaceLines3D();
            hermiteCurve.Color = Colors.Black;
            hermiteCurve.Thickness = 3;
            var p0 = new Point3D(0, 0, 0);
            var v0 = new Vector3D(3.5, -1.5, 5);
            var p1 = new Point3D(5, 5, -2);
            var v1 = new Vector3D(-4, 1, 6);
            var p2 = new Point3D(-3, -2, 3);
            var v2 = new Vector3D(6, 2, 0);
            int steps = 32;
            var list = getHermiteCurvePoints(p0, v0, p1, v1, steps);
            for (int i = 0; i < list.Count - 1; i++)
            {
                hermiteCurve.Points.Add(list[i]);
                hermiteCurve.Points.Add(list[i + 1]);
            }
            list = getHermiteCurvePoints(p1, v1, p2, v2, steps);
            for (int i = 0; i < list.Count - 1; i++)
            {
                hermiteCurve.Points.Add(list[i]);
                hermiteCurve.Points.Add(list[i + 1]);
            }
            list = getHermiteCurvePoints(p2, v2, p0, v0, steps);
            for (int i = 0; i < list.Count - 1; i++)
            {
                hermiteCurve.Points.Add(list[i]);
                hermiteCurve.Points.Add(list[i + 1]);
            }
            viewport.Children.Add(hermiteCurve);
 
            // vector v0 model
            var mv0 = new _3DTools.ScreenSpaceLines3D();
            mv0.Points.Add(p0);
            mv0.Points.Add(p0 + v0);
            mv0.Color = Colors.Gray;
            viewport.Children.Add(mv0);
 
            // vector v1 model
            var mv1 = new _3DTools.ScreenSpaceLines3D();
            mv1.Points.Add(p1);
            mv1.Points.Add(p1 + v1);
            mv1.Color = Colors.Gray;
            viewport.Children.Add(mv1);
 
            // vector v2 model
            var mv2 = new _3DTools.ScreenSpaceLines3D();
            mv2.Points.Add(p2);
            mv2.Points.Add(p2 + v2);
            mv2.Color = Colors.Gray;
            viewport.Children.Add(mv2);
 
            // spehre
            var sphere = new Sphere3D();
            var mg = new MaterialGroup();
            mg.Children.Add(new DiffuseMaterial(new SolidColorBrush(Colors.Gold)));
            mg.Children.Add(new SpecularMaterial(new SolidColorBrush(Colors.Gold), 60));
            sphere.Material = mg;
            var trans = new Transform3DGroup();
            trans.Children.Add(new ScaleTransform3D(0.3, 0.3, 0.3));
            trans.Children.Add(new TranslateTransform3D(0, 0, 0));
            sphere.Transform = trans;
            viewport.Children.Add(sphere);
 
            _hermiteCurve = hermiteCurve;
            _sphere = sphere;
        }
 
        private Point3DCollection getHermiteCurvePoints(Point3D p0, Vector3D v0, Point3D p1, Vector3D v1, int steps)
        {
            var points = new Point3DCollection();
            for (int i = 0; i < steps; i++)
            {
                double s1 = (double)i / (steps - 1);
                double s2 = s1 * s1;
                double s3 = s1 * s1 * s1;
                double h1 = 2 * s3 - 3 * s2 + 0 + 1;    // h1(s) =  2s^3 - 3s^2 + 1
                double h2 = -2 * s3 + 3 * s2;            // h2(s) = -2s^3 + 3s^2
                double h3 = s3 - 2 * s2 + s1;             // h3(s) =   s^3 - 2s^2 + s
                double h4 = s3 - s2;                    // h4(s) =   s^3 -  s^2
                double x = p0.X * h1 + p1.X * h2 + v0.X * h3 + v1.X * h4;
                double y = p0.Y * h1 + p1.Y * h2 + v0.Y * h3 + v1.Y * h4;
                double z = p0.Z * h1 + p1.Z * h2 + v0.Z * h3 + v1.Z * h4;
                points.Add(new Point3D(x, y, z));
            }
            return points;
        }
    }
}

実行結果は以下のとおり。太い黒線が3つのエルミート曲線、細いグレーの直線が3つの点におけるベクトルである。
wpf3d-9

以上。

WPF で 3D(その8)

今回はオブジェクトをワイヤーフレームで表示する。

表示する物体は、MSDN のサンプル「3-D ソリッドのサンプル」に含まれる円錐を使う。
http://msdn.microsoft.com/ja-jp/library/ms771784%28v=vs.90%29.aspx

サンプルの「cone3d.cs」と「primitive3d.cs」をプログラムに取り込んで使った。

ちなみに、今回はテクスチャは使わないので関係ないが、球体と同じく、やはりそのまま使うとテクスチャ画像が左右反転されてしまう問題があるようだ。修正するには Cone3D クラスの GetTextureCoordinate メソッドを以下のように少し書き換える。

        private Point GetTextureCoordinate(double t, double y)
        {
            Matrix m = new Matrix();
            m.Scale(1 / (2 * Math.PI), -0.5);
 
            //Point p = new Point(t, y);
            Point p = new Point(1 - t, y);
            p = p * m;
 
            return p;
        }

ワイヤーフレームは、座標系を描くのにも使っている ScreenSpaceLines3D クラスが MakeWireframe メソッドを持っているので、これを使う。ただし、単にワイヤーフレームで表示するという意味ではなく、ワイヤーフレームのオブジェクトを作るということになるが。

MainWindow.xaml.cs は以下のとおり。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Media.Media3D;
using System.Windows.Navigation;
using System.Windows.Shapes;
 
namespace sample1
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            Init();
        }
 
        private void Init()
        {
            var root = this.Content as Grid;
 
            // trackball
            var td = new _3DTools.TrackballDecorator();
            root.Children.Add(td);
 
            // viewport
            var viewport = new Viewport3D();
            td.Content = viewport;
 
            // camera
            var camera = new PerspectiveCamera();
            camera.Position = new Point3D(14, 13, 12);
            camera.LookDirection = new Vector3D(-14, -13, -12);
            camera.UpDirection = new Vector3D(0, 1, 0);
            viewport.Camera = camera;
 
            // light
            var light = new DirectionalLight();
            light.Color = Colors.White;
            light.Direction = new Vector3D(-2, -3, -1);
            var lightModel = new ModelVisual3D();
            lightModel.Content = light;
            viewport.Children.Add(lightModel);
 
            var light2 = new AmbientLight();
            light2.Color = Color.FromRgb(128, 128, 128);
            var lightModel2 = new ModelVisual3D();
            lightModel2.Content = light2;
            viewport.Children.Add(lightModel2);
 
            // axis
            var xAxis = new _3DTools.ScreenSpaceLines3D();
            xAxis.Points.Add(new Point3D(-100, 0, 0));
            xAxis.Points.Add(new Point3D(100, 0, 0));
            xAxis.Color = Colors.Red;
            xAxis.Thickness = 1;
            var yAxis = new _3DTools.ScreenSpaceLines3D();
            yAxis.Points.Add(new Point3D(0, -100, 0));
            yAxis.Points.Add(new Point3D(0, 100, 0));
            yAxis.Color = Colors.Green;
            yAxis.Thickness = 1;
            var zAxis = new _3DTools.ScreenSpaceLines3D();
            zAxis.Points.Add(new Point3D(0, 0, -100));
            zAxis.Points.Add(new Point3D(0, 0, 100));
            zAxis.Color = Colors.Blue;
            zAxis.Thickness = 1;
            var axis = new ModelVisual3D();
            axis.Children.Add(xAxis);
            axis.Children.Add(yAxis);
            axis.Children.Add(zAxis);
            viewport.Children.Add(axis);
 
            // cone
            var cone = new Cone3D();
            var coneWireframe = new _3DTools.ScreenSpaceLines3D();
            coneWireframe.MakeWireframe(cone.Content);
            coneWireframe.Color = Colors.Black;
            var trans = new Transform3DGroup();
            trans.Children.Add(new ScaleTransform3D(2.5, 2.5, 2.5));
            trans.Children.Add(new TranslateTransform3D(2.5, 2.5, 2.5));
            coneWireframe.Transform = trans;
 
            viewport.Children.Add(coneWireframe);
        }
    }
}

実行結果は以下のとおり。
wpf3d-8

以上。

WPF で 3D(その7)

前回、半透明を試してみたところ、うまく半透明の効果が現れない場合があった。これは、手前のものを先に描画すると、まだ奥のものが描画されていないためにアルファブレンドできないということだと思う。大抵のレンダリングシステムでも同じようなものだろう。

結局のところ、半透明をうまく描画するためには、オブジェクトを奥から手前の順に描画していけばいい、ということになる。

オブジェクトは Viewport3D クラスの Children プロパティ(Visual3DCollection)に格納されている。描画順は、単にこのプロパティに格納されている順番に行われているようなので、カメラから遠い順にソートし直すことにする。

今回はこのソートを行うクラス SceneSortingHelper を導入した。ソースコードは以下のとおり。
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media.Media3D;
 
namespace sample1
{
    public static class SceneSortingHelper
    {
        /// <summary>
        /// Sort Modelgroups in Farthest to Closest order, to enable transparency
        /// Should be applied whenever the scene is significantly re-oriented
        /// </summary>
        public static void AlphaSort(Point3D CameraPosition, Visual3DCollection Models)
        {
            ArrayList list = new ArrayList();
            foreach (ModelVisual3D model in Models)
            {
                ModelVisual3D m = model as ModelVisual3D;
                if (m != null)
                {
                    if (m.Content is Light)
                    {
                        list.Add(new ModelDistance(0, model));
                    }
                    else if (m.Content is Model3DGroup)
                    {
                        var center = new Point3D(
                            model.Content.Bounds.X + model.Content.Bounds.SizeX / 2,
                            model.Content.Bounds.Y + model.Content.Bounds.SizeY / 2,
                            model.Content.Bounds.Z + model.Content.Bounds.SizeZ / 2);
                        var modelPosition = model.Transform.Transform(center);
                        double distance = (Point3D.Subtract(CameraPosition, modelPosition)).Length;
                        list.Add(new ModelDistance(distance, model));
                    }
                    else
                    {
                        list.Add(new ModelDistance(0, model));
                    }
                }
                else
                {
                    list.Add(new ModelDistance(0, model));
                }
            }
            list.Sort(new DistanceComparer(SortDirection.FarToNear));
            Models.Clear();
            foreach (ModelDistance modelDistance in list)
            {
                Models.Add(modelDistance.model);
            }
        }
 
        private class ModelDistance
        {
            public ModelDistance(double distance, ModelVisual3D model)
            {
                this.distance = distance;
                this.model = model;
            }
 
            public double distance;
            public ModelVisual3D model;
        }
 
        private enum SortDirection
        {
            NearToFar,
            FarToNear
        }
 
        private class DistanceComparer : IComparer
        {
            public DistanceComparer(SortDirection sortDirection)
            {
                _sortDirection = sortDirection;
            }
 
            int IComparer.Compare(Object o1, Object o2)
            {
                double x1 = ((ModelDistance)o1).distance;
                double x2 = ((ModelDistance)o2).distance;
                if (_sortDirection == SortDirection.NearToFar)
                {
                    return (int)(x1 - x2);
                }
                else
                {
                    return (int)(-(x1 - x2));
                }
            }
 
            private SortDirection _sortDirection;
        }
    }
}

ソートは、レンダリングのタイミングで行う必要があるので、CompositionTarget.Rendering イベント内で行う。 カメラ位置は TrackballDecorator を導入しているので動き回るため、TrackballDecorator の変換してから使う。MainWindow.xaml.cs は以下のとおり。
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Media.Media3D;
using System.Windows.Navigation;
using System.Windows.Shapes;
 
namespace sample1
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            Init();
 
            CompositionTarget.Rendering += CompositionTarget_Rendering;
        }
 
        private void CompositionTarget_Rendering(object sender, EventArgs e)
        {
            var root = this.Content as Grid;
            var td = root.Children[0] as _3DTools.TrackballDecorator;
            var viewport = td.Content as Viewport3D;
 
            var camera = viewport.Camera as PerspectiveCamera;
            var cameraPostion = td.Transform.Transform(camera.Position);
 
            SceneSortingHelper.AlphaSort(cameraPostion, viewport.Children);
        }
 
        private void Init()
        {
            var root = this.Content as Grid;
 
            // trackball
            var td = new _3DTools.TrackballDecorator();
            root.Children.Add(td);
 
            // viewport
            var viewport = new Viewport3D();
            td.Content = viewport;
 
            // camera
            var camera = new PerspectiveCamera();
            camera.Position = new Point3D(14, 13, 12);
            camera.LookDirection = new Vector3D(-14, -13, -12);
            camera.UpDirection = new Vector3D(0, 1, 0);
            viewport.Camera = camera;
 
            // light
            var light = new DirectionalLight();
            light.Color = Colors.White;
            light.Direction = new Vector3D(-2, -3, -1);
            var lightModel = new ModelVisual3D();
            lightModel.Content = light;
            viewport.Children.Add(lightModel);
 
            // light2
            var light2 = new AmbientLight();
            light2.Color = Color.FromRgb(128, 128, 128);
            var lightModel2 = new ModelVisual3D();
            lightModel2.Content = light2;
            viewport.Children.Add(lightModel2);
 
            // axis
            var xAxis = new _3DTools.ScreenSpaceLines3D();
            xAxis.Points.Add(new Point3D(-100, 0, 0));
            xAxis.Points.Add(new Point3D(100, 0, 0));
            xAxis.Color = Colors.Red;
            xAxis.Thickness = 1;
            var yAxis = new _3DTools.ScreenSpaceLines3D();
            yAxis.Points.Add(new Point3D(0, -100, 0));
            yAxis.Points.Add(new Point3D(0, 100, 0));
            yAxis.Color = Colors.Green;
            yAxis.Thickness = 1;
            var zAxis = new _3DTools.ScreenSpaceLines3D();
            zAxis.Points.Add(new Point3D(0, 0, -100));
            zAxis.Points.Add(new Point3D(0, 0, 100));
            zAxis.Color = Colors.Blue;
            zAxis.Thickness = 1;
            var axis = new ModelVisual3D();
            axis.Children.Add(xAxis);
            axis.Children.Add(yAxis);
            axis.Children.Add(zAxis);
            viewport.Children.Add(axis);
 
            // cube
            var point0 = new Point3D(0, 0, 0);    // bottom-back-left
            var point1 = new Point3D(5, 0, 0);    // bottom-back-right
            var point2 = new Point3D(5, 0, 5);    // bottom-front-right
            var point3 = new Point3D(0, 0, 5);    // bottom-front-left
            var point4 = new Point3D(0, 5, 0);    // top-back-left
            var point5 = new Point3D(5, 5, 0);    // top-back-right
            var point6 = new Point3D(5, 5, 5);    // top-front-right
            var point7 = new Point3D(0, 5, 5);    // top-front-left
 
            //       Y
            //       | 
            //    p4 +------------------+ p5
            //      /|                 /|
            //     / |                / |
            // p7 /  |            p6 /  |
            //   +------------------+   |
            //   |   +--------------|---+---------- X
            //   |  / p0            |  / p1
            //   | /                | /
            //   |/                 |/
            //   +------------------+
            //  / p3                p2
            // Z
 
            var imageSrc = new BitmapImage(new Uri(@"C:\syaraku.JPG"));
            var bitmap = ImageHelper.BitmapImage2Bitmap(imageSrc);
            System.Drawing.Bitmap alphaImage = ImageHelper.CreateAlphaImage(bitmap, 0.5F);
            var alphaImageSrc = ImageHelper.ToWpfBitmap(alphaImage);
            var material = new DiffuseMaterial(new ImageBrush(alphaImageSrc));
 
            var group = new Model3DGroup();
            group.Children.Add(new GeometryModel3D(CreateMesh(point7, point6, point2, point3), material));    // front
            group.Children.Add(new GeometryModel3D(CreateMesh(point6, point5, point1, point2), material));    // right
            group.Children.Add(new GeometryModel3D(CreateMesh(point5, point4, point0, point1), material));    // back
            group.Children.Add(new GeometryModel3D(CreateMesh(point4, point7, point3, point0), material));    // left
            group.Children.Add(new GeometryModel3D(CreateMesh(point4, point5, point6, point7), material));    // top
            group.Children.Add(new GeometryModel3D(CreateMesh(point1, point0, point3, point2), material));    // bottom
 
            var cubeModel = new ModelVisual3D();
            cubeModel.Content = group;
            viewport.Children.Add(cubeModel);
            
            // cube2
            var group2 = new Model3DGroup();
            var br2 = new SolidColorBrush(Colors.Blue);
            br2.Opacity = 0.5;
            var material2 = new DiffuseMaterial(br2);
            group2.Children.Add(new GeometryModel3D(CreateMesh(point7, point6, point2, point3), material2));    // front
            group2.Children.Add(new GeometryModel3D(CreateMesh(point6, point5, point1, point2), material2));    // right
            group2.Children.Add(new GeometryModel3D(CreateMesh(point5, point4, point0, point1), material2));    // back
            group2.Children.Add(new GeometryModel3D(CreateMesh(point4, point7, point3, point0), material2));    // left
            group2.Children.Add(new GeometryModel3D(CreateMesh(point1, point0, point3, point2), material2));    // bottom
            group2.Children.Add(new GeometryModel3D(CreateMesh(point4, point5, point6, point7), material2));    // top
            var cubeModel2 = new ModelVisual3D();
            cubeModel2.Content = group2;
            var trans2 = new Transform3DGroup();
            trans2.Children.Add(new TranslateTransform3D(-3, -3, 3));
            cubeModel2.Transform = trans2;
            viewport.Children.Add(cubeModel2);
 
            // cube3
            var group3 = new Model3DGroup();
            var br3 = new SolidColorBrush(Colors.Green);
            br3.Opacity = 0.5;
            var material3 = new DiffuseMaterial(br3);
            group3.Children.Add(new GeometryModel3D(CreateMesh(point7, point6, point2, point3), material3));    // front
            group3.Children.Add(new GeometryModel3D(CreateMesh(point6, point5, point1, point2), material3));    // right
            group3.Children.Add(new GeometryModel3D(CreateMesh(point5, point4, point0, point1), material3));    // back
            group3.Children.Add(new GeometryModel3D(CreateMesh(point4, point7, point3, point0), material3));    // left
            group3.Children.Add(new GeometryModel3D(CreateMesh(point1, point0, point3, point2), material3));    // bottom
            group3.Children.Add(new GeometryModel3D(CreateMesh(point4, point5, point6, point7), material3));    // top
            var cubeModel3 = new ModelVisual3D();
            cubeModel3.Content = group3;
            var trans3 = new Transform3DGroup();
            trans3.Children.Add(new TranslateTransform3D(-3, 3, -3));
            cubeModel3.Transform = trans3;
            viewport.Children.Add(cubeModel3);
        }
 
        private static MeshGeometry3D CreateMesh(Point3D p0, Point3D p1, Point3D p2, Point3D p3)
        {
            var mesh = new MeshGeometry3D();
            mesh.Positions = new Point3DCollection(new Point3D[] { p0, p1, p2, p3 });
            mesh.TriangleIndices = new Int32Collection(new int[] { 0, 2, 1, 0, 3, 2 });
            mesh.TextureCoordinates = new PointCollection(new Point[] { new Point(0, 0), new Point(1, 0), new Point(1, 1), new Point(0, 1) });
            return mesh;
        }
    }
}

実行結果は以下のとおり。写楽立方体の向こうに青と緑の立方体が透けて見える。カメラを動かしても問題なくなった。
wpf3d-7

以上。

アクセスカウンター
  • 今日:
  • 昨日:
  • 累計:

livedoor 天気