FIXED: should use IDENTITY not ZERO, on curve rotation - to signify NO-ROTATION
I have this issue where the geometry of a Curve is being culled by default(CullHint.Dynamic) (unless it’s set to CullHint.Never) but it shouldn’t be. I might be missing something here, if not, it’s maybe a jme3 bug.
I’ve spent some time to simplify the code, and I have it split in two classes; when you run the program you can use left Alt
key to toggle between CullHint.Dynamic (where the Curve disappears) and CullHint.Never (where Curve is always shown), OR/AND when you run program, you can move mouse to your right to see the Curve appearing even with CullHint.Dynamic, then move to left again to see it disappear; it clearly should be still visible, right? Let me know if you think it’s a jme3 bug or a bug in my program, if the former let me know when fixed pls.
here are the screenies:
Curve is culled by default (in this camera position):
Curve is now not-culled so you can see it, because I pressed LAlt and CullHint changed from Dynamic(default) to Never:
http://i.imgur.com/7S7sB.jpg
Curve is not-culled now because I moved mouse to my right and this changed camera position, even tho CullHint is on Dynamic:
http://i.imgur.com/K9MFd.jpg
===============
code to reproduce the above screenies:
[java]
/**
*
* Copyright (c) 2011, pd
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.issues1;
import com.jme3.app.*;
import com.jme3.bounding.*;
import com.jme3.font.*;
import com.jme3.input.*;
import com.jme3.input.controls.*;
import com.jme3.math.*;
import com.jme3.renderer.*;
import com.jme3.renderer.Camera.FrustumIntersect;
import com.jme3.scene.*;
import com.jme3.scene.Spatial.CullHint;
public class CurveCullingFail extends SimpleApplication {
private static final float moveSpeed = 1f;
private static final float rotSpeed = 2f;
private static final float yawSpeed = 4f;
private static final float rollSpeed = 7f;
private static final float pitchSpeed = 11f;
private static final String mapChangeCullingType = "mapChangeCullingType";
private TheBox boxRed;
private final ActionListener actionListener;
private BitmapText isCulledText;
private BitmapText cullHintText;
public static void main( final String[] args ) {
final CurveCullingFail app = new CurveCullingFail();
app.setShowSettings( true );
app.start();
}
/**
* constructor
*/
public CurveCullingFail() {
super();
actionListener = new ActionListener() {
@SuppressWarnings( "synthetic-access" )
@Override
public void onAction( final String name, final boolean isPressed, final float tpf ) {
do {
if ( isPressed ) {
if ( mapChangeCullingType.equals( name ) ) {
if ( boxRed.curveGeom.getCullHint().equals( CullHint.Never ) ) {
boxRed.curveGeom.setCullHint( CullHint.Dynamic );
} else {
boxRed.curveGeom.setCullHint( CullHint.Never );
}
updateCHText();
break;
}
}
} while ( false );
}// onAction
};
}
@Override
public void simpleUpdate( float tpf ) {
boxRed.process( tpf );
if ( isCulled( boxRed.curveGeom.getWorldBound(), cam ) ) {
updateCText( "CULLED" );
} else {
updateCText( "SEEN" );
}
}
public static boolean isCulled( final BoundingVolume bv, final Camera cam1 ) {
final int planeState = bv.getCheckPlane();
bv.setCheckPlane( 0 );
final int camPS = cam1.getPlaneState();
cam1.setPlaneState( 0 );
final FrustumIntersect fi = cam1.contains( bv );
cam1.setPlaneState( camPS );
bv.setCheckPlane( planeState );
return fi.equals( FrustumIntersect.Outside );
}
private void updateCText( String msg ) {
isCulledText.setText( "red box's curve is " + msg );
}
private void updateCHText() {
cullHintText.setText( "red box's curve CullHint is " + boxRed.curveGeom.getCullHint().name()
+ " (press `Left Alt` key to change)" );
}
@Override
public void simpleInitApp() {
flyCam.setMoveSpeed( 20f + moveSpeed );
final Node subNode = new Node();
subNode.setLocalTranslation( new Vector3f( 2, 3.5f, 12 ) );
subNode.rotate( 1f, -2f, 4f );
// subNode.setLocalScale( 0.4f );
subNode.setLocalScale( 1.3f, 0.7f, 0.4f );
cam.lookAt( subNode.getWorldTranslation(), cam.getUp() );
cam.setLocation( new Vector3f( -15.568378f, 38.364925f, 1.7208971f ) );
cam.setRotation( new Quaternion( 0.7865911f, -0.1309775f, -0.017179994f, 0.6031784f ) );
rootNode.setLocalTranslation( -1, -2, -4 );
rootNode.rotate( -10f, 20f, -40f );
rootNode.setLocalScale( 1.5f );
rootNode.scale( 0.4f, 1.4f, 2.1f );
rootNode.attachChild( subNode );
boxRed = new TheBox( assetManager, "boxRed", ColorRGBA.Red, ColorRGBA.Green, subNode );
boxRed.setStuff( moveSpeed, rotSpeed, yawSpeed, rollSpeed, pitchSpeed );
inputManager.addMapping( mapChangeCullingType, new KeyTrigger( KeyInput.KEY_LMENU ) );
inputManager.addListener( actionListener, mapChangeCullingType );
int rs = guiFont.getCharSet().getRenderedSize();
isCulledText = new BitmapText( guiFont, false );
isCulledText.setSize( rs );
isCulledText.setLocalTranslation( 100, 22 * isCulledText.getLineHeight(), 0 );
guiNode.attachChild( isCulledText );
cullHintText = new BitmapText( guiFont, false );
cullHintText.setSize( rs );
cullHintText.setLocalTranslation( 100, 21 * cullHintText.getLineHeight(), 0 );
guiNode.attachChild( cullHintText );
updateCHText();
}
}
[/java]
============================================================================
====================================
===================
=========================
[java]
/**
*
* Copyright (c) 2011, pd
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.issues1;
import com.jme3.asset.*;
import com.jme3.font.*;
import com.jme3.material.*;
import com.jme3.math.*;
import com.jme3.renderer.*;
import com.jme3.renderer.queue.RenderQueue.Bucket;
import com.jme3.scene.*;
import com.jme3.scene.control.*;
import com.jme3.scene.debug.*;
import com.jme3.scene.shape.*;
public class TheBox {
private static final float lineWidth = 0.1f;
private static final float lineLen = 1f;
private final Box box;
private final Node boxNode;
private final AssetManager assetManager;
public final Geometry curveGeom;
private final Vector3f boxsCorner;
private final Quaternion absoluteRotation = new Quaternion();
private float yaw = 0f;
private float roll = 0f;
private float pitch = 0f;
private final Spline spline = new Spline();
private float moveSpeed = 2f;
private float rotSpeed = 2f;
private float yawSpeed = 4f;
private float rollSpeed = 7f;
private float pitchSpeed = 11f;
private float remainder;
private float elapsed = 0;
private final static int perSec = 60;
private static final float autoStopAfter_seconds = 6; // boxRed auto-stops
public void setStuff( final float moveSpeed1, final float rotSpeed1, final float yawSpeed1, final float rollSpeed1,
final float pitchSpeed1 )
{
moveSpeed = moveSpeed1;
rotSpeed = rotSpeed1;
yawSpeed = yawSpeed1;
rollSpeed = rollSpeed1;
pitchSpeed = pitchSpeed1;
}
public TheBox( final AssetManager assetManager1, final String boxName, final ColorRGBA boxColor,
final ColorRGBA curveColor, final Node attachToNode )
{
assetManager = assetManager1;
final Vector3f boxCenter =
// Vector3f.ZERO;
new Vector3f( 2, 3, 4 );
box = new Box( boxCenter, 0.5f, 0.9f, 1.3f );
final Geometry boxGeom = new Geometry( "boxGeom for `" + boxName + "`", box );
// don't set boxGeom's transforms, instead do boxNode's
boxNode = new Node( "boxNode for `" + boxName + "`" );
boxNode.attachChild( boxGeom );
boxNode.setLocalTranslation( 1f, 2f, 3f );
boxNode.scale( 1.3f, 1.2f, 1.1f );// always ok
boxNode.rotate( 2f, -4f, 0.4f );
final Material mat2 = new Material( assetManager, "Common/MatDefs/Misc/Unshaded.j3md" );
mat2.getAdditionalRenderState().setWireframe( true );
mat2.setColor( "Color", boxColor );
boxGeom.setMaterial( mat2 );
final Material curveMat = new Material( assetManager, "Common/MatDefs/Misc/Unshaded.j3md" );
curveMat.getAdditionalRenderState().setWireframe( true );
curveMat.setColor( "Color", curveColor );
curveGeom = new Geometry( "trails" );
curveGeom.setMaterial( curveMat );
attachToNode.attachChild( curveGeom );
attachToNode.attachChild( boxNode );
boxsCorner = new Vector3f();
// calculating position of corner, first getting corner as it were if
// box were at 0,0,0 and no rotation/scale
boxsCorner.set( box.getXExtent(), box.getYExtent(), box.getZExtent() );
// now considering box may have a different than 0,0,0 center
boxsCorner.addLocal( box.getCenter() );
attachCoordinateAxes( assetManager, box.getCenter(), boxNode, ColorRGBA.Green, lineLen, lineWidth );
}
public static Node attachCoordinateAxes( final AssetManager assetManager, final Vector3f pos, final Node toNode,
final ColorRGBA textColor, final float lineLen1, final float lineWidth1 )
{
final Node axesNode = new Node( "CoordinateAxesNode" );
final Geometry box = gimmeBox( assetManager, "origin" + " for `" + toNode.getName() + "`", lineWidth1, ColorRGBA.Cyan );
axesNode.attachChild( box );
final Node dislocated = new Node();
dislocated.setLocalTranslation( pos );
dislocated.attachChild( axesNode );
toNode.attachChild( dislocated );
axesNode.attachChild( putArrow( assetManager, Vector3f.UNIT_X, lineLen1, lineWidth1, ColorRGBA.Red, toNode.getName(),
"x", textColor ) );
axesNode.attachChild( putArrow( assetManager, Vector3f.UNIT_Y, lineLen1, lineWidth1, ColorRGBA.Green, toNode.getName(),
"y", textColor ) );
axesNode.attachChild( putArrow( assetManager, Vector3f.UNIT_Z, lineLen1, lineWidth1, ColorRGBA.Yellow,
toNode.getName(), "z", textColor ) );
return axesNode;
}
public static Geometry gimmeBox( final AssetManager assetManager, final String name, final float extent,
final ColorRGBA color )
{
final Box b = new Box( extent, extent, extent );
final Geometry g = new Geometry( name, b );
final Material m = new Material( assetManager, "Common/MatDefs/Misc/Unshaded.j3md" );
m.setColor( "Color", color );
g.setMaterial( m );
return g;
}
public static Node putArrow( final AssetManager assetManager, final Vector3f direction, final float lineLen1,
final float lineWidth1, final ColorRGBA color, final String forName, final String name, final ColorRGBA textColor )
{
final Node entireArrowNode = new Node();
final String for1 = " for `" + forName + "`";
final Vector3f dir = direction.normalize();
//
make Box
System.out.println( "box dir:" + dir );
final Vector3f boxSize = dir.mult( lineLen1 );
System.out.println( "box size:" + boxSize );
final Box boxMesh = new Box( boxSize.divide( 2 )// center
, lineWidth1 + ( boxSize.getX() / 2 ), lineWidth1 + ( boxSize.getY() / 2 ), lineWidth1 + ( boxSize.getZ() / 2 ) );// only
// for
System.out.println( "box center=" + boxSize.divide( 2 ) );
System.out.println( "x=" + lineWidth1 + "/" + ( boxSize.getX() / 2 ) );
System.out.println( "xE=" + boxMesh.getXExtent() );
final Geometry boxGeo = new Geometry( "Box axis " + name + for1, boxMesh );
final Material boxMat = new Material( assetManager, "Common/MatDefs/Misc/Unshaded.j3md" );
boxMat.setColor( "Color", color );
boxMat.getAdditionalRenderState().setWireframe( true );
boxGeo.setMaterial( boxMat );
entireArrowNode.attachChild( boxGeo );
//
make Arrow
final Vector3f arrowExtent = dir.mult( lineLen1 );
final Arrow arrowMesh = new Arrow( arrowExtent );// only for Arrow
arrowMesh.setLineWidth( lineWidth1 < 1 ? 1 : lineWidth1 );// only for Arrow
final Geometry arrowGeo = new Geometry( "Arrow axis " + name + for1, arrowMesh );
final Material arrowMat = boxMat.clone();
arrowMat.getAdditionalRenderState().setWireframe( true );
arrowGeo.setMaterial( arrowMat );
entireArrowNode.attachChild( arrowGeo );
//
make text
final Node txtNode = addTextTag( assetManager, name, color, lineLen1, true );
final Vector3f boxEdge =
new Vector3f( ( boxMesh.getXExtent() - lineWidth1 ) * 2, ( boxMesh.getYExtent() - lineWidth1 ) * 2,
( boxMesh.getZExtent() - lineWidth1 ) * 2 );
System.out.println( "boxEdge=" + boxEdge + " boxPos now=" + boxGeo.getLocalTranslation() );
if ( lineLen1 > 10 ) {
final Geometry whiteTip = gimmeBox( assetManager, "whiteTip " + name + for1, lineWidth1, ColorRGBA.White );
whiteTip.setLocalTranslation( boxEdge );
entireArrowNode.attachChild( whiteTip );
}
txtNode.setLocalTranslation( boxEdge );
entireArrowNode.attachChild( txtNode );
return entireArrowNode;
}
private static class XControl extends AbstractControl {
private final float initialSize;
public XControl( final float textSize ) {
initialSize = textSize;
}
/*
* (non-Javadoc)
*
* @see com.jme3.scene.control.AbstractControl#setSpatial(com.jme3.scene.Spatial)
*/
@Override
public void setSpatial( final Spatial spatial1 ) {
assert ( null == spatial1 ) || ( spatial1 instanceof BitmapText );
super.setSpatial( spatial1 );
}
@Override
public Control cloneForSpatial( final Spatial spatial1 ) {
throw null;
}
@Override
protected void controlUpdate( final float tpf ) {
// XXX: problem is, this runs on every update, CPU eater
final float x = spatial.getWorldScale().getX();
final float y = spatial.getWorldScale().getY();
final float z = spatial.getWorldScale().getZ();
( (BitmapText)spatial ).setSize( initialSize * ( ( x + y + z ) / 3 / 4 ) );// x > ( y / 2 ) ? x : y );
}
@Override
protected final void controlRender( final RenderManager rm, final ViewPort vp ) {
// empty
}
}
public static BitmapText addTextTag( final AssetManager assetManager, final String theText, final ColorRGBA color,
final float size, final boolean keepUpdated )
{
final BitmapFont fnt = assetManager.loadFont( "Interface/Fonts/Default.fnt" );
final BitmapText txt = new BitmapText( fnt, false );
txt.setQueueBucket( Bucket.Transparent );
txt.setText( theText );
txt.setColor( color );
txt.addControl( new BillboardControl() );
if ( keepUpdated ) {
txt.addControl( new XControl( size ) );
}
return txt;
}
/**
* @param tpf
*/
public void process( float tpf ) {
elapsed += tpf;
if ( elapsed > autoStopAfter_seconds ) {// autostop after x seconds
return;
}
final float repeatF = tpf * perSec;
int repeatI = (int)repeatF;
remainder += repeatF - repeatI;
if ( remainder > 1f ) {
final int intPart = (int)remainder;
repeatI += intPart;
remainder = remainder - intPart;
}
tpf = 1f / perSec;
while ( repeatI > 0 ) {
yaw = ( yaw + ( FastMath.DEG_TO_RAD * rotSpeed * yawSpeed * tpf ) ) % ( FastMath.PI * 2 );
roll = ( roll + ( FastMath.DEG_TO_RAD * rotSpeed * rollSpeed * tpf ) ) % ( FastMath.PI * 2 );
pitch = ( pitch + ( FastMath.DEG_TO_RAD * rotSpeed * pitchSpeed * tpf ) ) % ( FastMath.PI * 2 );
absoluteRotation.fromAngles( yaw, roll, pitch );
// rotating the box to these absolute angles (from its origin pos)
boxNode.setLocalRotation( absoluteRotation );
// move box "forward" too, that is, forward relative to itself ie. spaceship moving forward
boxNode.move( boxNode.getLocalRotation().getRotationColumn( 2 ).mult( moveSpeed * tpf ) );
// we keep getting world coords of the box's (same)corner
final Vector3f worldPosOfBoxsCorner = boxNode.localToWorld( boxsCorner, null );
spline.addControlPoint( worldPosOfBoxsCorner );// must be cloned! it is
final int subSegments = 1;
curveGeom.setMesh( new Curve( spline, subSegments ) );
curveGeom.setLocalRotation( getAnnullingLocalRotationForWorldRotation( curveGeom, Quaternion.ZERO ) );
curveGeom.setLocalScale( getAnnullingLocalScaleForWorldScale( curveGeom, Vector3f.UNIT_XYZ ) );
curveGeom.setLocalTranslation( getAnnullingLocalTranslationForWorldTranslation( curveGeom, Vector3f.ZERO ) );
repeatI--;
}
}
/**
* you must call this every time any of this Node's parents change their
* transform :/
*
* @param forNode
* @param desiredWorldRotation
* will keep this local rotation, no matter what the inherited
* (from parents) rotations are happening
* @return
*/
public static final Quaternion getAnnullingLocalRotationForWorldRotation( final Spatial forNode,
final Quaternion desiredWorldRotation )
{
final Spatial parent = forNode.getParent();
if ( null == parent ) {
return desiredWorldRotation;
} else {
final Quaternion parentSinverseRot = parent.getWorldRotation().inverse();
assert null != parentSinverseRot;
if ( null == parentSinverseRot ) {
throw null;// unexpected, recheck when happens
}
parentSinverseRot.multLocal( desiredWorldRotation );
return parentSinverseRot;
}
}
/**
* ignore all inherited scales from parents<br>
* you must call this every time parents' scale changes though :/
*
* @param forNode
* @param desiredWorldScale
* ensure this scale stays fixed, disregarding all inherited
* scales from parents
* @return
*/
public static final Vector3f getAnnullingLocalScaleForWorldScale( final Spatial forNode, final Vector3f desiredWorldScale )
{
final Spatial parent = forNode.getParent();
if ( null == parent ) {
return desiredWorldScale;
} else {
return desiredWorldScale.divide( parent.getWorldScale() );
}
}
/**
* make sure this node is always at the same desired world position, no
* matter what parents' transforms are affecting it<br>
* you must call this every time any parents' transform change :/ parents
* meaning anything above this node, ie. grandparents too
*
* @param forNode
* @param desiredWorldTranslation
* @return
*/
public static final Vector3f getAnnullingLocalTranslationForWorldTranslation( final Spatial forNode,
final Vector3f desiredWorldTranslation )
{
final Spatial parent = forNode.getParent();
if ( null == parent ) {
return desiredWorldTranslation;
} else {
return parent.worldToLocal( desiredWorldTranslation, null );
}
}
}
[/java]