Hi, heres some more code - i think i've found a solution for you ...
Again, its a cobbled together example you'll have to refine.
Please note that the transform code has changed dramatically, i'm only just getting to grips with WPF 3D myself and i've realised I was doing quite a few things wrong. I was recreating the Transform3DGroup each time instead of just changing the angle of the Transform that was already applied.
Have a look at the code and note the altTransform group - what i'm doing is adding an extra transfomation to this alt group. I've then used the altTransform on a cylinder and sphere that would otherwise be in same position as one of the other cylinders and spheres. I've marked this cylinder green so that you can see which one it is.
My suggestion to you is that you create all your spheres and cylinders on the same axis and then transform them to your desired location.
I've also changed the sphere and cylinder sizes to what you requested in an earlier comment.
Have a look at the code and see what you think?
Cheers,
Andy
<Window x:Class="wpfspin.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="600" Width="600">
<StackPanel>
<Viewport3D Name="mainViewport" ClipToBounds="True" HorizontalAlignment="Stretch" Height="600">
<Viewport3D.Camera>
<PerspectiveCamera
LookDirection="0,0,-5"
UpDirection="0,1,0"
Position="0,0,10"
/>
</Viewport3D.Camera>
<ModelVisual3D>
<ModelVisual3D.Content>
<Model3DGroup x:Name="group3d">
<SpotLight Position="30,30,30" x:Name="mySpotLight" Color="Yellow" InnerConeAngle="100" OuterConeAngle="1000" Range="100" />
</Model3DGroup>
</ModelVisual3D.Content>
</ModelVisual3D>
</Viewport3D>
</StackPanel>
</Window>
and heres the code behind ...
using System;
using System.Collections.Generic;
using System.Timers;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Windows.Threading;
namespace wpfspin
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
Init();
}
private Timer _timer;
private readonly List<ModelVisual3D> _models = new List<ModelVisual3D>();
private double _angle;
private Transform3DGroup _transform3DGroup;
private AxisAngleRotation3D _axisAngleRotation3D;
private Transform3DGroup _altTransform;
public void Init()
{
_models.Add(CreateSphere(new Point3D(0,0,0), 0.1, 10, 10, Colors.AliceBlue));
_models.Add(CreateSphere(new Point3D(0,0,2), 0.1, 10, 10, Colors.AliceBlue));
//notice that the following two spheres are created in the same place
_models.Add(CreateSphere(new Point3D(0, 0, -2), 0.1, 10, 10, Colors.AliceBlue));
_models.Add(CreateSphere(new Point3D(0, 0, -2), 0.1, 10, 10, Colors.AliceBlue));
_models.Add(GetCylinder(GetSurfaceMaterial(Colors.Red), new Point3D(0,0,0), 0.02, 2));
//notice that the following to cylinders are also created in the same place
_models.Add(GetCylinder(GetSurfaceMaterial(Colors.Red), new Point3D(0, 0, 0), 0.02, -2));
_models.Add(GetCylinder(GetSurfaceMaterial(Colors.Green), new Point3D(0, 0, 0), 0.02, -2));
_transform3DGroup = new Transform3DGroup();
_models.ForEach(x => x.Transform = _transform3DGroup);
//heres my alt transform that i'm just using for the duplicate sphere and cylinder
_altTransform = new Transform3DGroup();
_models[3].Transform = _altTransform;
_models[6].Transform = _altTransform;
var rotateTransform3D = new RotateTransform3D { CenterX = 0, CenterZ = 0 };
_axisAngleRotation3D = new AxisAngleRotation3D { Axis = new Vector3D(1, 1, 1), Angle = _angle };
rotateTransform3D.Rotation = _axisAngleRotation3D;
//i'm adding the animation rotation to both groups so that all my models spin
_transform3DGroup.Children.Add(rotateTransform3D);
_altTransform.Children.Add(rotateTransform3D);
//but my alt transform gets an extra transformation
var altRotate = new RotateTransform3D { CenterX = 0, CenterZ = 0 };
var altAxis = new AxisAngleRotation3D { Axis = new Vector3D(0, 1, 1), Angle = 90 };
altRotate.Rotation = altAxis;
_altTransform.Children.Add(altRotate);
_models.ForEach(x => mainViewport.Children.Add(x));
_timer = new Timer(10);
_timer.Elapsed += TimerElapsed;
_timer.Enabled = true;
}
void TimerElapsed(object sender, ElapsedEventArgs e)
{
Dispatcher.Invoke(DispatcherPriority.Normal, new Action<double>(Transform), 0.5d);
}
public MaterialGroup GetSurfaceMaterial(Color colour)
{
var materialGroup = new MaterialGroup();
var emmMat = new EmissiveMaterial(new SolidColorBrush(colour));
materialGroup.Children.Add(emmMat);
materialGroup.Children.Add(new DiffuseMaterial(new SolidColorBrush(colour)));
var specMat = new SpecularMaterial(new SolidColorBrush(Colors.White), 30);
materialGroup.Children.Add(specMat);
return materialGroup;
}
public ModelVisual3D GetCube(MaterialGroup materialGroup, Point3D point, Size3D size)
{
var farPoint = new Point3D(point.X - (size.X / 2), point.Y - (size.Y / 2), point.Z - (size.Z / 2));
var nearPoint = new Point3D(point.X + (size.X / 2), point.Y + (size.Y / 2), point.Z + (size.Z / 2));
var cube = new Model3DGroup();
var p0 = new Point3D(farPoint.X, farPoint.Y, farPoint.Z);
var p1 = new Point3D(nearPoint.X, farPoint.Y, farPoint.Z);
var p2 = new Point3D(nearPoint.X, farPoint.Y, nearPoint.Z);
var p3 = new Point3D(farPoint.X, farPoint.Y, nearPoint.Z);
var p4 = new Point3D(farPoint.X, nearPoint.Y, farPoint.Z);
var p5 = new Point3D(nearPoint.X, nearPoint.Y, farPoint.Z);
var p6 = new Point3D(nearPoint.X, nearPoint.Y, nearPoint.Z);
var p7 = new Point3D(farPoint.X, nearPoint.Y, nearPoint.Z);
//front side triangles
cube.Children.Add(CreateTriangleModel(materialGroup, p3, p2, p6));
cube.Children.Add(CreateTriangleModel(materialGroup, p3, p6, p7));
//right side triangles
cube.Children.Add(CreateTriangleModel(materialGroup, p2, p1, p5));
cube.Children.Add(CreateTriangleModel(materialGroup, p2, p5, p6));
//back side triangles
cube.Children.Add(CreateTriangleModel(materialGroup, p1, p0, p4));
cube.Children.Add(CreateTriangleModel(materialGroup, p1, p4, p5));
//left side triangles
cube.Children.Add(CreateTriangleModel(materialGroup, p0, p3, p7));
cube.Children.Add(CreateTriangleModel(materialGroup, p0, p7, p4));
//top side triangles
cube.Children.Add(CreateTriangleModel(materialGroup, p7, p6, p5));
cube.Children.Add(CreateTriangleModel(materialGroup, p7, p5, p4));
//bottom side triangles
cube.Children.Add(CreateTriangleModel(materialGroup, p2, p3, p0));
cube.Children.Add(CreateTriangleModel(materialGroup, p2, p0, p1));
var model = new ModelVisual3D();
model.Content = cube;
return model;
}
private Model3DGroup CreateTriangleModel(MaterialGroup materialGroup, Triangle triangle)
{
return CreateTriangleModel(materialGroup, triangle.P0, triangle.P1, triangle.P2);
}
private Model3DGroup CreateTriangleModel(Material material, Point3D p0, Point3D p1, Point3D p2)
{
var mesh = new MeshGeometry3D();
mesh.Positions.Add(p0);
mesh.Positions.Add(p1);
mesh.Positions.Add(p2);
mesh.TriangleIndices.Add(0);
mesh.TriangleIndices.Add(1);
mesh.TriangleIndices.Add(2);
var normal = CalculateNormal(p0, p1, p2);
mesh.Normals.Add(normal);
mesh.Normals.Add(normal);
mesh.Normals.Add(normal);
var model = new GeometryModel3D(mesh, material);
var group = new Model3DGroup();
group.Children.Add(model);
return group;
}
private Vector3D CalculateNormal(Point3D p0, Point3D p1, Point3D p2)
{
var v0 = new Vector3D(p1.X - p0.X, p1.Y - p0.Y, p1.Z - p0.Z);
var v1 = new Vector3D(p2.X - p1.X, p2.Y - p1.Y, p2.Z - p1.Z);
return Vector3D.CrossProduct(v0, v1);
}
void Transform(double adjustBy)
{
_angle += adjustBy;
_axisAngleRotation3D.Angle = _angle;
}
public ModelVisual3D GetCylinder(MaterialGroup materialGroup, Point3D midPoint, double radius, double depth)
{
var cylinder = new Model3DGroup();
var nearCircle = new CircleAssitor();
var farCircle = new CircleAssitor();
var twoPi = Math.PI * 2;
var firstPass = true;
double x;
double y;
var increment = 0.1d;
for (double i = 0; i < twoPi + increment; i = i + increment)
{
x = (radius * Math.Cos(i));
y = (-radius * Math.Sin(i));
farCircle.CurrentTriangle.P0 = midPoint;
farCircle.CurrentTriangle.P1 = farCircle.LastPoint;
farCircle.CurrentTriangle.P2 = new Point3D(x + midPoint.X, y + midPoint.Y, midPoint.Z);
nearCircle.CurrentTriangle = farCircle.CurrentTriangle.Clone(depth, true);
if (!firstPass)
{
cylinder.Children.Add(CreateTriangleModel(materialGroup, farCircle.CurrentTriangle));
cylinder.Children.Add(CreateTriangleModel(materialGroup, nearCircle.CurrentTriangle));
cylinder.Children.Add(CreateTriangleModel(materialGroup, farCircle.CurrentTriangle.P2, farCircle.CurrentTriangle.P1, nearCircle.CurrentTriangle.P2));
cylinder.Children.Add(CreateTriangleModel(materialGroup, nearCircle.CurrentTriangle.P2, nearCircle.CurrentTriangle.P1, farCircle.CurrentTriangle.P2));
}
else
{
farCircle.FirstPoint = farCircle.CurrentTriangle.P1;
nearCircle.FirstPoint = nearCircle.CurrentTriangle.P1;
firstPass = false;
}
farCircle.LastPoint = farCircle.CurrentTriangle.P2;
nearCircle.LastPoint = nearCircle.CurrentTriangle.P2;
}
var model = new ModelVisual3D { Content = cylinder };
return model;
}
public ModelVisual3D CreateSphere(Point3D center, double radius, int u, int v, Color color)
{
Model3DGroup spear = new Model3DGroup();
if (u < 2 || v < 2)
return null;
Point3D[,] pts = new Point3D[u, v];
for (int i = 0; i < u; i++)
{
for (int j = 0; j < v; j++)
{
pts[i, j] = GetPosition(radius,
i * 180 / (u - 1), j * 360 / (v - 1));
pts[i, j] += (Vector3D)center;
}
}
Point3D[] p = new Point3D[4];
for (int i = 0; i < u - 1; i++)
{
for (int j = 0; j < v - 1; j++)
{
p[0] = pts[i, j];
p[1] = pts[i + 1, j];
p[2] = pts[i + 1, j + 1];
p[3] = pts[i, j + 1];
spear.Children.Add(CreateTriangleFace(p[0], p[1], p[2], color));
spear.Children.Add(CreateTriangleFace(p[2], p[3], p[0], color));
}
}
ModelVisual3D model = new ModelVisual3D();
model.Content = spear;
return model;
}
private Point3D GetPosition(double radius, double theta, double phi)
{
Point3D pt = new Point3D();
double snt = Math.Sin(theta * Math.PI / 180);
double cnt = Math.Cos(theta * Math.PI / 180);
double snp = Math.Sin(phi * Math.PI / 180);
double cnp = Math.Cos(phi * Math.PI / 180);
pt.X = radius * snt * cnp;
pt.Y = radius * cnt;
pt.Z = -radius * snt * snp;
return pt;
}
public Model3DGroup CreateTriangleFace(Point3D p0, Point3D p1, Point3D p2, Color color)
{
MeshGeometry3D mesh = new MeshGeometry3D(); mesh.Positions.Add(p0); mesh.Positions.Add(p1); mesh.Positions.Add(p2); mesh.TriangleIndices.Add(0); mesh.TriangleIndices.Add(1); mesh.TriangleIndices.Add(2);
Vector3D normal = VectorHelper.CalcNormal(p0, p1, p2);
mesh.Normals.Add(normal);
mesh.Normals.Add(normal);
mesh.Normals.Add(normal);
Material material = new DiffuseMaterial(
new SolidColorBrush(color));
GeometryModel3D model = new GeometryModel3D(
mesh, material);
Model3DGroup group = new Model3DGroup();
group.Children.Add(model);
return group;
}
private class VectorHelper
{
public static Vector3D CalcNormal(Point3D p0, Point3D p1, Point3D p2)
{
Vector3D v0 = new Vector3D(p1.X - p0.X, p1.Y - p0.Y, p1.Z - p0.Z);
Vector3D v1 = new Vector3D(p2.X - p1.X, p2.Y - p1.Y, p2.Z - p1.Z);
return Vector3D.CrossProduct(v0, v1);
}
}
}
public class CircleAssitor
{
public CircleAssitor()
{
CurrentTriangle = new Triangle();
}
public Point3D FirstPoint { get; set; }
public Point3D LastPoint { get; set; }
public Triangle CurrentTriangle { get; set; }
}
public class Triangle
{
public Point3D P0 { get; set; }
public Point3D P1 { get; set; }
public Point3D P2 { get; set; }
public Triangle Clone(double z, bool switchP1andP2)
{
var newTriangle = new Triangle();
newTriangle.P0 = GetPointAdjustedBy(this.P0, new Point3D(0, 0, z));
var point1 = GetPointAdjustedBy(this.P1, new Point3D(0, 0, z));
var point2 = GetPointAdjustedBy(this.P2, new Point3D(0, 0, z));
if (!switchP1andP2)
{
newTriangle.P1 = point1;
newTriangle.P2 = point2;
}
else
{
newTriangle.P1 = point2;
newTriangle.P2 = point1;
}
return newTriangle;
}
private Point3D GetPointAdjustedBy(Point3D point, Point3D adjustBy)
{
var newPoint = new Point3D { X = point.X, Y = point.Y, Z = point.Z };
newPoint.Offset(adjustBy.X, adjustBy.Y, adjustBy.Z);
return newPoint;
}
}
}