与HelloSceneform的区别
- HelloSceneform使用的包是ux:sceneform-ux:1.6.0,并且在布局上使用了ArFragment,自动完成了一些相关配置(初步估计有Camera权限申请)。
1.SolarActivity
首先,同样是检测设备支持,之后加载布局、获取视图实例:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (!DemoUtils.checkIsSupportedDeviceOrFinish(this)) {
// Not a supported device.
return;
}
setContentView(R.layout.activity_solar);
arSceneView = findViewById(R.id.ar_scene_view);
}
与HellowSceneform不同的是,SolarSystem使用的视图是ArSceneView,而不是ArFragment。使用ArFragment会自动配置ArSceneView和ArSession等。
接下来,创建渲染对象:
// Build all the planet models.
CompletableFuture<ModelRenderable> sunStage =
ModelRenderable.builder().setSource(this, Uri.parse("Sol.sfb")).build();
CompletableFuture<ModelRenderable> mercuryStage =
ModelRenderable.builder().setSource(this, Uri.parse("Mercury.sfb")).build();
...
// Build a renderable from a 2D View.
CompletableFuture<ViewRenderable> solarControlsStage =
ViewRenderable.builder().setView(this, R.layout.solar_controls).build();
CompletableFuture.allOf(
sunStage,
mercuryStage,
...
solarControlsStage)
.handle(
(notUsed, throwable) -> {
// When you build a Renderable, Sceneform loads its resources in the background while
// returning a CompletableFuture. Call handle(), thenAccept(), or check isDone()
// before calling get().
if (throwable != null) {
DemoUtils.displayError(this, "Unable to load renderable", throwable);
return null;
}
try {
sunRenderable = sunStage.get();
mercuryRenderable = mercuryStage.get();
...
solarControlsRenderable = solarControlsStage.get();
// Everything finished loading successfully.
hasFinishedLoading = true;
} catch (InterruptedException | ExecutionException ex) {
DemoUtils.displayError(this, "Unable to load renderable", ex);
}
return null;
});
创建可渲染对象都是利用CompletableFuture<?>。其中在SolarSystem中分别创建了3D和2D的模型,差别在于分别是CompleteableFuture<ModelRenderable>和CompleteableFuture<ViewRenderable>。同时,构建方法也不同,ModelRenderable.builder().setSource()和ViewRenderable.builder().setView()。
然后,添加监听器,响应手指在屏幕上的各种动作:
gestureDetector =
new GestureDetector(
this,
new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onSingleTapUp(MotionEvent e) {
onSingleTap(e);
return true;
}
@Override
public boolean onDown(MotionEvent e) {
return true;
}
});
private void onSingleTap(MotionEvent tap) {
if (!hasFinishedLoading) {
// We can't do anything yet.
return;
}
Frame frame = arSceneView.getArFrame();
if (frame != null) {
if (!hasPlacedSolarSystem && tryPlaceSolarSystem(tap, frame)) {
hasPlacedSolarSystem = true;
}
}
}
其中具体的逻辑判断比较复杂,涉及的东西较多,主要就是以下3个方法:
private boolean tryPlaceSolarSystem(MotionEvent tap, Frame frame) {
if (tap != null && frame.getCamera().getTrackingState() == TrackingState.TRACKING) {
for (HitResult hit : frame.hitTest(tap)) {
Trackable trackable = hit.getTrackable();
if (trackable instanceof Plane && ((Plane) trackable).isPoseInPolygon(hit.getHitPose())) {
// Create the Anchor.
Anchor anchor = hit.createAnchor();
AnchorNode anchorNode = new AnchorNode(anchor);
anchorNode.setParent(arSceneView.getScene());
Node solarSystem = createSolarSystem();
anchorNode.addChild(solarSystem);
return true;
}
}
}
return false;
}
private Node createSolarSystem() {
//创建一个基础节点
Node base = new Node();
//创建太阳节点
Node sun = new Node();
sun.setParent(base);
sun.setLocalPosition(new Vector3(0.0f, 0.5f, 0.0f));
Node sunVisual = new Node();
sunVisual.setParent(sun);
//创建sunVisual的模型对象
sunVisual.setRenderable(sunRenderable);
sunVisual.setLocalScale(new Vector3(0.5f, 0.5f, 0.5f));
Node solarControls = new Node();
solarControls.setParent(sun);
//创建控制器的模型对象
solarControls.setRenderable(solarControlsRenderable);
solarControls.setLocalPosition(new Vector3(0.0f, 0.25f, 0.0f));
//获取ViewRenderable的View对象
View solarControlsView = solarControlsRenderable.getView();
SeekBar orbitSpeedBar = solarControlsView.findViewById(R.id.orbitSpeedBar);
orbitSpeedBar.setProgress((int) (solarSettings.getOrbitSpeedMultiplier() * 10.0f));
orbitSpeedBar.setOnSeekBarChangeListener(
new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
float ratio = (float) progress / (float) orbitSpeedBar.getMax();
solarSettings.setOrbitSpeedMultiplier(ratio * 10.0f);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {}
});
SeekBar rotationSpeedBar = solarControlsView.findViewById(R.id.rotationSpeedBar);
rotationSpeedBar.setProgress((int) (solarSettings.getRotationSpeedMultiplier() * 10.0f));
rotationSpeedBar.setOnSeekBarChangeListener(
new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
float ratio = (float) progress / (float) rotationSpeedBar.getMax();
solarSettings.setRotationSpeedMultiplier(ratio * 10.0f);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {}
});
// Toggle the solar controls on and off by tapping the sun.
sunVisual.setOnTapListener(
(hitTestResult, motionEvent) -> solarControls.setEnabled(!solarControls.isEnabled()));
//创建模型对象
createPlanet("Mercury", sun, 0.4f, 47f, mercuryRenderable, 0.019f);
createPlanet("Venus", sun, 0.7f, 35f, venusRenderable, 0.0475f);
Node earth = createPlanet("Earth", sun, 1.0f, 29f, earthRenderable, 0.05f);
createPlanet("Moon", earth, 0.15f, 100f, lunaRenderable, 0.018f);
createPlanet("Mars", sun, 1.5f, 24f, marsRenderable, 0.0265f);
createPlanet("Jupiter", sun, 2.2f, 13f, jupiterRenderable, 0.16f);
createPlanet("Saturn", sun, 3.5f, 9f, saturnRenderable, 0.1325f);
createPlanet("Uranus", sun, 5.2f, 7f, uranusRenderable, 0.1f);
createPlanet("Neptune", sun, 6.1f, 5f, neptuneRenderable, 0.074f);
return base;
}
private Node createPlanet(String name, Node parent, float auFromParent,
float orbitDegreesPerSecond, ModelRenderable renderable, float planetScale) {
// Orbit is a rotating node with no renderable positioned at the sun.
// The planet is positioned relative to the orbit so that it appears to rotate around the sun.
// This is done instead of making the sun rotate so each planet can orbit at its own speed.
RotatingNode orbit = new RotatingNode(solarSettings, true);
orbit.setDegreesPerSecond(orbitDegreesPerSecond);
orbit.setParent(parent);
// Create the planet and position it relative to the sun.
Planet planet = new Planet(this, name, planetScale, renderable, solarSettings);
planet.setParent(orbit);
planet.setLocalPosition(new Vector3(auFromParent * AU_TO_METERS, 0.0f, 0.0f));
return planet;
}
其中createSolarSystem()和createPlanet()方法相对复杂,需要进一步拆解。而tryPlaceSolarSystem()方法主要的功能就是将建立好的SolarSystem放入场景之中,不难看懂。
对于自转和公转都是靠ObjectAnimator实现,具体实现不详细展开。
个人理解:Node都存在parent,可以把parent理解为一个空间坐标系。在这个程序例子中,orbit为planet的parent,orbit的绕Y轴转形成了公转,而planet的绕Y轴转形成了自转。