I have the following test code where I have a ClothMesh (from FXyz lib) that I
can drag, rotate and drag my circle handles. All works well, FXyz is great. Now I want to use SegmentedSphereMesh, it mostly work except that my circle handles are 2D and not wrapped around the sphere. I know the possible problems mixing 2D and 3D. However, it is so close to working; how can I make my handles work with the sphere, or what would be another way to do the same function.
Note, I do not want to control the shape/mesh by moving the camera around.
import org.fxyz.shapes.complex.cloth.ClothMesh
import org.fxyz.shapes.primitives.SegmentedSphereMesh
import scalafx.Includes._
import scalafx.application.JFXApp
import scalafx.application.JFXApp.PrimaryStage
import scalafx.beans.property.DoubleProperty
import scalafx.collections.ObservableFloatArray
import scalafx.scene.image.Image
import scalafx.scene.input.{MouseButton, MouseEvent}
import scalafx.scene.paint.PhongMaterial
import scalafx.scene.shape._
import scalafx.scene.transform.Rotate
import scalafx.scene._
import scalafx.scene.paint.Color
/**
* left mouse to drag the meshView and also to drag the handles
* right mouse drag + ctrl to rotate about X axis
* right mouse drag + alt to rotate about Y axis
* right mouse drag + shift to rotate about Z axis
*/
object ClothTest2 extends JFXApp {
private var dx = 0.0
private var dy = 0.0
stage = new PrimaryStage {
scene = new Scene(600, 600, true, SceneAntialiasing.Balanced) {
fill = Color.LightGray
val testImg = "https://upload.wikimedia.org/wikipedia/commons/c/c4/PM5544_with_non-PAL_signals.png"
val img = new Image(testImg, 400, 400, false, true)
val meshView = new SegmentedSphereMesh(20, 4, 2, 200d)
// val meshView = new ClothMesh(4, 4, 200, 200, 0.5, 0.5, 1.0)
meshView.setDrawMode(DrawMode.Fill)
meshView.setCullFace(CullFace.None)
meshView.style = "-fx-background-color: #00000000"
meshView.setMaterial(new PhongMaterial(Color.White, img, null, null, null))
val controller = new MeshController(meshView.getMesh().asInstanceOf[javafx.scene.shape.TriangleMesh].points)
val viewGroup = new Group(meshView, controller)
root = new Group(new AmbientLight(Color.White), viewGroup) { translateX = 70; translateY = 70 }
camera = new PerspectiveCamera(true) {
nearClip = 0.0
farClip = 100000.0
fieldOfView = 42
verticalFieldOfView = true
translateZ = -900
}
val rotHandler = new RotHandler(viewGroup)
onMouseDragged = (event: MouseEvent) => {
rotHandler.onMouseDragged(event)
if (event.button == MouseButton.PRIMARY) {
viewGroup.layoutX = event.sceneX + dx
viewGroup.layoutY = event.sceneY + dy
event.consume()
}
}
onMousePressed = (event: MouseEvent) => {
rotHandler.onMousePressed(event)
dx = viewGroup.layoutX.value - event.sceneX
dy = viewGroup.layoutY.value - event.sceneY
event.consume()
}
}
}
}
class CircleHandle(color: Color) extends Circle {
radius = 8
var dx = 0.0
var dy = 0.0
fill <== when(hover) choose Color.Red.deriveColor(1, 1, 1, 0.4) otherwise color.deriveColor(1, 1, 1, 0.4)
strokeWidth <== when(hover) choose 3 otherwise 2
stroke = color
onMousePressed = (event: MouseEvent) => {
dx = centerX.value - event.x
dy = centerY.value - event.y
event.consume()
}
onMouseDragged = (event: MouseEvent) => {
centerX = event.x + dx
centerY = event.y + dy
event.consume()
}
}
class MeshController(thePoints: ObservableFloatArray) extends Group {
children = for (i <- 0 until thePoints.size by 3) yield new CircleHandle(Color.Yellow) {
centerX() = thePoints.get(i)
centerX.onChange { (obs, oldVal, newVal) => thePoints.set(i, newVal.floatValue()) }
centerY() = thePoints.get(i + 1)
centerY.onChange { (obs, oldVal, newVal) => thePoints.set(i + 1, newVal.floatValue()) }
}
}
class RotHandler(val viewer: Group) {
private val angleX = DoubleProperty(0)
private val angleY = DoubleProperty(0)
private val angleZ = DoubleProperty(0)
private var anchorX = 0d
private var anchorY = 0d
private val rotX = new Rotate { angle <== angleX; axis = Rotate.XAxis }
private val rotY = new Rotate { angle <== angleY; axis = Rotate.YAxis }
private val rotZ = new Rotate { angle <== angleZ; axis = Rotate.ZAxis }
viewer.transforms = Seq(rotX, rotY, rotZ)
def onMousePressed(event: MouseEvent) = {
anchorX = event.sceneX
anchorY = event.sceneY
event.consume()
}
def onMouseDragged(event: MouseEvent) = {
// right mouse only
if (event.button == MouseButton.SECONDARY) {
event match {
// rotation about the Y axis, dragging the mouse in the x direction
case ev if ev.altDown => angleY() = anchorX - event.sceneX
// rotation about the X axis, dragging the mouse in the y direction
case ev if ev.controlDown => angleX() = anchorY - event.sceneY
// rotation about the Z axis, dragging the mouse in the x direction
case ev if ev.shiftDown => angleZ() = anchorX - event.sceneX
case _ => // ignore everything else
}
}
event.consume()
}
}
Related
I have a TriangleMesh with a texture/diffuse map that is a 1024x1024 texture which is fully black except the last 3 lines which are filled with red, green and blue.
I gave each vertex of each triangle a constant V value (either 1021, 1022 or 1023) added 0.5 to it to center and divided it by the texture's height so it would only use one of the 3 colors and arbitrary U values.
// arbitrary u values.
float u1 = 0.1f;
float u2 = 0.3f;
float u3 = 0.9f;
int randomY = ThreadLocalRandom.current()
.nextInt((int) atlas.getHeight() - 3, (int) atlas.getHeight());
float y = randomY + 0.5f;
float v = (float) (y / atlas.getHeight());
int texIndex1 = mesh.addUV(u1, v);
int texIndex2 = mesh.addUV(u2, v);
int texIndex3 = mesh.addUV(u3, v);
mesh.getFaces().addAll(
vertexIndex1, texIndex1,
vertexIndex2, texIndex2,
vertexIndex3, texIndex3
);
The addUV method looks like this(i have my own mesh class that extends TriangleMesh which contains some helper methods)
public int addUV(float u, float v) {
int cur = getTexCoords().size() / 2;
getTexCoords().addAll(u, v);
return cur;
}
The expected result is a mesh that has it's triangles colored solid red, green and blue because V is constant and each line(y) is filled with a single color however what i got instead was a bunch of different colors that change as u zoom in/out.
If i use the same U value for each vertex as well, it does give the correct result but i don't understand why it wouldn't do the same with arbitrary U values given that the color at any given U is the exact same.
The current result(gif to show the color changing): https://i.imgur.com/4lTcLfH.gif | As seen it actually does show the correct colors but only if u zoom in a lot
The expected result(can be produced if i have constant U values as well like 0.5, 0.5, 0.5): https://i.imgur.com/x35u6xv.gif | Looks as it should, doesn't change when u zoom in/out
The texture i used as the diffuse map: https://i.imgur.com/BB6P7z6.png
Minimal reproducible example with a quad made of 2 triangles:
Create a main method (either in the same class or another) and add: Application.launch(TextureMappingIssue.class); i didn't add it in my example as depending on the setup, the main method must be in a different class
import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.*;
import javafx.scene.image.Image;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.MeshView;
import javafx.scene.shape.TriangleMesh;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Scale;
import javafx.scene.transform.Translate;
import javafx.stage.Stage;
import javax.imageio.ImageIO;
import java.nio.file.Path;
/**
* Note that when the application is first opened without
* the camera moved, it looks as it should, as soon as
* the camera is moved (i.e if the mouse is moved in this case)
* it looks completely different, even if the camera is moved slightly
*/
public class TextureMappingIssue extends Application {
private static final int WIDTH = 800;
private static final int HEIGHT = 600;
private AnchorPane modelPane;
private Group scene;
private SubScene subScene;
#Override
public void start(Stage primaryStage) throws Exception {
modelPane = new AnchorPane();
modelPane.setPrefWidth(WIDTH);
modelPane.setPrefHeight(HEIGHT);
initScene();
// smaller palette = looks correct until u zoom out more
int paletteWidth = 1024;
int paletteHeight = 1024;
/*
* amount of copies for red, green, blue(the colors at the bottom), the center one is picked
* note that copies = 1 just writes the original color, copies = 2 writes the original + 1 copy and so on (so with copies = 3, it writes the color 3 times and picks the 2nd one for v)
*/
int copies = 1;
float QUAD_SCALE = 1f;
float[] vertices = {
-QUAD_SCALE, -QUAD_SCALE, 0,
-QUAD_SCALE, QUAD_SCALE, 0,
QUAD_SCALE, QUAD_SCALE, 0,
QUAD_SCALE, -QUAD_SCALE, 0
};
int[] indices = {
0, 0, 1, 1, 2, 2, // first triangle
0, 3, 2, 4, 3, 5, // second triangle
};
// set these to 0f, 0f, 0f (or any value as long as they're identical to get the expected result)
float u1 = 0.1f;
float u2 = 0.3f;
float u3 = 0.5f;
int colorIndex = 1; // either 0, 1 or 2 (red, green, blue)
int offset = (3 - colorIndex) * copies;
// v is constant for each vertex in my actual application as well.
float v1 = (paletteHeight - offset + (copies / 2) + 0.5f) / paletteHeight;
float[] texCoords = {
u1, v1, u2, v1, u3, v1
};
Image palette = generatePalette(paletteWidth, paletteHeight, copies);
ImageIO.write(SwingFXUtils.fromFXImage(palette, null), "png", Path.of("./testpalette.png")
.toFile());
TriangleMesh triangle = new TriangleMesh();
triangle.getPoints().addAll(vertices);
triangle.getFaces().addAll(indices);
triangle.getTexCoords().addAll(texCoords);
triangle.getTexCoords().addAll(texCoords);
MeshView view = new MeshView(triangle);
PhongMaterial material = new PhongMaterial();
material.setDiffuseMap(palette);
//material.setSpecularMap(specular);
// material.setSpecularPower(32); // default
view.setMaterial(material);
scene.getChildren().add(view);
Scene scene = new Scene(modelPane, WIDTH, HEIGHT, true, SceneAntialiasing.BALANCED);
scene.setFill(Color.BLACK);
primaryStage.setScene(scene);
primaryStage.show();
}
private void initScene() {
scene = new Group();
//Group grid = new Grid3D().create(48f, 1.25f);
//scene.getChildren().add(grid);
subScene = createScene3D();
scene.getChildren().add(new AmbientLight(Color.WHITE));
modelPane.getChildren().addAll(subScene);
}
private SubScene createScene3D() {
SubScene scene3d = new SubScene(scene, modelPane.getPrefWidth(), modelPane.getPrefHeight(), true, SceneAntialiasing.BALANCED);
scene3d.setFill(Color.rgb(25, 25, 25));
new OrbitCamera(scene3d, scene);
return scene3d;
}
private Image generatePalette(int width, int height, int copies) {
WritableImage palette = new WritableImage(width, height);
Color[] debugColors = {Color.RED, Color.GREEN, Color.BLUE};
PixelWriter writer = palette.getPixelWriter();
int offset = height - (debugColors.length * copies);
for (int y = 0; y < offset; y++) {
for (int x = 0; x < width; x++) {
writer.setColor(x, y, Color.BLACK);
}
}
int colorOff = 0;
for (int y = offset; y < height - (copies - 1); y += copies) {
Color c = debugColors[colorOff];
if (c == Color.GREEN) {
System.out.println("Y = " + y);
}
for (int k = 0; k < copies; k++) {
for (int x = 0; x < width; x++) {
writer.setColor(x, y + k, c);
}
}
colorOff++;
}
return palette;
}
private Image generateSpecular(int width, int height) {
WritableImage specular = new WritableImage(width, height);
PixelWriter writer = specular.getPixelWriter();
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
writer.setColor(x, y, Color.WHITE);
}
}
return specular;
}
/*
* Orbit camera
*/
private static class OrbitCamera {
private final SubScene subScene;
private final Group root3D;
private final double MAX_ZOOM = 300.0;
public OrbitCamera(SubScene subScene, Group root) {
this.subScene = subScene;
this.root3D = root;
init();
}
private void init() {
camera.setNearClip(0.1D);
camera.setFarClip(MAX_ZOOM * 1.15D);
camera.getTransforms().addAll(
yUpRotate,
cameraPosition,
cameraLookXRotate,
cameraLookZRotate
);
Group rotateGroup = new Group();
try {
rotateGroup.getChildren().addAll(cameraXform);
} catch (Exception e) {
e.printStackTrace();
}
cameraXform.ry.setAngle(0);
cameraXform.rx.setAngle(-18);
cameraXform.getChildren().add(cameraXform2);
cameraXform2.getChildren().add(cameraXform3);
cameraXform3.getChildren().add(camera);
cameraPosition.setZ(-cameraDistance);
root3D.getChildren().addAll(rotateGroup);
subScene.setCamera(camera);
subScene.setOnScroll(event -> {
double zoomFactor = 1.05;
double deltaY = event.getDeltaY();
if (deltaY < 0) {
zoomFactor = 2.0 - zoomFactor;
}
double z = cameraPosition.getZ() / zoomFactor;
z = Math.max(z, -MAX_ZOOM);
z = Math.min(z, 10.0);
cameraPosition.setZ(z);
});
subScene.setOnMousePressed(event -> {
if (!event.isAltDown()) {
dragStartX = event.getSceneX();
dragStartY = event.getSceneY();
dragStartRotateX = cameraXRotate.getAngle();
dragStartRotateY = cameraYRotate.getAngle();
mousePosX = event.getSceneX();
mousePosY = event.getSceneY();
mouseOldX = event.getSceneX();
mouseOldY = event.getSceneY();
}
});
subScene.setOnMouseDragged(event -> {
if (!event.isAltDown()) {
double modifier = 1.0;
double modifierFactor = 0.3;
if (event.isControlDown()) modifier = 0.1;
if (event.isSecondaryButtonDown()) modifier = 0.035;
mouseOldX = mousePosX;
mouseOldY = mousePosY;
mousePosX = event.getSceneX();
mousePosY = event.getSceneY();
mouseDeltaX = mousePosX - mouseOldX;
mouseDeltaY = mousePosY - mouseOldY;
double flip = -1.0;
if (event.isSecondaryButtonDown()) {
double newX = cameraXform2.t.getX() + flip * mouseDeltaX * modifierFactor * modifier * 2.0;
double newY = cameraXform2.t.getY() + 1.0 * -mouseDeltaY * modifierFactor * modifier * 2.0;
cameraXform2.t.setX(newX);
cameraXform2.t.setY(newY);
} else if (event.isPrimaryButtonDown()) {
double yAngle = cameraXform.ry.getAngle() - 1.0 * -mouseDeltaX * modifierFactor * modifier * 2.0;
double xAngle = cameraXform.rx.getAngle() + flip * mouseDeltaY * modifierFactor * modifier * 2.0;
cameraXform.ry.setAngle(yAngle);
cameraXform.rx.setAngle(xAngle);
}
}
});
}
private final PerspectiveCamera camera = new PerspectiveCamera(true);
private final Rotate cameraXRotate = new Rotate(-20.0, 0.0, 0.0, 0.0, Rotate.X_AXIS);
private final Rotate cameraYRotate = new Rotate(-20.0, 0.0, 0.0, 0.0, Rotate.Y_AXIS);
private final Rotate cameraLookXRotate = new Rotate(0.0, 0.0, 0.0, 0.0, Rotate.X_AXIS);
private final Rotate cameraLookZRotate = new Rotate(0.0, 0.0, 0.0, 0.0, Rotate.Z_AXIS);
private final Translate cameraPosition = new Translate(0.0, 0.0, 0.0);
private Xform cameraXform = new Xform();
private Xform cameraXform2 = new Xform();
private Xform cameraXform3 = new Xform();
private double cameraDistance = 25.0;
private double dragStartX = 0;
private double dragStartY = 0;
private double dragStartRotateX = 0;
private double dragStartRotateY = 0;
private double mousePosX = 0;
private double mousePosY = 0;
private double mouseOldX = 0;
private double mouseOldY = 0;
private double mouseDeltaX = 0;
private double mouseDeltaY = 0;
private Rotate yUpRotate = new Rotate(0.0, 0.0, 0.0, 0.0, Rotate.X_AXIS);
public Camera getCamera() {
return camera;
}
public Xform getCameraXform() {
return cameraXform;
}
}
private static class Xform extends Group {
Translate t = new Translate();
Translate p = new Translate();
public Rotate rx = new Rotate();
public Rotate ry = new Rotate();
Rotate rz = new Rotate();
Scale s = new Scale();
public Xform() {
rx.setAxis(Rotate.X_AXIS);
ry.setAxis(Rotate.Y_AXIS);
rz.setAxis(Rotate.Z_AXIS);
getTransforms().addAll(t, rz, ry, rx, s);
}
}
}
Edit: updated the code to support generating multiple copies of a single color and picking the center one, however this doesn't solve the issue either, it's just less visible :/
Update: the issue can be reproduced even with a 128x3 image (where it's just the red, green, blue color with 128 pixel rows)
Update 2: I can reproduce the same issue in my original code even with my original palette (that is a 128x512 image of colors that are all potentially used)
Update 3: I have decided to go for per pixel shading instead (i.e provide my mesh with a set of normals and add light sources to the scene(other than ambient)) what i wanted to do initially with the palette was to export all the vertex colors generated from a function that emulates gouraud shading but because of these interpolation issues i have went for per pixel shading (which looks better anyway, altho ideally i would've wanted to emulate gouraud shading as the game engine i use for which the javafx program is for also uses gouraud shading)
With the help of several posts from here I created a draggable and rotable rectangle. I want that user can drag an anchor to rotate rectangle from center and resize it by one corner while opposite corner stays at same place, if rectangle keep unrotated calculations to do it are very simple. Unfortunally in this case the bounding rectangle can be rotated and trigonometry and how apply here is not very clear to me. After days googling so far any attemp I made to make this work fail miserably. Sorry for any mistake, english is not my native language. Thanks in advance
import javafx.application.Application;
import javafx.geometry.Point2D;
import javafx.stage.Stage;
import javafx.scene.Cursor;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.scene.transform.Rotate;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
final Delta dragDelta = new Delta();
final Rotate rotate = new Rotate();
Wrapper<Point2D> mouseLocation = new Wrapper<>();
Pane root = new Pane();
Rectangle rect = new Rectangle(100,100);
//set pivot on rectangle center
rotate.setPivotX((rect.getX() + rect.getWidth())/2);
rotate.setPivotY((rect.getY() + rect.getHeight())/2);
rect.setStyle(
"-fx-stroke: blue; " +
"-fx-stroke-width: 2px; " +
"-fx-stroke-dash-array: 12 2 4 2; " +
"-fx-stroke-dash-offset: 6; " +
"-fx-stroke-line-cap: butt; " +
"-fx-fill: rgba(255, 255, 255, .0);"
);
Group group = new Group();
// make a rectangle movable by dragging it around with the mouse.
rect.setOnMousePressed(mouseEvent -> {
// record a delta distance for the drag and drop operation.
mouseLocation.value = new Point2D(mouseEvent.getSceneX(), mouseEvent.getSceneY());
group.getScene().setCursor(Cursor.MOVE);
});
rect.setOnMouseReleased(mouseEvent -> {
mouseLocation.value = null ;
group.getScene().setCursor(Cursor.HAND);
});
rect.setOnMouseDragged(mouseEvent -> {
// Get the mouse deltas
double deltaX = mouseEvent.getSceneX() - mouseLocation.value.getX();
double deltaY = mouseEvent.getSceneY() - mouseLocation.value.getY();
group.setTranslateX(group.getTranslateX() + deltaX);
group.setTranslateY(group.getTranslateY() + deltaY);
mouseLocation.value = new Point2D(mouseEvent.getSceneX(), mouseEvent.getSceneY());
});
Circle topLeft = new Circle(7);
topLeft.centerXProperty().bind(rect.xProperty());
topLeft.centerYProperty().bind(rect.yProperty());
//Here is the tricky part
topLeft.setOnMousePressed(e->{
});
topLeft.setOnMouseDragged(e->{
});
//Anchor for rotate the rectangle
Circle rotateCircle = new Circle(7);
rotateCircle.centerXProperty().bind(rect.xProperty().add(rect.widthProperty()).divide(2));
rotateCircle.centerYProperty().bind(rect.yProperty().subtract(25d));
rotateCircle.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> {
dragDelta.x = event.getSceneX();
dragDelta.x = event.getSceneY();
});
// When it's dragged rotate the box
rotateCircle.addEventHandler(MouseEvent.MOUSE_DRAGGED, event -> {
var localToScene = group.getLocalToSceneTransform();
double x1 = dragDelta.x;
double y1 = dragDelta.y;
var x2 = event.getSceneX();
var y2 = event.getSceneY();
var px = rotate.getPivotX() + localToScene.getTx();
var py = rotate.getPivotY() + localToScene.getTy();
// Work out the angle rotated
double th1 = deltaAngle(x1, y1, px, py);
double th2 = deltaAngle(x2, y2, px, py);
var angle = rotate.getAngle();
angle += th2 - th1;
// Rotate the rectangle
rotate.setAngle(angle);
dragDelta.x = event.getSceneX();
dragDelta.y = event.getSceneY();
});
group.getChildren().addAll(rect, rotateCircle, topLeft);
group.setLayoutX(100);
group.setLayoutY(100);
group.getTransforms().add(rotate);
root.getChildren().add(group);
Scene scene = new Scene(root,400,400);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
}
// Return the angle from 0 to 360
public static double deltaAngle (double x, double y, double px, double py) {
double dx = x - px;
double dy = y - py;
double angle = Math.abs(Math.toDegrees(Math.atan2(dy, dx)));
if(dy < 0) {
angle = 360 - angle;
}
return angle;
}
// records relative x and y co-ordinates.
private static class Delta {
double x, y;
}
static class Wrapper<T> {
T value ;
}
public static void main(String[] args) {
launch(args);
}
}
Something like this...
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Cursor;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.scene.transform.NonInvertibleTransformException;
import javafx.scene.transform.Rotate;
public class FancyRectangle extends Application {
#Override
public void start(Stage primaryStage) {
final MouseContext refPoint = new MouseContext();
final Rotate rotate = new Rotate();
Pane root = new Pane();
Rectangle rect = new Rectangle(100, 100);
//set pivot on rectangle center
rotate.setPivotX((rect.getX() + rect.getWidth()) / 2);
rotate.setPivotY((rect.getY() + rect.getHeight()) / 2);
rect.setStyle(
"-fx-stroke: blue; "
+ "-fx-stroke-width: 2px; "
+ "-fx-stroke-dash-array: 12 2 4 2; "
+ "-fx-stroke-dash-offset: 6; "
+ "-fx-stroke-line-cap: butt; "
+ "-fx-fill: rgba(255, 255, 255, .0);"
);
Group group = new Group();
// make a rectangle movable by dragging it around with the mouse.
rect.setOnMousePressed(mouseEvent -> {
// record a delta distance for the drag and drop operation.
refPoint.sceneX = mouseEvent.getSceneX();
refPoint.sceneY = mouseEvent.getSceneY();
group.getScene().setCursor(Cursor.MOVE);
});
rect.setOnMouseReleased(mouseEvent -> {
group.getScene().setCursor(Cursor.HAND);
});
rect.setOnMouseDragged(mouseEvent -> {
// Get the mouse deltas
double deltaX = mouseEvent.getSceneX() - refPoint.sceneX;
double deltaY = mouseEvent.getSceneY() - refPoint.sceneY;
group.setTranslateX(group.getTranslateX() + deltaX);
group.setTranslateY(group.getTranslateY() + deltaY);
refPoint.sceneX = mouseEvent.getSceneX();
refPoint.sceneY = mouseEvent.getSceneY();
});
Circle resizeCircle = new Circle(7);
resizeCircle.centerXProperty().bind(rect.xProperty());
resizeCircle.centerYProperty().bind(rect.yProperty());
// Handle resize
resizeCircle.setOnMousePressed(e -> {
var sceneCenter = resizeCircle.localToScene(resizeCircle.getCenterX(), resizeCircle.getCenterY());
refPoint.sceneX = e.getSceneX();
refPoint.sceneY = e.getSceneY();
refPoint.offX = rect.getWidth();
refPoint.offY = rect.getHeight();
});
resizeCircle.setOnMouseDragged(e -> {
try {
var sceneToLocal = group.getLocalToSceneTransform().createInverse();
var localRef = sceneToLocal.transform(refPoint.sceneX, refPoint.sceneY);
var localMouse = sceneToLocal.transform(e.getSceneX(), e.getSceneY());
var dx = localMouse.getX() - localRef.getX();
var dy = localMouse.getY() - localRef.getY();
var x = localMouse.getX();
var y = localMouse.getY();
var w = Math.abs(refPoint.offX-dx);
var h = Math.abs(refPoint.offY-dy);
rect.setX(x);
rect.setY(y);
rect.setWidth(w);
rect.setHeight(h);
rotate.setPivotX(x + w/2);
rotate.setPivotY(y + h/2);
} catch (NonInvertibleTransformException ex) {}
e.consume();
});
// Anchor for rotate the rectangle
Circle rotateCircle = new Circle(7);
rotateCircle.centerXProperty().bind(rect.xProperty().add(rect.widthProperty().divide(2)));
rotateCircle.centerYProperty().bind(rect.yProperty().subtract(25d));
rotateCircle.addEventHandler(MouseEvent.MOUSE_PRESSED, e -> {
var sceneCenter = rotateCircle.localToScene(rotateCircle.getCenterX(), rotateCircle.getCenterY());
refPoint.sceneX = e.getSceneX();
refPoint.sceneY = e.getSceneY();
refPoint.offX = sceneCenter.getX()-refPoint.sceneX;
refPoint.offY = sceneCenter.getY()-refPoint.sceneY;
});
// When it's dragged rotate the box
rotateCircle.addEventHandler(MouseEvent.MOUSE_DRAGGED, event -> {
var localToScene = group.getLocalToSceneTransform();
var x2 = event.getSceneX()+refPoint.offX;
var y2 = event.getSceneY()+refPoint.offY;
var pivotPoint = localToScene.transform(rotate.getPivotX(), rotate.getPivotY());
double angle = Math.toDegrees(Math.atan2(x2-pivotPoint.getX(), pivotPoint.getY()-y2));
// Rotate the rectangle
rotate.setAngle(angle);
});
group.getChildren().addAll(rect, rotateCircle, resizeCircle);
group.setLayoutX(100);
group.setLayoutY(100);
group.getTransforms().add(rotate);
root.getChildren().add(group);
Scene scene = new Scene(root, 400, 400);
//scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
}
// records relative x and y co-ordinates.
private static class MouseContext {
double sceneX, sceneY;
double offX, offY;
}
public static void main(String[] args) {
launch(args);
}
}
Trying to create dynamically a series of circles with Javafx. After typing the number of circles i got this:
But actually i want that my circles be in that position:
Here is my code and thanks for any hints!!
int k = 5;
for (int i = 0; i < nbNoeuds; i++) {
Noeudfx circle = new Noeudfx(k * 2, k * 2, 1, String.valueOf(i));
Label id = new Label(String.valueOf(i));
noeuds.getChildren().add(id);
id.setLayoutX(k * 2 - 20);
id.setLayoutY(k * 2 - 20);
id.setBlendMode(BlendMode.DIFFERENCE);
k += 10;
FillTransition ft1 = new FillTransition(Duration.millis(300), circle, Color.RED, Color.BLACK);
ft1.play();
noeuds.getChildren().add(circle);
ScaleTransition tr = new ScaleTransition(Duration.millis(100), circle);
tr.setByX(10f);
tr.setByY(10f);
tr.setInterpolator(Interpolator.EASE_OUT);
tr.play();
}
}
public class Noeudfx extends Circle {
Noeud noeud;
Point point;
Label distance = new Label("distance : infinite");
boolean isSelected = false;
List<Noeudfx> circles = new ArrayList<>();
public Noeudfx(double a, double b, double c, String nom) {
super(a, b, c);
noeud = new Noeud(nom, this);
point = new Point((int) a, (int) b);
circles.add(this);
}
}
Here is my solution:
int nbNoeuds = Integer.parseInt(nodeID.getText());
System.out.println("nnnnn"
+ nbNoeuds);
final Timeline animation = new Timeline(
new KeyFrame(Duration.seconds(.5), (ActionEvent actionEvent) -> {
while (noeuds.getChildren().size() <= nbNoeuds) {
// noeuds.getChildren().remove(0);
int radius =10 ;
noeuds.getChildren().add(
new Circle(
rnd.nextInt(SCENE_SIZE - radius * 2) + radius, rnd.nextInt(SCENE_SIZE - radius * 2) + radius,
radius,
Color.GRAY
)
);
}
})
);
animation.setCycleCount(Animation.INDEFINITE);
animation.play();
animation.setOnFinished((ActionEvent actionevent) -> {
animation.stop();
});
Update: i tried to add label to each circle, the problem was that the number of circles in the screen is not correct i don't know why!
Label id = new Label(String.valueOf(i));
id.setTextFill(Color.CADETBLUE);
id.setAlignment(Pos.CENTER);
Circle circle = new Circle(
rnd.nextInt(SCENE_SIZE - radius * 2) + radius, rnd.nextInt(SCENE_SIZE - radius * 2) + radius,
radius,
Color.GRAY
);
Double a = circle.getCenterX();
Double b = circle.getCenterY();
id.setLayoutX(a - 20);
id.setLayoutY(b - 20);
id.setBlendMode(BlendMode.DIFFERENCE);
noeuds.getChildren().add(id);
noeuds.getChildren().add(circle);
I'm trying to huge graph visualization with threejs r86(latest master version), for showing 600,000 nodes I found a way to draw them faster than using mesh with THREE.points but know I need to make them draggable, after many searches I found raycast to found closest object to mouse point but I have a problem becouse all of taht points are just an object and can not be changed seperately.
function Graph3(Nodes, Edges) {
this.renderer = new THREE.WebGLRenderer({ alpha: true});
var width = window.innerWidth , height = window.innerHeight;
this.renderer.setSize(width, height, false);
document.body.appendChild(this.renderer.domElement);
this.scene = new THREE.Scene(),
this.camera = new THREE.PerspectiveCamera(100, width / height, 0.1, 3000),
this.controls = new THREE.OrbitControls(this.camera);
this.controls.enableKeys = true;
this.controls.enableRotate = false;
var material, geometry;
self = this;
material = new THREE.LineBasicMaterial({color: '#ccc'});
geometry = new THREE.Geometry();
geometry.vertices = Nodes.map(function(item){return new THREE.Vector3(item.pos.x,item.pos.y,item.pos.z);});
// this.vertices = geometry.vertices;
this.line = new THREE.LineSegments(geometry, material);
this.scene.add(this.line);
var Node = new THREE.Group();
material = new THREE.PointsMaterial( { color:0x000060 ,size:1 } );
this.particles = new THREE.Mesh(geometry,material)
this.particles = new THREE.Points( geometry, material);
this.scene.add( this.particles );
dragControls = new THREE.DragControls([this.particles], this.camera/*,this.scene*/, this.renderer.domElement);
this.camera.position.z = 200;
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
document.addEventListener( 'click', function ( event ) {
// calculate mouse position in normalized device coordinates
// (-1 to +1) for both components
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
console.log(mouse);
}, false );
stats = new Stats();
document.body.appendChild(stats.dom);
this.animate = function()
{
raycaster.setFromCamera( mouse, self.camera );
var intersections = raycaster.intersectObject( self.particles );
intersection = ( intersections.length ) > 0 ? intersections[ 0 ] : null;
if ( intersection !== null) {
console.log(intersection);
}
requestAnimationFrame( self.animate );
stats.update();
self.renderer.render(self.scene, self.camera);
}
this.animate();}
I had able to change all the points with dragControls but can't move them seperatly
I had found EventsControls.js file which help us to handle events but I couldn't use it
Here you can check how to target individual parts of a buffer geometry with a raycaster:
https://github.com/mrdoob/three.js/blob/master/examples/webgl_interactive_buffergeometry.html
As for moving them, refer to this question and answer:
How to quickly update a large BufferGeometry?
Thanks for helping me in previous question.
I am making my points in 2d plane (z = 0) and I could making them with bufferGeometry and RawShaderMaterial but now I have another problem in dragging them, how raycaster do? it need vec3 positions but I have changed it for performance purpose.
var Geo = new THREE.BufferGeometry();
var position = new Float32Array( NodeCount * 2 );
var colors = new Float32Array( NodeCount * 3 );
var sizes = new Float32Array( NodeCount );
for ( var i = 0; i < NodeCount; i++ ) {
position[ 2*i ] = (Math.random() - 0.5) * 10;
position[ 2*i + 1 ] = (Math.random() - 0.5) * 10;
colors[ 3*i ] = Math.random();
colors[3*i+1] = Math.random();
colors[3*i+2] = Math.random();
// sizes
sizes[i] = Math.random() * 5 ;
}
Geo.addAttribute( 'position', new THREE.BufferAttribute( position, 2 ) );
Geo.addAttribute( 'color', new THREE.BufferAttribute( colors, 3 ) );
Geo.addAttribute( 'size', new THREE.BufferAttribute( sizes, 1 ) );
points = new THREE.Points( Geo, new THREE.RawShaderMaterial({
vertexShader:`
precision highp float;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
uniform vec3 cameraPosition;
attribute vec2 position; /// reason of problem
varying vec3 vColor;
attribute vec3 color;
attribute float size;
void main() {
vColor = color;
gl_PointSize = size;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position , 0, 1 );
}`,
fragmentShader:`
precision highp float;
varying vec3 vColor;
void main() {
gl_FragColor = vec4( vColor, 1.0 ) ;
}`
}) );
scene.add( points );
and my using of raycaster:
function mouseDown(e) {
e.preventDefault();
var mouse = new THREE.Vector2();
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
// mouse.z = 0;
raycaster.setFromCamera(mouse, camera);
raycaster.far = camera.position.z + 3;
const intersect = raycaster.intersectObject(points);
console.log(intersect);
if (intersect.length > 0) {
controls.enabled = false;
console.log(intersect);
selection = intersect[0].index;
}
}
function mouseUp(e) {
controls.enabled = true;
var vector = new THREE.Vector3();
vector.x = (( event.clientX / window.innerWidth ) * 2 - 1);
vector.y = (- ( event.clientY / window.innerHeight ) * 2 + 1);
vector.z = 1.0;
console.log(camera.position.z);
vector.unproject( camera );
var dir = vector.sub( camera.position ).normalize();
var distance = - camera.position.z / dir.z;
var temp = camera.position.clone().add( dir.multiplyScalar( distance ) );
var pos = points.geometry.attributes.position;
pos.setXY(selection, temp.x, temp.y);
pos.updateRange.offset = selection; // where to start updating
pos.updateRange.count = 1; // how many vertices to update
pos.needsUpdate = true;
selection = undefined;
}
I tried to create a spring like behavior with JavaFX by creating draggable circles. When I drag one circle, the others should follow and simulate elasticity.
I created a boilerplate template which contains 3 circles, they are draggable via mouse. The animation is running, of course all appears still because currently speed is 0. All I need to know is how to calculate the angle and the speed of the attached particles.
It would be great if someone could help me.
Here's the code:
import java.util.ArrayList;
import java.util.List;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.shape.StrokeType;
import javafx.stage.Stage;
public class PhysicsTest extends Application {
List<Particle> particles = new ArrayList<>();
List<Spring> springs = new ArrayList<>();
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
Group root = new Group();
// create particles
Particle pRed = new Particle(Color.RED, 100, 100);
Particle pBlue = new Particle(Color.BLUE, 400, 200);
Particle pGreen = new Particle(Color.GREEN, 100, 300);
// red -> blue
Line lineRedBlue = new Line(100, 100, 500, 500);
lineRedBlue.setStroke(Color.BLACK);
lineRedBlue.setStrokeWidth(5);
// green -> blue
Line lineGreenBlue = new Line(100, 100, 500, 500);
lineGreenBlue.setStroke(Color.BLACK);
lineGreenBlue.setStrokeWidth(5);
// line binding
// line 1 -> 2
lineRedBlue.startXProperty().bind(pRed.centerXProperty());
lineRedBlue.startYProperty().bind(pRed.centerYProperty());
lineRedBlue.endXProperty().bind(pBlue.centerXProperty());
lineRedBlue.endYProperty().bind(pBlue.centerYProperty());
// line 3 -> 2
lineGreenBlue.startXProperty().bind(pGreen.centerXProperty());
lineGreenBlue.startYProperty().bind(pGreen.centerYProperty());
lineGreenBlue.endXProperty().bind(pBlue.centerXProperty());
lineGreenBlue.endYProperty().bind(pBlue.centerYProperty());
MouseGestures mg = new MouseGestures();
mg.makeDraggable(pRed);
mg.makeDraggable(pBlue);
mg.makeDraggable(pGreen);
root.getChildren().addAll(pRed, pBlue, pGreen, lineRedBlue, lineGreenBlue);
// add to list
particles.add( pRed);
particles.add( pBlue);
particles.add( pGreen);
// add springs
Spring s1 = new Spring( pRed, pBlue, 10, 0.5);
springs.add( s1);
Spring s2 = new Spring( pGreen, pBlue, 10, 0.5);
springs.add( s2);
primaryStage.setScene(new Scene(root, 1024, 768));
primaryStage.show();
// animate
startAnimation();
}
private void startAnimation() {
AnimationTimer timer = new AnimationTimer() {
#Override
public void handle(long now) {
// move particles
for (Particle p : particles) {
if (!p.selected) {
p.move();
}
}
// apply springs
for (Spring s : springs) {
s.update();
}
// move particles to new location
for (Particle p : particles) {
p.updateLocation();
}
}
};
timer.start();
}
/**
* The spring constraint and calculation. Updates particle
*/
public class Spring {
Particle p1;
Particle p2;
double length; // length it tries to obtain
double strength; // how quickly it tries to reach that length
public Spring( Particle p1, Particle p2, double length, double strength) {
this.p1 = p1;
this.p2 = p2;
this.length = length;
this.strength = strength;
}
public void update() {
double dx = p1.getCenterX() - p2.getCenterX();
double dy = p1.getCenterY() - p2.getCenterY();
double dist = Math.hypot(dx, dy);
double theta = Math.atan2(dy, dx);
double force = (length - dist) * strength;
// System.out.println( dist + ", " + Math.toDegrees( theta) + ", " + force);
// what's supposed to happen here?
p1.angle = ... // <===
p1.speed = ... // <===
p2.angle = ... // <===
p2.speed = ... // <===
}
}
/**
* The particle itself
*/
public class Particle extends Circle {
double x;
double y;
double angle = 0.0;
double speed = 0.0;
double mass = 1;
boolean selected = false;
public Particle(Color color, double x, double y) {
super(x, y, 50);
this.x = x;
this.y = y;
setFill(color.deriveColor(1, 1, 1, 0.5));
setStroke(color);
setStrokeWidth(2);
setStrokeType(StrokeType.OUTSIDE);
}
public void move() {
x += Math.sin( angle) * speed;
y += Math.cos( angle) * speed;
}
public void updateLocation() {
setCenterX( x);
setCenterY( y);
}
}
/**
* Allow movement of objects via mouse.
*/
public class MouseGestures {
double orgSceneX, orgSceneY;
double orgTranslateX, orgTranslateY;
public void makeDraggable( Node node) {
node.setOnMousePressed(circleOnMousePressedEventHandler);
node.setOnMouseDragged(circleOnMouseDraggedEventHandler);
node.setOnMouseReleased(circleOnMouseReleasedEventHandler);
}
EventHandler<MouseEvent> circleOnMousePressedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
orgSceneX = t.getSceneX();
orgSceneY = t.getSceneY();
Particle p = ((Particle) (t.getSource()));
p.selected = true;
orgTranslateX = p.getCenterX();
orgTranslateY = p.getCenterY();
}
};
EventHandler<MouseEvent> circleOnMouseReleasedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
Particle p = ((Particle) (t.getSource()));
p.selected = false;
};
};
EventHandler<MouseEvent> circleOnMouseDraggedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
double offsetX = t.getSceneX() - orgSceneX;
double offsetY = t.getSceneY() - orgSceneY;
double newTranslateX = orgTranslateX + offsetX;
double newTranslateY = orgTranslateY + offsetY;
Particle p = ((Particle) (t.getSource()));
p.x = newTranslateX;
p.y = newTranslateY;
}
};
}
}
The update() method in the Spring class is the unknown territory:
public void update() {
double dx = p1.getCenterX() - p2.getCenterX();
double dy = p1.getCenterY() - p2.getCenterY();
double dist = Math.hypot(dx, dy);
double theta = Math.atan2(dy, dx);
double force = (length - dist) * strength;
// System.out.println( dist + ", " + Math.toDegrees( theta) + ", " + force);
// what's supposed to happen here?
p1.angle = ... // <===
p1.speed = ... // <===
p2.angle = ... // <===
p2.speed = ... // <===
}
Here's a screenshot of how it looks like currently:
Thank you very much!
Ok, adding a damping and a non compressable spring:
import java.util.ArrayList;
import java.util.List;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.geometry.Point2D;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.shape.StrokeType;
import javafx.stage.Stage;
public class SpringField extends Application {
MouseGestures mg = new MouseGestures();
double damping = 0.995;
double speedo = 0.001;
List<Particle> particles = new ArrayList<>();
List<Spring> springs = new ArrayList<>();
public static void main(String[] args) {
launch(args);
}
Particle addParticle(Group parent, Paint p, double x, double y, double mass) {
Particle particle = new Particle(p, x, y, mass);
mg.makeDraggable(particle);
particles.add(particle);
parent.getChildren().add(particle);
return particle;
}
void addSpring(Group parent, Particle p1, Particle p2, double length, double strength) {
Spring spring = new Spring(parent, p1, p2, length, strength);
springs.add(spring);
}
#Override
public void start(Stage primaryStage) {
Group root = new Group();
// create particles
Particle pRed = addParticle(root, Color.RED, 300, 100, 10);
Particle pBlue = addParticle(root, Color.BLUE, 600, 200, 1);
Particle pGreen = addParticle(root, Color.GREEN, 300, 300, 1);
// add springs
addSpring(root, pRed, pBlue, 100, 0.5);
addSpring(root, pGreen, pBlue, 100, 0.5);
addSpring(root, pGreen, pRed, 100, 0.5);
primaryStage.setScene(new Scene(root, 1024, 768));
primaryStage.show();
// animate
startAnimation();
}
private void startAnimation() {
AnimationTimer timer = new AnimationTimer() {
#Override
public void handle(long now) {
// move particles
for (Particle p : particles) {
if (!p.selected) {
p.move();
}
}
// apply springs
for (Spring s : springs) {
s.update();
}
// move particles to new location
for (Particle p : particles) {
p.updateLocation();
}
}
};
timer.start();
}
/**
* The spring constraint and calculation. Updates particle
*/
public class Spring {
Particle p1;
Particle p2;
double length; // length it tries to obtain
double strength; // how quickly it tries to reach that length
public Spring(Group parent, Particle p1, Particle p2, double length, double strength) {
this.p1 = p1;
this.p2 = p2;
this.length = length;
this.strength = strength;
Line lineRedBlue = new Line(100, 100, 500, 500);
lineRedBlue.setStroke(Color.BLACK);
lineRedBlue.setStrokeWidth(5);
lineRedBlue.startXProperty().bind(p1.centerXProperty());
lineRedBlue.startYProperty().bind(p1.centerYProperty());
lineRedBlue.endXProperty().bind(p2.centerXProperty());
lineRedBlue.endYProperty().bind(p2.centerYProperty());
parent.getChildren().add(lineRedBlue);
}
public void update() {
double stop = 1.0;
double dx = p1.getCenterX() - p2.getCenterX();
double dy = p1.getCenterY() - p2.getCenterY();
double dist = Math.hypot(dx, dy);
double theta = Math.atan2(dy, dx);
double force = (length - dist) * strength;
if (force > 0) { force *= 4; stop = 0.9; }
// System.out.println( dist + ", " + Math.toDegrees( theta) + ", " + force);
Point2D p1v = new Point2D(force*Math.cos(theta)*speedo/p1.mass, force*Math.sin(theta)*speedo/p1.mass);
Point2D p2v = new Point2D(-force*Math.cos(theta)*speedo/p2.mass, -force*Math.sin(theta)*speedo/p2.mass);
p1.vector = p1.vector.add(p1v).multiply(stop);
p2.vector = p2.vector.add(p2v).multiply(stop);
}
}
/**
* The particle itself
*/
public class Particle extends Circle {
double x;
double y;
Point2D vector = new Point2D(0, 0);
double mass = 1;
boolean selected = false;
public Particle(Paint color, double x, double y, double mass) {
super(x, y, 50);
this.x = x;
this.y = y;
this.mass = mass;
setFill(color);
setStroke(color);
setStrokeWidth(2);
setStrokeType(StrokeType.OUTSIDE);
}
public void move() {
x += vector.getX();
y += vector.getY();
vector = vector.multiply(damping);
}
public void updateLocation() {
setCenterX( x);
setCenterY( y);
}
}
/**
* Allow movement of objects via mouse.
*/
public class MouseGestures {
double orgSceneX, orgSceneY;
double orgTranslateX, orgTranslateY;
public void makeDraggable( Node node) {
node.setOnMousePressed(circleOnMousePressedEventHandler);
node.setOnMouseDragged(circleOnMouseDraggedEventHandler);
node.setOnMouseReleased(circleOnMouseReleasedEventHandler);
}
EventHandler<MouseEvent> circleOnMousePressedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
orgSceneX = t.getSceneX();
orgSceneY = t.getSceneY();
Particle p = ((Particle) (t.getSource()));
p.selected = true;
orgTranslateX = p.getCenterX();
orgTranslateY = p.getCenterY();
}
};
EventHandler<MouseEvent> circleOnMouseReleasedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
Particle p = ((Particle) (t.getSource()));
p.selected = false;
};
};
EventHandler<MouseEvent> circleOnMouseDraggedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
double offsetX = t.getSceneX() - orgSceneX;
double offsetY = t.getSceneY() - orgSceneY;
double newTranslateX = orgTranslateX + offsetX;
double newTranslateY = orgTranslateY + offsetY;
Particle p = ((Particle) (t.getSource()));
p.x = newTranslateX;
p.y = newTranslateY;
}
};
}
}
First I would propose to not use angle and speed in your Particle, but Point2D as a movement vector:
public class Particle extends Circle {
double x;
double y;
Point2D vector = new Point2D(0, 0);
that simplifies the later update calulation...
Then you might programm update as follows:
public void update() {
double dx = p1.getCenterX() - p2.getCenterX();
double dy = p1.getCenterY() - p2.getCenterY();
double dist = Math.hypot(dx, dy);
double theta = Math.atan2(dy, dx);
double force = (length - dist) * strength;
// System.out.println( dist + ", " + Math.toDegrees( theta) + ", " + force);
Point2D p1v = new Point2D(force*Math.cos(theta)/p1.mass/10000, force*Math.sin(theta)/p1.mass/10000);
Point2D p2v = new Point2D(-force*Math.cos(theta)/p2.mass/10000, -force*Math.sin(theta)/p2.mass/10000);
p1.vector = p1.vector.add(p1v);
p2.vector = p2.vector.add(p2v);
}
That ignores any collisions, but will provide a fair physics model.
Well here was my approach in a 3D cloth simulation using Verlet Integration:
You can View it here: https://www.youtube.com/watch?v=uRsCcpbsdsg
/**
*
* #author Jason Pollastrini aka jdub1581
*/
#FunctionalInterface
public interface Constraint {
public void solve();
public default void solve(int iter){
IntStream.range(0, iter).parallel().forEach(i->{solve();});
}
}
Then in implementing class (I called it PointLink):
public void solve() {
// calculate the distance between the two PointMasss
Point3D diff = new Point3D(
p1.getPosition().getX() - p2.getPosition().getX(),
p1.getPosition().getY() - p2.getPosition().getY(),
p1.getPosition().getZ() - p2.getPosition().getZ()
);
double d = diff.magnitude();
double difference = (distance - d) / d;
double im1 = 1 / p1.getMass();
double im2 = 1 / p2.getMass();
double scalarP1 = (im1 / (im1 + im2)) * stiffness;
double scalarP2 = stiffness - scalarP1;
p1.position.x = (float) (p1.getPosition().x + diff.x * scalarP1 * difference);
p1.position.y = (float) (p1.getPosition().y + diff.y * scalarP1 * difference);
p1.position.z = (float) (p1.getPosition().z + diff.z * scalarP1 * difference);
p2.position.x = (float) (p2.getPosition().x - diff.x * scalarP2 * difference);
p2.position.y = (float) (p2.getPosition().y - diff.y * scalarP2 * difference);
p2.position.z = (float) (p2.getPosition().z - diff.z * scalarP2 * difference);
}
Then in the Main class something like this:
public void solveConstraints() {
constraints.values().parallelStream().forEach((Constraint c) -> {
c.solve();
});
}
public void updatePhysics(double dt, double t) {
if (isAnchored()) {
setPosition(getAnchorPosition());
return;
}
Point3D vel = new Point3D(
(position.x - oldPosition.x),
(position.y - oldPosition.y),
(position.z - oldPosition.z)
);
float dtSq = (float) (dt * dt);
// calculate the next position using Verlet Integration
Point3D next = new Point3D(
position.x + vel.x + (((force.x / (float) (mass)) * 0.5f) * dtSq),
position.y + vel.y + (((force.y / (float) (mass)) * 0.5f) * dtSq),
position.z + vel.z + (((force.z / (float) (mass)) * 0.5f) * dtSq)
);
// reset variables
setOldPosition(position);
setPosition(next);
setForce(new Point3D(0, 0, 0));
}
Note that I used a simple class with double x,y,z as public members for ease of use for position variable...