From c8a2cfa48336dd98b1c3f3c3cddaef8a8e08d33d Mon Sep 17 00:00:00 2001 From: norangebit Date: Sat, 12 Jan 2019 16:39:42 +0100 Subject: [PATCH] solar system --- .../it/norangeb/solarsystem/MainActivity.kt | 60 +++++++++++- .../it/norangeb/solarsystem/PlanetNode.kt | 54 ++++++++++ .../it/norangeb/solarsystem/RotationNode.kt | 98 +++++++++++++++++++ .../main/java/it/norangeb/solarsystem/Util.kt | 15 +-- 4 files changed, 213 insertions(+), 14 deletions(-) create mode 100644 solar-system/app/src/main/java/it/norangeb/solarsystem/PlanetNode.kt create mode 100644 solar-system/app/src/main/java/it/norangeb/solarsystem/RotationNode.kt diff --git a/solar-system/app/src/main/java/it/norangeb/solarsystem/MainActivity.kt b/solar-system/app/src/main/java/it/norangeb/solarsystem/MainActivity.kt index 8b4c537..68310f6 100644 --- a/solar-system/app/src/main/java/it/norangeb/solarsystem/MainActivity.kt +++ b/solar-system/app/src/main/java/it/norangeb/solarsystem/MainActivity.kt @@ -25,15 +25,19 @@ import android.support.v7.app.AppCompatActivity import android.view.MotionEvent import com.google.ar.core.HitResult import com.google.ar.core.Plane +import com.google.ar.sceneform.Node +import com.google.ar.sceneform.math.Vector3 import com.google.ar.sceneform.rendering.ModelRenderable import com.google.ar.sceneform.ux.ArFragment import kotlinx.coroutines.* class MainActivity : AppCompatActivity() { private val TAG = MainActivity::class.java.canonicalName + private val AU_TO_METERS = 1.1f + private lateinit var arFragment: ArFragment private var isModelAdded = false - private lateinit var planets: Map + private lateinit var renderablePlanets: Map private lateinit var loadPlanetsJob: Job override fun onCreate(savedInstanceState: Bundle?) { @@ -45,17 +49,65 @@ class MainActivity : AppCompatActivity() { setContentView(R.layout.activity_main) arFragment = supportFragmentManager.findFragmentById(R.id.ar_fragment) as ArFragment - loadPlanetsJob = GlobalScope.launch (Dispatchers.Main) { - planets = loadPlanets(this@MainActivity) + loadPlanetsJob = GlobalScope.launch(Dispatchers.Main) { + renderablePlanets = loadPlanets(this@MainActivity) } arFragment.setOnTapArPlaneListener(this::placeSolarSystem) } private fun placeSolarSystem(hitResult: HitResult, plane: Plane, motionEvent: MotionEvent) { + if (isModelAdded) + return + GlobalScope.launch(Dispatchers.Main) { loadPlanetsJob.join() - addTransformableNodeToScene(arFragment, hitResult.createAnchor(), planets[Planet.EARTH]!!) + val solarSystem = createSolarSystem(renderablePlanets) + addNodeToScene(arFragment, hitResult.createAnchor(), solarSystem) + isModelAdded = true } } + + private fun createSolarSystem(renderablePlanets: Map): Node { + val base = Node() + + val sun = Node() + sun.setParent(base) + sun.localPosition = Vector3(0.0f, 0.5f, 0.0f) + + val sunVisual = Node() + sunVisual.setParent(sun) + sunVisual.renderable = renderablePlanets[Planet.SUN] + sunVisual.localScale = Vector3(0.5f, 0.5f, 0.5f) + + createPlanetNode(Planet.MERCURY, sun, 0.4f, 47f, renderablePlanets) + createPlanetNode(Planet.VENUS, sun, 0.7f, 35f, renderablePlanets) + createPlanetNode(Planet.EARTH, sun, 1.0f, 29f, renderablePlanets) + createPlanetNode(Planet.MARS, sun, 1.5f, 24f, renderablePlanets) + createPlanetNode(Planet.JUPITER, sun, 2.2f, 13f, renderablePlanets) + createPlanetNode(Planet.SATURN, sun, 3.5f, 9f, renderablePlanets) + createPlanetNode(Planet.URANUS, sun, 5.2f, 7f, renderablePlanets) + createPlanetNode(Planet.NEPTUNE, sun, 6.1f, 5f, renderablePlanets) + + return base + } + + private fun createPlanetNode( + planet: Planet, + parent: Node, + auFromParent: Float, + orbitDegreesPerSecond: Float, + renderablePlanets: Map + ) { + val orbit = RotationNode() + orbit.degreesPerSecond = orbitDegreesPerSecond + orbit.setParent(parent) + + val renderable = renderablePlanets[planet] ?: return + val planetNode = PlanetNode(renderable) + planetNode.setParent(orbit) + planetNode.localPosition = Vector3(AU_TO_METERS * auFromParent, 0.0f, 0.0f) + } + + } diff --git a/solar-system/app/src/main/java/it/norangeb/solarsystem/PlanetNode.kt b/solar-system/app/src/main/java/it/norangeb/solarsystem/PlanetNode.kt new file mode 100644 index 0000000..2b3017e --- /dev/null +++ b/solar-system/app/src/main/java/it/norangeb/solarsystem/PlanetNode.kt @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2019, norangebit + * + * This file is part of solar-system. + * + * solar-system is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * solar-system is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with solar-system. If not, see + * + */ + +package it.norangeb.solarsystem + +import com.google.ar.sceneform.FrameTime +import com.google.ar.sceneform.Node +import com.google.ar.sceneform.math.Vector3 +import com.google.ar.sceneform.rendering.ModelRenderable + +class PlanetNode( + private val planetRenderable: ModelRenderable +) : Node() { + private val planetScale = 0.6f + private var planetVisual: RotationNode? = null + + override fun onActivate() { + if (scene == null) + throw IllegalStateException("Scene is null!") + + if (planetVisual == null) + initRotationNode() + + } + + override fun onUpdate(frameTime: FrameTime?) { + if (scene == null) + return + } + + fun initRotationNode() { + planetVisual = RotationNode(false) + planetVisual?.setParent(this) + planetVisual?.renderable = planetRenderable + planetVisual?.localScale = Vector3(planetScale, planetScale, planetScale) + } +} \ No newline at end of file diff --git a/solar-system/app/src/main/java/it/norangeb/solarsystem/RotationNode.kt b/solar-system/app/src/main/java/it/norangeb/solarsystem/RotationNode.kt new file mode 100644 index 0000000..947081a --- /dev/null +++ b/solar-system/app/src/main/java/it/norangeb/solarsystem/RotationNode.kt @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2019, norangebit + * + * This file is part of solar-system. + * + * solar-system is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * solar-system is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with solar-system. If not, see + * + */ + +package it.norangeb.solarsystem + +import android.animation.ObjectAnimator +import android.view.animation.LinearInterpolator +import com.google.ar.sceneform.FrameTime +import com.google.ar.sceneform.Node +import com.google.ar.sceneform.math.Quaternion +import com.google.ar.sceneform.math.QuaternionEvaluator +import com.google.ar.sceneform.math.Vector3 + +class RotationNode(private val isOrbit: Boolean = true) : Node() { + val SPEED_MULTIPLIER = 1 + val ROTATION_MULTIPLIER = 1 + + private var orbitAnimation: ObjectAnimator? = null + var degreesPerSecond = 90.0f + + private val animationDuration: Long + get() = (1000 * 360 / (degreesPerSecond * if (isOrbit) + SPEED_MULTIPLIER + else + ROTATION_MULTIPLIER)) + .toLong() + + override fun onUpdate(frameTime: FrameTime?) { + super.onUpdate(frameTime) + + if (orbitAnimation == null) + return + } + + override fun onActivate() { + startAnimation() + } + + override fun onDeactivate() { + stopAnimation() + } + + private fun startAnimation() { + if (orbitAnimation != null) { + return + } + orbitAnimation = createAnimator() + orbitAnimation!!.target = this + orbitAnimation!!.duration = animationDuration + orbitAnimation!!.start() + } + + private fun stopAnimation() { + if (orbitAnimation == null) { + return + } + orbitAnimation!!.cancel() + orbitAnimation = null + } + + private fun createAnimator(): ObjectAnimator { + val orientation1 = Quaternion.axisAngle(Vector3(0.0f, 1.0f, 0.0f), 0f) + val orientation2 = Quaternion.axisAngle(Vector3(0.0f, 1.0f, 0.0f), 120f) + val orientation3 = Quaternion.axisAngle(Vector3(0.0f, 1.0f, 0.0f), 240f) + val orientation4 = Quaternion.axisAngle(Vector3(0.0f, 1.0f, 0.0f), 360f) + + val orbitAnimation = ObjectAnimator() + orbitAnimation.setObjectValues(orientation1, orientation2, orientation3, orientation4) + + orbitAnimation.propertyName = "localRotation" + + orbitAnimation.setEvaluator(QuaternionEvaluator()) + + orbitAnimation.repeatCount = ObjectAnimator.INFINITE + orbitAnimation.repeatMode = ObjectAnimator.RESTART + orbitAnimation.interpolator = LinearInterpolator() + orbitAnimation.setAutoCancel(true) + + return orbitAnimation + } +} diff --git a/solar-system/app/src/main/java/it/norangeb/solarsystem/Util.kt b/solar-system/app/src/main/java/it/norangeb/solarsystem/Util.kt index 2df232d..3d4f40a 100644 --- a/solar-system/app/src/main/java/it/norangeb/solarsystem/Util.kt +++ b/solar-system/app/src/main/java/it/norangeb/solarsystem/Util.kt @@ -34,7 +34,6 @@ import com.google.ar.sceneform.Node import com.google.ar.sceneform.assets.RenderableSource import com.google.ar.sceneform.rendering.* import com.google.ar.sceneform.ux.ArFragment -import com.google.ar.sceneform.ux.TransformableNode import kotlinx.coroutines.Deferred import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope @@ -98,9 +97,9 @@ fun loadPlanet(context: Context, planet: Planet): Deferred { ) return GlobalScope.async(Dispatchers.IO) { - threadLog("start load: $planet") + threadLog("start loading: $planet") val renderable = futureRenderable.get() - threadLog("end load: $planet") + threadLog("end loading: $planet") renderable } } @@ -124,14 +123,10 @@ fun fetchModel( .setRecenterMode(RenderableSource.RecenterMode.ROOT) .build() -fun addTransformableNodeToScene(arFragment: ArFragment, anchor: Anchor, renderable: ModelRenderable): Node { +fun addNodeToScene(arFragment: ArFragment, anchor: Anchor, node: Node) { val anchorNode = AnchorNode(anchor) - val transformableNode = TransformableNode(arFragment.transformationSystem) - transformableNode.renderable = renderable - transformableNode.setParent(anchorNode) + anchorNode.addChild(node) arFragment.arSceneView.scene.addChild(anchorNode) - transformableNode.select() - return transformableNode } enum class Planet(val value: String) { @@ -149,4 +144,4 @@ enum class Planet(val value: String) { fun threadLog(message: String) = Log.d( "COROUTINES", "[${Thread.currentThread().name}]: $message" - ) +)