diff --git a/src/main/java/ru/mcs/q/TriangularBipyramidDemo.java b/src/main/java/ru/mcs/q/TriangularBipyramidDemo.java new file mode 100644 index 0000000..9e12d98 --- /dev/null +++ b/src/main/java/ru/mcs/q/TriangularBipyramidDemo.java @@ -0,0 +1,55 @@ +package ru.mcs.q; + +import ru.mcs.q.field.FaceColor; +import ru.mcs.q.field.QuantumFieldMesh3D; +import ru.mcs.q.field.QuantumFieldVisualization; +import ru.mcs.q.field.Vector3D; + +import javax.swing.*; + +public class TriangularBipyramidDemo { + public static void main(String[] args) { + System.out.println("Starting Perfect Alignment Demo..."); + + // Создаем сетку тетраэдров + QuantumFieldMesh3D mesh = new QuantumFieldMesh3D(); + + // Создаем тетраэдры с начальными позициями, которые позволят им соединиться правильно + mesh.addTetrahedron("T1", new Vector3D(0, 0, 0), 1.0); + mesh.addTetrahedron("T2", new Vector3D(3, 0, 0), 1.0); // Смещен в сторону для соединения красными гранями + mesh.addTetrahedron("T3", new Vector3D(0, 3, 0), 1.0); // Смещен вверх для соединения синими гранями + mesh.addTetrahedron("T4", new Vector3D(0, 0, 3), 1.0); // Смещен вперед для соединения зелеными гранями + mesh.addTetrahedron("T5", new Vector3D(3, 0, 0), 1.0); // Смещен влево для соединения с T2 + + // Создаем соединения - теперь грани должны быть идеально параллельны + System.out.println("Creating perfectly aligned connections:"); + + // T1 и T2 соединяются красными гранями + mesh.connectTetrahedrons("T1", FaceColor.RED, "T2", FaceColor.RED); + + // T1 и T3 соединяются синими гранями + mesh.connectTetrahedrons("T1", FaceColor.BLUE, "T3", FaceColor.BLUE); + + // T1 и T4 соединяются зелеными гранями + mesh.connectTetrahedrons("T1", FaceColor.GREEN, "T4", FaceColor.GREEN); + + // T2 и T5 соединяются синими гранями + mesh.connectTetrahedrons("T2", FaceColor.BLUE, "T5", FaceColor.BLUE); + + // Запускаем колебания + mesh.startFieldOscillations(); + + // Создаем и показываем визуализацию + SwingUtilities.invokeLater(() -> { + JFrame frame = new JFrame("Perfectly Aligned Quantum Field"); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + QuantumFieldVisualization visualization = new QuantumFieldVisualization(mesh); + frame.add(visualization); + frame.pack(); + frame.setLocationRelativeTo(null); + frame.setVisible(true); + }); + + } +} \ No newline at end of file diff --git a/src/main/java/ru/mcs/q/VisualizationMain.java b/src/main/java/ru/mcs/q/VisualizationMain.java index cbb3d6f..8b1725b 100644 --- a/src/main/java/ru/mcs/q/VisualizationMain.java +++ b/src/main/java/ru/mcs/q/VisualizationMain.java @@ -9,7 +9,7 @@ public class VisualizationMain { public static void main(String[] args) { - System.out.println("Starting 3D Quantum Field Visualization..."); + System.out.println("Starting 3D Quantum Field Visualization with Face Orientation..."); // Создаем сетку тетраэдров QuantumFieldMesh3D mesh = new QuantumFieldMesh3D(); @@ -23,7 +23,8 @@ mesh.addTetrahedron("T6", new Vector3D(1, 1, 1), 0.5); mesh.addTetrahedron("T7", new Vector3D(-1, -1, -1), 0.5); - // Создаем соединения + // Создаем соединения - теперь грани будут ориентированы друг к другу + System.out.println("\nCreating oriented connections:"); mesh.connectTetrahedrons("T1", FaceColor.RED, "T2", FaceColor.RED); mesh.connectTetrahedrons("T1", FaceColor.BLUE, "T3", FaceColor.BLUE); mesh.connectTetrahedrons("T1", FaceColor.GREEN, "T4", FaceColor.GREEN); @@ -36,7 +37,7 @@ // Создаем и показываем визуализацию SwingUtilities.invokeLater(() -> { - JFrame frame = new JFrame("3D Quantum Field Visualization"); + JFrame frame = new JFrame("3D Quantum Field Visualization - Oriented Faces"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); QuantumFieldVisualization visualization = new QuantumFieldVisualization(mesh); diff --git a/src/main/java/ru/mcs/q/field/OctahedronDemo.java b/src/main/java/ru/mcs/q/field/OctahedronDemo.java new file mode 100644 index 0000000..29e785e --- /dev/null +++ b/src/main/java/ru/mcs/q/field/OctahedronDemo.java @@ -0,0 +1,42 @@ +package ru.mcs.q.field; + +import javax.swing.*; + +public class OctahedronDemo { + public static void main(String[] args) { + System.out.println("Starting Octahedron Formation Demo..."); + + // Создаем сетку тетраэдров + QuantumFieldMesh3D mesh = new QuantumFieldMesh3D(); + + // Создаем тетраэдры, которые будут образовывать октаэдр + mesh.addTetrahedron("T1", new Vector3D(0, 0, 0), 1.0); + mesh.addTetrahedron("T2", new Vector3D(2, 0, 0), 1.0); + + // Создаем соединение между красными гранями - должно образовать октаэдр + System.out.println("Creating octahedral connection:"); + mesh.connectTetrahedrons("T1", FaceColor.RED, "T2", FaceColor.RED); + + // Добавляем еще тетраэдры для демонстрации других соединений + mesh.addTetrahedron("T3", new Vector3D(0, 2, 0), 1.0); + mesh.addTetrahedron("T4", new Vector3D(0, 0, 2), 1.0); + + mesh.connectTetrahedrons("T1", FaceColor.BLUE, "T3", FaceColor.BLUE); + mesh.connectTetrahedrons("T1", FaceColor.GREEN, "T4", FaceColor.GREEN); + + // Запускаем колебания + mesh.startFieldOscillations(); + + // Создаем и показываем визуализацию + SwingUtilities.invokeLater(() -> { + JFrame frame = new JFrame("Octahedral Quantum Field Visualization"); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + QuantumFieldVisualization visualization = new QuantumFieldVisualization(mesh); + frame.add(visualization); + frame.pack(); + frame.setLocationRelativeTo(null); + frame.setVisible(true); + }); + } +} \ No newline at end of file diff --git a/src/main/java/ru/mcs/q/field/QuantumFieldMesh3D.java b/src/main/java/ru/mcs/q/field/QuantumFieldMesh3D.java index 5b5045d..248dc2a 100644 --- a/src/main/java/ru/mcs/q/field/QuantumFieldMesh3D.java +++ b/src/main/java/ru/mcs/q/field/QuantumFieldMesh3D.java @@ -51,8 +51,18 @@ double oscillation = (Math.random() - 0.5) * 0.1; double newEnergy = Math.max(0, tetrahedron.getEnergyLevel() + oscillation); - // Обновляем размер в зависимости от энергии - double newSize = 0.5 + newEnergy * 0.5; + // Можно добавить небольшие случайные вращения для анимации + if (Math.random() < 0.3) { + // Случайное небольшое вращение + Vector3D randomAxis = new Vector3D( + Math.random() - 0.5, + Math.random() - 0.5, + Math.random() - 0.5 + ); + double angle = (Math.random() - 0.5) * 0.1; + Quaternion rotation = Quaternion.fromAxisAngle(randomAxis, angle); + tetrahedron.setOrientation(rotation.multiply(tetrahedron.getOrientation())); + } }); } diff --git a/src/main/java/ru/mcs/q/field/QuantumFieldVisualization.java b/src/main/java/ru/mcs/q/field/QuantumFieldVisualization.java index 086e472..3b71015 100644 --- a/src/main/java/ru/mcs/q/field/QuantumFieldVisualization.java +++ b/src/main/java/ru/mcs/q/field/QuantumFieldVisualization.java @@ -4,7 +4,6 @@ import java.awt.*; import java.awt.event.*; import java.util.*; -import java.util.List; public class QuantumFieldVisualization extends JPanel { private final QuantumFieldMesh3D mesh; @@ -84,21 +83,28 @@ private void drawConnections(Graphics2D g2d) { for (Tetrahedron3D tetra : mesh.getTetrahedrons().values()) { - Vector3D pos1 = tetra.getPosition(); - for (Map.Entry entry : tetra.getConnections().entrySet()) { Tetrahedron3D connected = entry.getValue(); FaceColor color = entry.getKey(); - Vector3D pos2 = connected.getPosition(); + // Рисуем линию между центрами соединенных граней + int faceIndex1 = tetra.getFaceIndex(color); + int faceIndex2 = connected.getFaceIndex(color); - // Рисуем линию соединения - Point p1 = projectPoint(rotatePoint(pos1)); - Point p2 = projectPoint(rotatePoint(pos2)); + Vector3D faceCenter1 = tetra.getFaceCenter(faceIndex1); + Vector3D faceCenter2 = connected.getFaceCenter(faceIndex2); + + Point p1 = projectPoint(rotatePoint(faceCenter1)); + Point p2 = projectPoint(rotatePoint(faceCenter2)); g2d.setColor(toAwtColor(color)); - g2d.setStroke(new BasicStroke(2)); + g2d.setStroke(new BasicStroke(3, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); g2d.drawLine(p1.x, p1.y, p2.x, p2.y); + + // Рисуем маленькие кружки в центрах граней + g2d.setColor(toAwtColor(color).darker()); + g2d.fillOval(p1.x - 3, p1.y - 3, 6, 6); + g2d.fillOval(p2.x - 3, p2.y - 3, 6, 6); } } } @@ -119,42 +125,66 @@ projectedVertices[i] = projectPoint(rotatePoint(vertices[i])); } - // Рисуем грани + // Сначала рисуем непрозрачные грани for (int i = 0; i < faces.length; i++) { - int[] face = faces[i]; FaceColor faceColor = tetra.getFaceColor(i); - - // Пропускаем прозрачные грани if (faceColor == FaceColor.TRANSPARENT) continue; + int[] face = faces[i]; Polygon polygon = new Polygon(); for (int vertexIndex : face) { Point p = projectedVertices[vertexIndex]; polygon.addPoint(p.x, p.y); } - // Заливаем грань цветом с прозрачностью + // Определяем, является ли грань соединенной + boolean isConnected = tetra.getConnections().containsKey(faceColor); + + // Заливаем грань цветом с разной прозрачностью Color fillColor = toAwtColor(faceColor); - g2d.setColor(new Color(fillColor.getRed(), fillColor.getGreen(), fillColor.getBlue(), 100)); + if (isConnected) { + g2d.setColor(new Color(fillColor.getRed(), fillColor.getGreen(), fillColor.getBlue(), 150)); + } else { + g2d.setColor(new Color(fillColor.getRed(), fillColor.getGreen(), fillColor.getBlue(), 80)); + } g2d.fill(polygon); // Рисуем контур - g2d.setColor(fillColor); - g2d.setStroke(new BasicStroke(1)); + g2d.setColor(isConnected ? fillColor.darker() : fillColor); + g2d.setStroke(new BasicStroke(isConnected ? 2 : 1)); + g2d.draw(polygon); + } + + // Затем рисуем прозрачную грань (основание) + for (int i = 0; i < faces.length; i++) { + FaceColor faceColor = tetra.getFaceColor(i); + if (faceColor != FaceColor.TRANSPARENT) continue; + + int[] face = faces[i]; + Polygon polygon = new Polygon(); + for (int vertexIndex : face) { + Point p = projectedVertices[vertexIndex]; + polygon.addPoint(p.x, p.y); + } + + // Рисуем прозрачную грань пунктиром + g2d.setColor(new Color(150, 150, 150, 100)); + g2d.fill(polygon); + + g2d.setColor(Color.GRAY); + g2d.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, + 0, new float[]{3}, 0)); g2d.draw(polygon); } // Рисуем центр тетраэдра Point centerPoint = projectPoint(rotatePoint(tetra.getPosition())); g2d.setColor(Color.BLACK); - g2d.fillOval(centerPoint.x - 3, centerPoint.y - 3, 6, 6); + g2d.fillOval(centerPoint.x - 2, centerPoint.y - 2, 4, 4); // Подписываем тетраэдр + g2d.setColor(Color.BLACK); g2d.drawString(tetra.getId(), centerPoint.x + 5, centerPoint.y - 5); - - // Показываем энергию - g2d.setColor(Color.DARK_GRAY); - g2d.drawString(String.format("%.1f", tetra.getEnergyLevel()), centerPoint.x + 5, centerPoint.y + 15); } private Vector3D rotatePoint(Vector3D point) { @@ -191,6 +221,13 @@ g2d.drawString("Projection: " + projectionType, 10, 20); g2d.drawString("Scale: " + scale, 10, 40); g2d.drawString("Tetrahedrons: " + mesh.getTetrahedrons().size(), 10, 60); - g2d.drawString("Click to change projection, Drag to rotate, Scroll to zoom", 10, 80); + g2d.drawString("Connections: " + countConnections(), 10, 80); + g2d.drawString("Click to change projection, Drag to rotate, Scroll to zoom", 10, 100); + } + + private int countConnections() { + return mesh.getTetrahedrons().values().stream() + .mapToInt(t -> t.getConnections().size()) + .sum() / 2; } } \ No newline at end of file diff --git a/src/main/java/ru/mcs/q/field/Quaternion.java b/src/main/java/ru/mcs/q/field/Quaternion.java new file mode 100644 index 0000000..b25b8a7 --- /dev/null +++ b/src/main/java/ru/mcs/q/field/Quaternion.java @@ -0,0 +1,60 @@ +package ru.mcs.q.field; + +public class Quaternion { + public double w, x, y, z; + + public Quaternion(double w, double x, double y, double z) { + this.w = w; + this.x = x; + this.y = y; + this.z = z; + } + + public static Quaternion identity() { + return new Quaternion(1, 0, 0, 0); + } + + // Создание кватерниона из оси и угла вращения + public static Quaternion fromAxisAngle(Vector3D axis, double angle) { + double halfAngle = angle / 2; + double sinHalf = Math.sin(halfAngle); + double cosHalf = Math.cos(halfAngle); + + Vector3D normalizedAxis = normalize(axis); + return new Quaternion( + cosHalf, + normalizedAxis.x * sinHalf, + normalizedAxis.y * sinHalf, + normalizedAxis.z * sinHalf + ); + } + + private static Vector3D normalize(Vector3D v) { + double length = Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z); + if (length == 0) return new Vector3D(0, 0, 0); + return new Vector3D(v.x / length, v.y / length, v.z / length); + } + + // Умножение кватернионов + public Quaternion multiply(Quaternion other) { + return new Quaternion( + w * other.w - x * other.x - y * other.y - z * other.z, + w * other.x + x * other.w + y * other.z - z * other.y, + w * other.y - x * other.z + y * other.w + z * other.x, + w * other.z + x * other.y - y * other.x + z * other.w + ); + } + + // Применение вращения к вектору + public Vector3D rotateVector(Vector3D v) { + Quaternion qVec = new Quaternion(0, v.x, v.y, v.z); + Quaternion conjugate = new Quaternion(w, -x, -y, -z); + Quaternion result = this.multiply(qVec).multiply(conjugate); + return new Vector3D(result.x, result.y, result.z); + } + + @Override + public String toString() { + return String.format("(%.2f, %.2f, %.2f, %.2f)", w, x, y, z); + } +} \ No newline at end of file diff --git a/src/main/java/ru/mcs/q/field/Tetrahedron3D.java b/src/main/java/ru/mcs/q/field/Tetrahedron3D.java index 1dec243..7134460 100644 --- a/src/main/java/ru/mcs/q/field/Tetrahedron3D.java +++ b/src/main/java/ru/mcs/q/field/Tetrahedron3D.java @@ -1,22 +1,27 @@ package ru.mcs.q.field; -import java.awt.Color; import java.util.*; public class Tetrahedron3D { private final String id; private Vector3D position; + private Quaternion orientation; private final Map faces; private final Map connections; private double energyLevel; - private double size; + private final double size; - // Вершины тетраэдра относительно его центра - private final Vector3D[] vertices; + // Вершины тетраэдра в локальной системе координат + private final Vector3D[] localVertices; + // Нормали к граням в локальной системе координат + private final Vector3D[] faceNormals; + // Центры граней в локальной системе координат + private final Vector3D[] localFaceCenters; public Tetrahedron3D(String id, Vector3D position, double size) { this.id = id; this.position = position; + this.orientation = Quaternion.identity(); this.size = size; this.faces = new EnumMap<>(FaceColor.class); this.connections = new EnumMap<>(FaceColor.class); @@ -27,38 +32,100 @@ faces.put(color, FaceState.NEUTRAL); } - // Вычисляем вершины тетраэдра - this.vertices = calculateVertices(); + // Вычисляем вершины, нормали и центры граней + this.localVertices = calculateVertices(); + this.faceNormals = calculateFaceNormals(); + this.localFaceCenters = calculateFaceCenters(); } private Vector3D[] calculateVertices() { Vector3D[] verts = new Vector3D[4]; - - // Вершины правильного тетраэдра double a = size; - verts[0] = new Vector3D(0, 0, a * Math.sqrt(2.0/3.0)); // Верхняя вершина - verts[1] = new Vector3D(0, a/Math.sqrt(3), -a/Math.sqrt(6)); // Основание - verts[2] = new Vector3D(a/2, -a/(2*Math.sqrt(3)), -a/Math.sqrt(6)); // Основание - verts[3] = new Vector3D(-a/2, -a/(2*Math.sqrt(3)), -a/Math.sqrt(6)); // Основание + + // Вершины правильного тетраэдра с центром в начале координат + double height = a * Math.sqrt(2.0/3.0); + + verts[0] = new Vector3D(0, height/2, 0); // Верхняя вершина + verts[1] = new Vector3D(0, -height/3, a/2); // Основание + verts[2] = new Vector3D(a/2, -height/3, -a/4); // Основание + verts[3] = new Vector3D(-a/2, -height/3, -a/4); // Основание return verts; } + private Vector3D[] calculateFaceNormals() { + Vector3D[] normals = new Vector3D[4]; + + // Нормали для каждой грани (направлены наружу от центра) + normals[0] = calculateFaceNormal(localVertices[0], localVertices[1], localVertices[2]); // RED + normals[1] = calculateFaceNormal(localVertices[0], localVertices[2], localVertices[3]); // BLUE + normals[2] = calculateFaceNormal(localVertices[0], localVertices[3], localVertices[1]); // GREEN + normals[3] = calculateFaceNormal(localVertices[1], localVertices[3], localVertices[2]); // TRANSPARENT + + return normals; + } + + private Vector3D[] calculateFaceCenters() { + Vector3D[] centers = new Vector3D[4]; + int[][] faceIndices = getFacesIndices(); + + for (int i = 0; i < 4; i++) { + int[] indices = faceIndices[i]; + Vector3D sum = new Vector3D(0, 0, 0); + for (int idx : indices) { + sum = sum.add(localVertices[idx]); + } + centers[i] = new Vector3D(sum.x / 3, sum.y / 3, sum.z / 3); + } + + return centers; + } + + private Vector3D calculateFaceNormal(Vector3D v1, Vector3D v2, Vector3D v3) { + Vector3D edge1 = new Vector3D(v2.x - v1.x, v2.y - v1.y, v2.z - v1.z); + Vector3D edge2 = new Vector3D(v3.x - v1.x, v3.y - v1.y, v3.z - v1.z); + + // Векторное произведение + Vector3D cross = edge1.cross(edge2); + + // Нормализация + return cross.normalize(); + } + public Vector3D[] getWorldVertices() { Vector3D[] worldVerts = new Vector3D[4]; for (int i = 0; i < 4; i++) { - worldVerts[i] = vertices[i].add(position); + // Поворачиваем вершину и добавляем позицию + Vector3D rotated = orientation.rotateVector(localVertices[i]); + worldVerts[i] = rotated.add(position); } return worldVerts; } + public Vector3D getFaceNormal(int faceIndex) { + return orientation.rotateVector(faceNormals[faceIndex]); + } + + public Vector3D getFaceCenter(int faceIndex) { + Vector3D rotatedCenter = orientation.rotateVector(localFaceCenters[faceIndex]); + return rotatedCenter.add(position); + } + + public Vector3D getLocalFaceCenter(int faceIndex) { + return localFaceCenters[faceIndex]; + } + + public Vector3D getLocalFaceNormal(int faceIndex) { + return faceNormals[faceIndex]; + } + // Грани тетраэдра (индексы вершин) public int[][] getFacesIndices() { return new int[][] { - {0, 1, 2}, // Грань 0 - {0, 2, 3}, // Грань 1 - {0, 3, 1}, // Грань 2 - {1, 3, 2} // Грань 3 (основание) + {0, 1, 2}, // Грань 0 - RED + {0, 2, 3}, // Грань 1 - BLUE + {0, 3, 1}, // Грань 2 - GREEN + {1, 3, 2} // Грань 3 - TRANSPARENT (основание) }; } @@ -67,6 +134,14 @@ return colors[faceIndex]; } + public int getFaceIndex(FaceColor color) { + FaceColor[] colors = {FaceColor.RED, FaceColor.BLUE, FaceColor.GREEN, FaceColor.TRANSPARENT}; + for (int i = 0; i < colors.length; i++) { + if (colors[i] == color) return i; + } + return -1; + } + public boolean connect(FaceColor face, Tetrahedron3D other, FaceColor otherFace) { if (other == null) { return false; @@ -74,6 +149,9 @@ if (face == otherFace && face != FaceColor.TRANSPARENT) { if (!connections.containsKey(face) && !other.connections.containsKey(otherFace)) { + // Ориентируем тетраэдры для идеального параллельного соединения + alignFacesPerfectly(this, face, other, otherFace); + connections.put(face, other); other.connections.put(otherFace, this); @@ -83,16 +161,98 @@ faces.put(face, FaceState.ATTRACTED); other.faces.put(otherFace, FaceState.ATTRACTED); + System.out.printf("Perfect parallel connection: %s[%s] ↔ %s[%s]%n", + id, face, other.id, otherFace); return true; } } return false; } + private void alignFacesPerfectly(Tetrahedron3D tetra1, FaceColor face1, + Tetrahedron3D tetra2, FaceColor face2) { + int faceIndex1 = tetra1.getFaceIndex(face1); + int faceIndex2 = tetra2.getFaceIndex(face2); + + // Получаем нормали и центры граней в локальных координатах + Vector3D normal1 = tetra1.getLocalFaceNormal(faceIndex1); + Vector3D normal2 = tetra2.getLocalFaceNormal(faceIndex2); + Vector3D center1 = tetra1.getLocalFaceCenter(faceIndex1); + Vector3D center2 = tetra2.getLocalFaceCenter(faceIndex2); + + // Мы хотим, чтобы нормаль второй грани была противоположна нормали первой + Vector3D desiredNormal2 = normal1.negate(); + + // Находим вращение, которое переводит normal2 в desiredNormal2 + Quaternion rotation = findRotation(normal2, desiredNormal2); + + // Применяем это вращение ко второму тетраэдру + tetra2.orientation = rotation.multiply(tetra2.orientation); + + // Теперь позиционируем тетраэдры так, чтобы грани были параллельны и обращены друг к другу + Vector3D worldFaceCenter1 = tetra1.getFaceCenter(faceIndex1); + Vector3D worldFaceCenter2 = tetra2.getFaceCenter(faceIndex2); + + // Вычисляем вектор между центрами граней + Vector3D between = worldFaceCenter2.subtract(worldFaceCenter1); + + // Мы хотим, чтобы этот вектор был параллелен нормали первой грани + // Проецируем вектор between на нормаль первой грани + Vector3D worldNormal1 = tetra1.getFaceNormal(faceIndex1); + double projectionLength = between.dot(worldNormal1); + Vector3D projection = worldNormal1.multiply(projectionLength); + + // Корректируем позицию второго тетраэдра + Vector3D correction = between.subtract(projection); + tetra2.position = tetra2.position.subtract(correction); + + // Добавляем небольшое расстояние между гранями для визуализации + double separation = size * 0.1; + tetra2.position = tetra2.position.add(worldNormal1.multiply(separation)); + } + + private Quaternion findRotation(Vector3D from, Vector3D to) { + from = from.normalize(); + to = to.normalize(); + + double dot = from.dot(to); + + // Если векторы уже совпадают, возвращаем единичный кватернион + if (dot > 0.99999) { + return Quaternion.identity(); + } + + // Если векторы противоположны, выбираем произвольную ось + if (dot < -0.99999) { + // Выбираем ось, перпендикулярную from + Vector3D axis = findPerpendicularAxis(from); + return Quaternion.fromAxisAngle(axis, Math.PI); + } + + // Общий случай: вычисляем ось и угол вращения + Vector3D axis = from.cross(to).normalize(); + double angle = Math.acos(dot); + + return Quaternion.fromAxisAngle(axis, angle); + } + + private Vector3D findPerpendicularAxis(Vector3D v) { + // Находим произвольную ось, перпендикулярную v + if (Math.abs(v.x) > 0.1) { + return new Vector3D(-v.y, v.x, 0).normalize(); + } else if (Math.abs(v.y) > 0.1) { + return new Vector3D(0, -v.z, v.y).normalize(); + } else { + return new Vector3D(v.z, 0, -v.x).normalize(); + } + } + // Getters public String getId() { return id; } public Vector3D getPosition() { return position; } public void setPosition(Vector3D position) { this.position = position; } + public Quaternion getOrientation() { return orientation; } + public void setOrientation(Quaternion orientation) { this.orientation = orientation; } public double getEnergyLevel() { return energyLevel; } public Map getFaces() { return faces; } public Map getConnections() { return connections; } diff --git a/src/main/java/ru/mcs/q/field/Vector3D.java b/src/main/java/ru/mcs/q/field/Vector3D.java index fa97804..5806347 100644 --- a/src/main/java/ru/mcs/q/field/Vector3D.java +++ b/src/main/java/ru/mcs/q/field/Vector3D.java @@ -13,10 +13,40 @@ return new Vector3D(x + other.x, y + other.y, z + other.z); } + public Vector3D subtract(Vector3D other) { + return new Vector3D(x - other.x, y - other.y, z - other.z); + } + public Vector3D multiply(double scalar) { return new Vector3D(x * scalar, y * scalar, z * scalar); } + public double dot(Vector3D other) { + return x * other.x + y * other.y + z * other.z; + } + + public Vector3D cross(Vector3D other) { + return new Vector3D( + y * other.z - z * other.y, + z * other.x - x * other.z, + x * other.y - y * other.x + ); + } + + public double length() { + return Math.sqrt(x * x + y * y + z * z); + } + + public Vector3D normalize() { + double len = length(); + if (len == 0) return new Vector3D(0, 0, 0); + return new Vector3D(x / len, y / len, z / len); + } + + public Vector3D negate() { + return new Vector3D(-x, -y, -z); + } + @Override public String toString() { return String.format("(%.2f, %.2f, %.2f)", x, y, z);