处理模型
在导入obj文件后,我们得到每个小立方体的编号,当魔方正确放置后(白色面朝上,蓝色面朝前),程序中我们将用一个List存储27个项目。
第一组9个索引是前面的9个方块,从左上(R/W/B 颜色)到右下(Y/O/B 颜色)。
第二组9个索引是中间的立面,从左上(R/W)到右下(Y/O)。
第三组9个索引是后面,从左上(G/R/W)到右下(G/Y/O).。
但是对于操作立方体的旋转,最好的办法是使用整形的3维数组。
private final int[][][] cube={{{50,51,52},{49,54,53},{59,48,46}},
{{58,55,60},{57,62,61},{47,56,63}},
{{67,64,69},{66,71,70},{68,65,72}}};
where 50 is the number of the R/W/B cubie and 72 is the number for the G/Y/O.
这里50号是R/W/B小立方块,72号是G/Y/O小立方块。
(R-RED,W-WHITE,B-BLUE,G-GREEN,Y-YELLOW,O-ORANGE)
旋转类将处理面的旋转。
F-S-B
U-E-D
L-M-R LEFT-MIDDLE-RIGHT
前F后B左L右R上U下D
S-second
E-embed
M—MIDDLE
/* This is the method to perform any rotation on the 3D array just by swapping indexes */
/*这种方法通过交换索引实现3D数组的旋转*/
// first index refers to faces F-S-B
// second index refers to faces U-E-D
// third index refers to faces L-M-R
public void turn(String rot){
int t = 0;
for(int y = 2; y >= 0; --y){
for(int x = 0; x < 3; x ){
switch(rot){
case "L": tempCube[x][t][0] = cube[y][x][0]; break;
case "Li": tempCube[t][x][0] = cube[x][y][0]; break;
case "M": tempCube[x][t][1] = cube[y][x][1]; break;
case "Mi": tempCube[t][x][1] = cube[x][y][1]; break;
case "R": tempCube[t][x][2] = cube[x][y][2]; break;
case "Ri": tempCube[x][t][2] = cube[y][x][2]; break;
case "U": tempCube[t][0][x] = cube[x][0][y]; break;
case "Ui": tempCube[x][0][t] = cube[y][0][x]; break;
case "E": tempCube[x][1][t] = cube[y][1][x]; break;
case "Ei": tempCube[t][1][x] = cube[x][1][y]; break;
case "D": tempCube[x][2][t] = cube[y][2][x]; break;
case "Di": tempCube[t][2][x] = cube[x][2][y]; break;
case "F": tempCube[0][x][t] = cube[0][y][x]; break;
case "Fi": tempCube[0][t][x] = cube[0][x][y]; break;
case "S": tempCube[1][x][t] = cube[1][y][x]; break;
case "Si": tempCube[1][t][x] = cube[1][x][y]; break;
case "B": tempCube[2][t][x] = cube[2][x][y]; break;
case "Bi": tempCube[2][x][t] = cube[2][y][x]; break;
}
}
t ;
}
save();
}
相似的操作可以在整个魔方上实现,X,Y,Z。
模型内容
当我们有了模型,我们需要一个场景显示它。我们使用SubScene对象作为Content容器,引入一个ContentModel类,同时相机、光线、方向轴都被加入,这都来源于3DViewer的应用。
public class ContentModel {
public ContentModel(double paneW, double paneH, double dimModel) {
this.paneW=paneW;
this.paneH=paneH;
this.dimModel=dimModel;
buildCamera();
buildSubScene();
buildAxes();
addLights();
}
private void buildCamera() {
camera.setNearClip(1.0);
camera.setFarClip(10000.0);
camera.setFieldOfView(2d*dimModel/3d);
camera.getTransforms().addAll(yUpRotate,cameraPosition,
cameraLookXRotate,cameraLookZRotate);
cameraXform.getChildren().add(cameraXform2);
cameraXform2.getChildren().add(camera);
cameraPosition.setZ(-2d*dimModel);
root3D.getChildren().add(cameraXform);
/* Rotate camera to show isometric view X right, Y top, Z 120º left-down from each */
/*旋转相机显示等轴侧图,X轴在右,Y轴在上,Z轴在左下120度/
cameraXform.setRx(-30.0);
cameraXform.setRy(30);
}
private void buildSubScene() {
root3D.getChildren().add(autoScalingGroup);
subScene = new SubScene(root3D,paneW,paneH,true,javafx.scene.SceneAntialiasing.BALANCED);
subScene.setCamera(camera);
subScene.setFill(Color.CADETBLUE);
setListeners(true);
}
private void buildAxes() {
double length = 2d*dimModel;
double width = dimModel/100d;
double radius = 2d*dimModel/100d;
final PhongMaterial redMaterial = new PhongMaterial();
redMaterial.setDiffuseColor(Color.DARKRED);
redMaterial.setSpecularColor(Color.RED);
final PhongMaterial greenMaterial = new PhongMaterial();
greenMaterial.setDiffuseColor(Color.DARKGREEN);
greenMaterial.setSpecularColor(Color.GREEN);
final PhongMaterial blueMaterial = new PhongMaterial();
blueMaterial.setDiffuseColor(Color.DARKBLUE);
blueMaterial.setSpecularColor(Color.BLUE);
Sphere xSphere = new Sphere(radius);
Sphere ySphere = new Sphere(radius);
Sphere zSphere = new Sphere(radius);
xSphere.setMaterial(redMaterial);
ySphere.setMaterial(greenMaterial);
zSphere.setMaterial(blueMaterial);
xSphere.setTranslateX(dimModel);
ySphere.setTranslateY(dimModel);
zSphere.setTranslateZ(dimModel);
Box xAxis = new Box(length, width, width);
Box yAxis = new Box(width, length, width);
Box zAxis = new Box(width, width, length);
xAxis.setMaterial(redMaterial);
yAxis.setMaterial(greenMaterial);
zAxis.setMaterial(blueMaterial);
autoScalingGroup.getChildren().addAll(xAxis, yAxis, zAxis);
autoScalingGroup.getChildren().addAll(xSphere, ySphere, zSphere);
}
private void addLights(){
root3D.getChildren().add(ambientLight);
root3D.getChildren().add(light1);
light1.setTranslateX(dimModel*0.6);
light1.setTranslateY(dimModel*0.6);
light1.setTranslateZ(dimModel*0.6);
}
}
对于相机,一个来源于3DViewer的Xform类被用于简单地改变它的旋转数值。这也允许相机的初始旋转显示一个等轴的视口。
cameraXform.setRx(-30.0);
cameraXform.setRy(30);
Other valid ways to perform these rotations could be based on obtaining the vector and angle of rotation to combine two rotations, which involve calculate the rotation matrix first and then the vector and angle (as I explainedhere):
其它有效地执行旋转的方式都基于结合两次旋转间向量、角度的获取,这些都包含了旋转矩阵、向量、角度的计算(我在这解释一下。)
camera.setRotationAxis(new Point3D(-0.694747,0.694747,0.186157));
camera.setRotate(42.1812);
或者用prepend方法把两次旋转和所有之前的变换记录下来,在记录两次旋转之前,把之前的所有项加入到一个单独的Affine矩阵中,最后执行。
Affine affineCamIni=new Affine();
camera.getTransforms().stream().forEach(affineCamIni::append);
affineCamIni.prepend(new Rotate(-30, Rotate.X_AXIS));
affineCamIni.prepend(new Rotate(30, Rotate.Y_AXIS));
我们给subscene添加监听器,那么相机可以很容易地旋转。
private void setListeners(boolean addListeners){
if(addListeners){
subScene.addEventHandler(MouseEvent.ANY, mouseEventHandler);
} else {
subScene.removeEventHandler(MouseEvent.ANY, mouseEventHandler);
}
}
private final EventHandler<MouseEvent> mouseEventHandler = event -> {
double xFlip = -1.0, yFlip=1.0; // y Up
if (event.getEventType() == MouseEvent.MOUSE_PRESSED) {
mousePosX = event.getSceneX();
mousePosY = event.getSceneY();
mouseOldX = event.getSceneX();
mouseOldY = event.getSceneY();
} else if (event.getEventType() == MouseEvent.MOUSE_DRAGGED) {
double modifier = event.isControlDown()?0.1:event.isShiftDown()?3.0:1.0;
mouseOldX = mousePosX;
mouseOldY = mousePosY;
mousePosX = event.getSceneX();
mousePosY = event.getSceneY();
mouseDeltaX = (mousePosX - mouseOldX);
mouseDeltaY = (mousePosY - mouseOldY);
if(event.isMiddleButtonDown() || (event.isPrimaryButtonDown() && event.isSecondaryButtonDown())) {
cameraXform2.setTx(cameraXform2.t.getX() xFlip*mouseDeltaX*modifierFactor*modifier*0.3);
cameraXform2.setTy(cameraXform2.t.getY() yFlip*mouseDeltaY*modifierFactor*modifier*0.3);
}
else if(event.isPrimaryButtonDown()) {
cameraXform.setRy(cameraXform.ry.getAngle() - yFlip*mouseDeltaX*modifierFactor*modifier*2.0);
cameraXform.setRx(cameraXform.rx.getAngle() xFlip*mouseDeltaY*modifierFactor*modifier*2.0);
}
else if(event.isSecondaryButtonDown()) {
double z = cameraPosition.getZ();
double newZ = z - xFlip*(mouseDeltaX mouseDeltaY)*modifierFactor*modifier;
cameraPosition.setZ(newZ);
}
}
};
现在我们可以把所有东西放在一起创建Rubik类了,此时3D模型已导入,所有的meshviews面已经创建了且集合在cube中,然后添加到内容子场景中。同时,ROT已经实例化在在小立方体的初始位置。
instantiate :实例化
public class Rubik {
public Rubik(){
/* Import Rubik's Cube model and arrows */
//导入魔方、箭头模型
Model3D model=new Model3D();
model.importObj();
mapMeshes=model.getMapMeshes();
cube.getChildren().setAll(mapMeshes.values());
dimCube=cube.getBoundsInParent().getWidth();
/* Create content subscene, add cube, set camera and lights */
//创建场景、添加魔方模型、设置相机、光线
content = new ContentModel(800,600,dimCube);
content.setContent(cube);
/* Initialize 3D array of indexes and a copy of original/solved position */
//初始化顺序索引的3维数组,拷贝原始、复位后的位置
rot=new Rotations();
order=rot.getCube();
/* save original position */
//保存原始位置
mapMeshes.forEach((k,v)->mapTransformsOriginal.put(k, v.getTransforms().get(0)));
orderOriginal=order.stream().collect(Collectors.toList());
/* Listener to perform an animated face rotation */
//监听动作面的旋转
rotMap=(ov,angOld,angNew)->{
mapMeshes.forEach((k,v)->{
layer.stream().filter(l->k.contains(l.toString()))
.findFirst().ifPresent(l->{
Affine a=new Affine(v.getTransforms().get(0));
a.prepend(new Rotate(angNew.doubleValue()-angOld.doubleValue(),axis));
v.getTransforms().setAll(a);
});
});
};
}
}
最后对于旋转层的小立方体,我们在时间轴动画上创建一个监听器。当旋转预计发生在当前小立方体的AFFINE矩阵时,将展现一个平顺的动画,改变的角度在0到90度之间,我们监听时间轴如何在内部插值计算,确保在angNew和angOld之间的旋转。
实现旋转的方法如下:
public void rotateFace(final String btRot){
if(onRotation.get()){
return;
}
onRotation.set(true);
// rotate cube indexes 旋转小立方体的索引编号
rot.turn(btRot);
// get new indexes in terms of blocks numbers from original order
//依据原始顺序中块编号,获取新的索引序号
reorder=rot.getCube();
// select cubies to rotate: those in reorder different from order.
//选择小立方体旋转:所有新顺序与原顺序不同的小立方体
AtomicInteger index = new AtomicInteger();
layer=order.stream()
.filter(o->!Objects.equals(o, reorder.get(index.getAndIncrement())))
.collect(Collectors.toList());
// add central cubie
//加入中心小立方体
layer.add(0,reorder.get(Utils.getCenter(btRot)));
// set rotation axis
//设置旋转轴
axis=Utils.getAxis(btRot);
// define rotation
//定义旋转
double angEnd=90d*(btRot.endsWith("i")?1d:-1d);
rotation.set(0d);
// add listener to rotation changes
//为旋转变化添加监听
rotation.addListener(rotMap);
// create animation
//创建动画
Timeline timeline=new Timeline();
timeline.getKeyFrames().add(
new KeyFrame(Duration.millis(600), e->{
// remove listener
//移除监听器
rotation.removeListener(rotMap);
onRotation.set(false);
}, new KeyValue(rotation,angEnd)));
timeline.playFromStart();
// update order with last list
//更新顺序表
order=reorder.stream().collect(Collectors.toList());
}
Copyright © 2024 妖气游戏网 www.17u1u.com All Rights Reserved