After some nights of coding here my (now working) intermediate solution of the requirements described above. The problem is that this app state only provides ultra accurate positioning and speeding for rootNode children and doesn’t allow recursion (for example the star–>planet–>moon problem described above).
package spacecraft.scene;
import com.jme3.app.Application;
import com.jme3.app.SimpleApplication;
import com.jme3.app.state.AbstractAppState;
import com.jme3.app.state.AppStateManager;
import com.jme3.asset.AssetManager;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import java.math.BigDecimal;
import java.util.Objects;
import java.util.Optional;
import static java.util.Optional.of;
import static java.util.Optional.ofNullable;
import java.util.function.BiFunction;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import spacecraft.character.PlayerState;
import spacecraft.objects.SceneControl;
import static spacecraft.scene.StateVelocityUtilities.doForwardEuler;
/**
* This class fullfills two tasks associated with the placement of the scene.
* <p>
*
* Requirements:
* <p>
* This appstate provides functionality for displaying every possible position in space and
* every velocities which range from millimeters per second to multiples the
* speed of light
* (Yes I know Einstein but this is for SoftSciFi warp speed or time compression).
* <p>
* Req 1. the center of the scene coordinates is always the players vehicle.
* player.getWorldLocation() --> new Vector3f(0,0,0). The origin of space
* coordinate system center has BigDecimal[]{0,0,0} position and double[]{0,0,0}
* velocity. space coordinates and velocities are stored inside players vehicle
* spatial.
*
* Req 2. All vehicles have their own absolute space coordinates x3 and velocity
* v3.
*
* Req 3. At any time space coordinates and scene coordinates and velocities
* shall be transformable into each other. ==> Solved: a static BigDecimal[]
* vector storing the absolute position of the spatial
*
* (Req 4.) Definitions of hierarchical structures of celestial bodies in space:
* e.g.. solar system: SolarSystem[x3,v3] --> Stars[x3rel,v3rel] -->
* planets{x3rel2,v3rel2] --> moons[x3rel3,v3rel3]--> space stations. Arbitrary
* transformations (especially rotational position) has to be considered.
* Rotating solar system node affects star and planets and moons.
*
* Req 5. Native JME3 function like rootNode.attachChild(spatial) shall work and
* provide default functionality and scene placement. x3 and v3 are then set
* with respect to the players vessel.
*
* Req 6. Usually spatial controls do not have direct access to the assetManager
* or rootNode they do not/shall not have the capability to generate/remove
* scene content directly. This apostate shall collect command objects (generated by
* SceneControl objects) for adding/removing content (GoF command pattern).
*
*
* @author harryschwenk
*/
public final class BigSceneState extends AbstractAppState {
private AssetManager assetManagerOpt = null;
private Camera cameraOpt = null;
private Node rootNodeOpt = null;
// private float tpf;
private AppStateManager stateManager = null;
/**
* asset manager getter
*
* @return asset manager
*/
public AssetManager getAssetManager() {
return Objects.requireNonNull(assetManagerOpt);
}
/**
* camera getter
*
* @return asset manager
*/
public Camera getCameraOpt() {
return Objects.requireNonNull(cameraOpt);
}
/**
* getter for all children of the root node (TopLevel children)
*
* @return asset manager
*/
public Stream<Spatial> getChildren() {
return getRootNode().getChildren().stream();
}
/**
* root node getter function
*
* @return rootNode
*/
public Node getRootNode() {
return Objects.requireNonNull(rootNodeOpt);
}
/**
* initialization of app state. rootNode, assetManager and camera references
* are stored for later use.
*
* @param stateManager der StateManager
* @param app das Applikaitonsobjekt.
* @throws IllegalStateException if initialize is called twice.
*/
@Override
public void initialize(AppStateManager stateManager, Application app) {
this.stateManager = Objects.requireNonNull(stateManager);
// Nur ausführen, wenn nicht initialsisiert!
if (isInitialized()) {
throw new IllegalStateException("Already initialized");
}
/*
* Auslesen der RootNode
*/
final Node rootNodeTmp = ((SimpleApplication) app).getRootNode();
setRootNode(Objects.requireNonNull(rootNodeTmp));
/*
* Auslesen des AssetManagers
*/
final AssetManager assetManagerTmp = app.getAssetManager();
setAssetManager(Objects.requireNonNull(assetManagerTmp));
/*
* Auslesen der Camera
*/
final Camera cameraTmp = app.getCamera();
setCamera(Objects.requireNonNull(cameraTmp));
// Hier wird das initialized flag auf true gesetzt.
super.initialize(stateManager, app); //To change body of generated methods, choose Tools | Templates.
}
/**
*
* @return
*/
@Override
public String toString() {
return "SceneState{" + "assetManager=" + assetManagerOpt
//+ ", bulletAppState=" + bulletAppState
+ ", camera=" + cameraOpt + ", rootNode=" + rootNodeOpt + '}';
}
/**
* Update cycle.
*
* <p>
* 1. (Req 2) updateEachTopLevelChild performs additional updates for every
* direct root node child. An Euler-Integration step is performed on every
* single top-Level-child for updating ultra accurte position and velocity
* vector.
* <p>
* 2. (Req 6) every child is checked for scene modification commands
* objects.
* <p>
* 3. Req 1. the center of the scene coordinates is always the players
* vehicle. player.getWorldLocation() --> Vector3f(0,0,0). The origin of
* space coordinate system center has BigDecimal[]{0,0,0} position and
* double[]{0,0,0} velocity. space coordinates and velocities are stored
* inside players vehicle spatial.
*
* @param tpf
*/
@Override
public void update(float tpf) {
super.update(tpf); //To change body of generated methods, choose Tools | Templates.
getRootNode().getChildren().stream().forEach(topLevelChild -> doForwardEuler(topLevelChild, tpf));
updateReferenceLocation();
getRootNode().getChildren().stream().forEach(this::updateEachTopLevelChild);
getRootNode().breadthFirstTraversal(this::loadOrRemoveSceneObjects);
}
/**
* @return the cameraState
*/
public BigDecimal[] getReferenceLocation() {
return StateVelocityUtilities.centerPositionOfLocalScene.clone();
}
/**
* @param referenceStateArray the cameraState to set
*/
public void setReferenceLocation(BigDecimal[] referenceStateArray) {
StateVelocityUtilities.centerPositionOfLocalScene = referenceStateArray.clone();
}
/**
*
* @param xx
* @param xy
* @param xz
* @param vx
* @param vy
* @param vz
*/
public void setCameraStateVector(BigDecimal xx, BigDecimal xy, BigDecimal xz, BigDecimal vx, BigDecimal vy, BigDecimal vz) {
StateVelocityUtilities.centerPositionOfLocalScene = new BigDecimal[]{xx, xy, xz, vx, vy, vz};
}
/**
* (Req 1) the reference location is updated here. This is the ultra
* accurate BigDecimal[] vector which defines the space location of the
* rootNode origin.
*/
private void updateReferenceLocation() {
ofNullable(getStateManager().getState(PlayerState.class))
.flatMap(PlayerState::getPlayerVehicle)
.ifPresent(sp -> this.setReferenceLocation(StateVelocityUtilities.getAbsoluteLocation(sp)));
}
/**
* (Req 6) every child is checked for scene modification commands objects.
*
* @param sc
*/
private void loadOrRemoveSceneObjects(Spatial oneOfAllSceneElements) {
SceneControl sc = oneOfAllSceneElements.getControl(SceneControl.class);
if (sc == null) {
return;
}
// this is necessary at least one time per scene control beacuse
// sceneState reference is necessary for command object creation.
// Probably redundant but there is virtually no runtime overhead here.
sc.setSceneState(of(this));
/*
* retrieving commands for execution. The commands are cleared
* automatically by the control.
*/
sc.pollCommands().stream().forEach((
BiFunction<AssetManager, Node, Spatial> cmd) -> {
Spatial newSpatial = cmd.apply(getAssetManager(),
getRootNode());
// Only to be sure that every thing is running fine the first cycle.
newSpatial.breadthFirstTraversal((Spatial spNew)
-> ofNullable(spNew.getControl(SceneControl.class)).ifPresent(
(SceneControl spNewSc) -> spNewSc.setSceneState(
of(this))));
});
/*
* Perform destruction of spatial if condition is true.
*/
Spatial scSp = sc.getSpatial();
if (sc.isDestroyed() && scSp != null) {
Node scSpParent = scSp.getParent();
if (scSpParent != null) {
scSpParent.detachChild(scSp);
}
// Unlink scene state: the object is out of scene and cannot do anyting.
sc.setSceneState(Optional.empty());
}
}
/**
*
* @param assetManager
*/
private void setAssetManager(AssetManager assetManager) {
// Fail fast
this.assetManagerOpt = Objects.requireNonNull(assetManager);
}
/**
*
* @param camera
*/
private void setCamera(Camera camera) {
// Fail fast
this.cameraOpt = Objects.requireNonNull(camera);
}
/**
*
* @param rootNode
*/
private void setRootNode(Node rootNode) {
// Fail fast
this.rootNodeOpt = Objects.requireNonNull(rootNode);
}
/**
*
* Req 1. the center of the scene coordinates is always the players vehicle.
* player.getWorldLocation() --> new Vector3f(0,0,0). The origin of space
* coordinate system center has BigDecimal[]{0,0,0} position and
* double[]{0,0,0} velocity. space coordinates and velocities are stored
* inside players vehicle spatial.
*
* @param topLevelChild child of rootNode which is placed by ujltra accurate
* coordinates.
*/
private void updateEachTopLevelChild(Spatial topLevelChild) {
// Transformation to scene coordinates is necessary here because the reference
// vector has been updated here requiring a complete retransformatino of the scene.
BigDecimal[] absoluteLocation = StateVelocityUtilities.getAbsoluteLocation(topLevelChild);
BigDecimal[] sceneLocation = IntStream.range(0, 3).mapToObj(i -> absoluteLocation[i].subtract(this.getReferenceLocation()[i])).toArray(i -> new BigDecimal[i]);
Vector3f vector = new Vector3f(sceneLocation[0].floatValue(), sceneLocation[1].floatValue(), sceneLocation[2].floatValue());
topLevelChild.setLocalTranslation(vector);
if (topLevelChild instanceof Node) {
// We remove position data from sub-children in order to clear
// non updated / false information. This covers the case if a topLevel child
// is attached to another spatial and then reattached to the rootNode.
((Node) topLevelChild)
.getChildren()
.stream()
.forEach(sp -> sp.breadthFirstTraversal(subchild -> StateVelocityUtilities.clearLocationData(subchild)));
}
}
/**
*
* @return
*/
private AppStateManager getStateManager() {
// Fail fast
return Objects.requireNonNull(stateManager);
}
}
Critics and feedback are always welcome.
Thank you very much.
Regards,
Harry
P.S.:
this is a very interesting point. After the coding night and reading twice this statement is now far clearer to me 