simulated-genetics/docs/codox/Understanding-jme3-character-models.html

116 lines
15 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html PUBLIC ""
"">
<html><head><meta charset="UTF-8" /><title>Understanding how jME3 handles character models</title><link rel="stylesheet" type="text/css" href="css/default.css" /><link rel="stylesheet" type="text/css" href="css/highlight.css" /><script type="text/javascript" src="js/highlight.min.js"></script><script type="text/javascript" src="js/jquery.min.js"></script><script type="text/javascript" src="js/page_effects.js"></script><script>hljs.initHighlightingOnLoad();</script></head><body><div id="header"><h2>Generated by <a href="https://github.com/weavejester/codox">Codox</a></h2><h1><a href="index.html"><span class="project-title"><span class="project-name">Simulated-genetics</span> <span class="project-version">0.1.0-SNAPSHOT</span></span></a></h1></div><div class="sidebar primary"><h3 class="no-link"><span class="inner">Project</span></h3><ul class="index-link"><li class="depth-1 "><a href="index.html"><div class="inner">Index</div></a></li></ul><h3 class="no-link"><span class="inner">Topics</span></h3><ul><li class="depth-1 current"><a href="Understanding-jme3-character-models.html"><div class="inner"><span>Understanding how jME3 handles character models</span></div></a></li><li class="depth-1 "><a href="intro.html"><div class="inner"><span>Introduction to simulated-genetics</span></div></a></li></ul><h3 class="no-link"><span class="inner">Namespaces</span></h3><ul><li class="depth-1"><div class="no-link"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>cc</span></div></div></li><li class="depth-2"><div class="no-link"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>journeyman</span></div></div></li><li class="depth-3"><div class="no-link"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>simulated-genetics</span></div></div></li><li class="depth-4 branch"><a href="cc.journeyman.simulated-genetics.genome.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>genome</span></div></a></li><li class="depth-4 branch"><a href="cc.journeyman.simulated-genetics.launcher.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>launcher</span></div></a></li><li class="depth-4 branch"><a href="cc.journeyman.simulated-genetics.makehuman-bridge.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>makehuman-bridge</span></div></a></li><li class="depth-4"><a href="cc.journeyman.simulated-genetics.utils.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>utils</span></div></a></li></ul></div><div class="document" id="content"><div class="doc"><div class="markdown"><h1><a href="#understanding-how-jme3-handles-character-models" id="understanding-how-jme3-handles-character-models"></a>Understanding how jME3 handles character models</h1>
<p>(This note is largely based on analysis of Stephen Golds <a href="https://github.com/stephengold/Maud">Maud</a>).</p>
<h2><a href="#dem-bones" id="dem-bones"></a>Dem Bones</h2>
<p>One potential way to vary character models is to adjust the length of their bones. In order to be able to do this, I need to get hold of them. This note documents how to get hold of them in the <a href="https://jmonkeyengine.org/">jMonkeyEngine</a> 3 API (referenced as jME3).</p>
<p>Model files (including, but not limited to, <code>.j3o</code> files) are <a href="https://javadoc.jmonkeyengine.org/v3.6.1-stable/com/jme3/asset/AssetManager.html#loadModel(com.jme3.asset.ModelKey)">loaded by the AssetManager</a> as instances of <a href="https://javadoc.jmonkeyengine.org/v3.6.1-stable/com/jme3/scene/Spatial.html">Spatial</a>. A spatial can be any sort of scene node a building, a tree, a rock, a container. It is not inherently a character and does not inherently have a skeleton or a rig; furthermore, the abstract class Spatial has no methods to extract a skeleton or a rig or even, actually, a geometry, material or texture.</p>
<p>Maud loads models as instances of <code>maud.model.cgm.LoadedCgm</code>, which wraps the Spatial.</p>
<p><a href="https://github.com/stephengold/Maud/blob/master/src/main/java/maud/model/cgm/Cgm.java#L205">Instance variable declaration</a>:</p>
<pre><code class="language-java"> /**
* root spatial in the MVC model's copy of the C-G model
*/
protected Spatial rootSpatial = null;
</code></pre>
<p><a href="https://github.com/stephengold/Maud/blob/master/src/main/java/maud/model/cgm/LoadedCgm.java#L292">Instantiation</a>:</p>
<pre><code class="language-java"> this.rootSpatial = Heart.deepCopy(cgmRoot);
</code></pre>
<p><code>LoadedCgm</code> is a subclass of <a href="https://github.com/stephengold/Maud/blob/master/src/main/java/maud/model/cgm/Cgm.java"><code>Cgm</code></a>, where CGM is stated in the documentation to be an acronym for Computer Graphics Model.</p>
<p>The Cgm class has an instance variable <code>selectedSkeleton</code> which is instantiated at the time the Cgm instance is constructed to a new, empty, instance of <a href="https://github.com/stephengold/Maud/blob/master/src/main/java/maud/model/cgm/SelectedSkeleton.java">SelectedSkeleton</a>. So how does the SelectedSkeleton instance (which is instantiated before the <code>rootSpatial</code> is set) get to know about the skeleton from the Spatial, which has, inherently, no skeleton? The answer is that is calls the <a href="https://github.com/stephengold/Maud/blob/master/src/main/java/maud/model/cgm/SelectedSkeleton.java#L148"><code>countBones()</code></a> method of the selectedSkeleton:</p>
<pre><code class="language-java"> public int countBones() {
int result = 0;
Object selected = find();
if (selected instanceof Armature) {
result = ((Armature) selected).getJointCount();
} else if (selected instanceof Skeleton) {
result = ((Skeleton) selected).getBoneCount();
}
assert result &gt;= 0 : result;
return result;
}
</code></pre>
<p>Part of the complexity here is backwards compatibility. The class <a href="https://javadoc.jmonkeyengine.org/v3.6.1-stable/com/jme3/anim/Armature.html"><code>Armature</code></a> is a newer replacement for the older (and now deprecated) class <a href="https://javadoc.jmonkeyengine.org/v3.6.1-stable/com/jme3/animation/Skeleton.html"><code>Skeleton</code></a>; this appears to be part of a major re-engineering of how jME3 handles animation.</p>
<p>and <code>countBones()</code> calls <a href="https://github.com/stephengold/Maud/blob/master/src/main/java/maud/model/cgm/SelectedSkeleton.java#L253"><code>find()</code></a>:</p>
<pre><code class="language-java"> /**
* Find the selected Armature or Skeleton.
*
* @return the pre-existing instance, or null if none
*/
Object find() {
Object result = find(null);
return result;
}
</code></pre>
<p>which in turn calls <a href="https://github.com/stephengold/Maud/blob/master/src/main/java/maud/model/cgm/SelectedSkeleton.java#L181"><code>find(binary[])</code></a>:</p>
<pre><code class="language-java"> /**
* Find the selected Armature or Skeleton.
*
* @param storeSelectedSgcFlag if not null, set the first element to true if
* the skeleton came from the selected S-G control or its controlled
* spatial, false if it came from the C-G model root
* @return a pre-existing Armature or Skeleton, or null if none selected
*/
Object find(boolean[] storeSelectedSgcFlag) {
boolean selectedSgcFlag;
Object skeleton = null;
/*
* If the selected S-G control is an AnimControl, SkeletonControl,
* or SkinningControl, use its skeleton, if it has one.
*/
Control selectedSgc = cgm.getSgc().get();
if (selectedSgc instanceof AnimControl) {
skeleton = ((AnimControl) selectedSgc).getSkeleton();
}
if (skeleton == null &amp;&amp; selectedSgc instanceof SkeletonControl) {
skeleton = ((SkeletonControl) selectedSgc).getSkeleton();
}
if (skeleton == null &amp;&amp; selectedSgc instanceof SkinningControl) {
skeleton = ((SkinningControl) selectedSgc).getArmature();
}
...
</code></pre>
<p>And so on.</p>
<p>Im going to confess here that coming back to object oriented programming after a decade of concentrating on functional programming, its frustrating how complicated, messy and repetitious it is. But in this instance I cant help feeling that it would have been less messy if the abstract class <code>CGM</code> had a method <code>getSkeleton()</code> which by default returned null; and which was overridden in subclasses of <code>CGM</code> which represented things which did have skeletons to return their skeletons.</p>
<p>But this only kicks the how to get the skeleton one step further down the road, to <a href="https://javadoc.jmonkeyengine.org/v3.6.1-stable/com/jme3/scene/control/Control.html"><code>Control</code></a>. <code>Control</code> also wraps a <code>Spatial</code>, which also isnt instantiated at construction time, and also doesnt have a <code>getSkeleton()</code> method.</p>
<p>To be fair I dont know what proportion of subclasses of <code>Control</code> have skeletons, but on the evidence here at least four do; and an overridable instance method on <a href="https://javadoc.jmonkeyengine.org/v3.6.1-stable/com/jme3/scene/control/AbstractControl.html"><code>AbstractControl</code></a> returning <code>null</code>, declared on the <a href="https://javadoc.jmonkeyengine.org/v3.6.1-stable/com/jme3/scene/control/Control.html"><code>Control</code></a> interface, would have little cost and save a lot of mess.</p>
<p>As there are now a lot of branches to cover, Im going to concentrate on the <code>SkinningControl</code> one, which <em>seems</em> to be the current state of the art. I havent at this stage investigated how <code>AssetManager.loadModel(String)</code> determines which classes to instantiate when loading a model, but Im going to assume that I can coerce my models to be loaded in a non-deprecated form.</p>
<p>A <a href="https://javadoc.jmonkeyengine.org/v3.6.1-stable/com/jme3/anim/SkinningControl.html"><code>SkinningControl</code></a> has a private instance variable <code>armature</code>:</p>
<pre><code class="language-java"> /**
* The armature of the model.
*/
private Armature armature;
</code></pre>
<p>which is instantiated in the constructor:</p>
<pre><code class="language-java"> /**
* Creates an armature control. The list of targets will be acquired
* automatically when the control is attached to a node.
*
* @param armature the armature
*/
public SkinningControl(Armature armature) {
if (armature == null) {
throw new IllegalArgumentException("armature cannot be null");
}
this.armature = armature;
this.numberOfJointsParam = new MatParamOverride(VarType.Int, "NumberOfBones", null);
this.jointMatricesParam = new MatParamOverride(VarType.Matrix4Array, "BoneMatrices", null);
}
</code></pre>
<p>the <code>Armature</code> class has an instance method <a href="https://javadoc.jmonkeyengine.org/v3.6.1-stable/com/jme3/anim/Armature.html#getJointCount()"><code>getJointCount()</code></a>; it also has instance methods <a href="https://javadoc.jmonkeyengine.org/v3.6.1-stable/com/jme3/anim/Armature.html#getJointList()"><code>getJointList()</code></a>, <a href="https://javadoc.jmonkeyengine.org/v3.6.1-stable/com/jme3/anim/Armature.html#getJoint(int)"><code>getJoint(int)</code></a>, and <a href="https://javadoc.jmonkeyengine.org/v3.6.1-stable/com/jme3/anim/Armature.html#getJoint(java.lang.String)"><code>getJoint(String)</code></a>.</p>
<p><strong>Note that</strong> <code>Joint</code> <em>seems</em> to be a name used in the rewrite of the animation system to avoid confusion with the <code>Bone</code> in the earlier animation system, and <em>I think</em> represents what normal animation rig nomenclature would refer to as a bone.</p>
<p>However, the <code>Joint</code> class has no <code>length</code> instance variable. What it has is a <code>targetGeometry</code> instance variable which is declared as a <a href="https://javadoc.jmonkeyengine.org/v3.6.1-stable/com/jme3/scene/Geometry.html"><code>Geometry</code></a>. Geometry, in turn, has no dimensions, but has an instance variable <code>mesh</code> declared as a <a href="https://javadoc.jmonkeyengine.org/v3.6.1-stable/com/jme3/scene/Mesh.html"><code>Mesh</code></a>. I suspect that it is the Mesh object which contains lines, triangles and vertices which provides the actual dimensioned objects.</p>
<p>I dont think, however, that I either need to or should change anything in the Geometry objects themselves. Instead, the Joint object also has three instance variables bound to instances of <a href="https://javadoc.jmonkeyengine.org/v3.6.1-stable/com/jme3/math/Transform.html"><code>Transform</code></a>:</p>
<ul>
<li><code>localTransform</code>;</li>
<li><code>initialTransform</code>;</li>
<li><code>jointModelTransform</code>;</li>
</ul>
<p>Each of these is private, and has a getter but no setter. It is my hypothesis that to alter the length of the bone, one should make its <code>localTransform</code> IV a scaling transform, by calling its <code>setScale(float)</code> method. There are alternate signatures to this method, one taking three floats, one for each coordinate, and the other taking an instance of <a href="https://javadoc.jmonkeyengine.org/v3.6.1-stable/com/jme3/math/Vector3f.html"><code>Vector3f</code></a>; it may be that one of these would be preferable because for my purposes Im only interested in varing the length.</p>
<h2><a href="#skin" id="skin"></a>Skin</h2>
<p>In order to change the overall skin colour of a character, we have to modify the <a href="https://javadoc.jmonkeyengine.org/v3.6.1-stable/com/jme3/material/Material.html"><code>Material</code></a> or the <code>Texture</code> of the skin;</p>
<p>Material has a method <code>setColor(String, ColorRGBA)</code> which should do the trick; and every Geometry has a Material. The Geometry class is a subclass of Spatial, so if the Spatial returned by <code>AssetManager.loadNodel(String)</code> is an instance of Geometry, which I believe it will be, were <em>probably</em> golden. If not, the <code>Cgm</code> class has a mechanism for getting the Texture, but not the Material, of the Spatial, and Ill have to explore that route.</p>
<p>Broadly, I think that the material of the skin should be acted on by the genome (to set overall colour), while the texture may be acted on by some mechanism for handling acquired characteristics (to handle scars, amputations, tattoos, etc).</p>
<h2><a href="#eyes-hair" id="eyes-hair"></a>Eyes, hair</h2>
<p>I need to be able to set eye colour, eyebrow shape and colour, hair colour, and baldness from the genome (modulated by age). The hair <strong>style</strong> should not be set from the genome but from acquired characteristics. Im assuming that hair dyes are not a thing in the in-game culture, otherwise hair colour would also have to be in acquired characteristics.</p>
</div></div></div></body></html>