[Fixed] Curve culling fail [jme3 r8228]

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):

http://i.imgur.com/BeaJG.jpg


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]

Try recomputing the bounding volume before you attach:



Ex.

geometryObj.setModelBound(new BoundingBox());

that has no effect

I believe it’s a jme3 bug, … any ideas against this?

If it is a bug, a small test case example (single class) reproducing it would be best to get it fixed.

maybe this

[java]/**

*

  • Copyright © 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.issues2;







    import com.jme3.app.
    ;

    import com.jme3.bounding.;

    import com.jme3.font.
    ;

    import com.jme3.input.;

    import com.jme3.input.controls.
    ;

    import com.jme3.material.;

    import com.jme3.math.
    ;

    import com.jme3.renderer.;

    import com.jme3.renderer.Camera.FrustumIntersect;

    import com.jme3.scene.
    ;

    import com.jme3.scene.Spatial.CullHint;

    import com.jme3.scene.shape.*;







    public class SingleClassCurveCulling 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 float remainder;

    private float elapsed = 0;

    private final static int perSec = 60;

    private static final float autoStopAfter_seconds = 6; // boxRed auto-stops

    private final ActionListener actionListener;

    private BitmapText isCulledText;

    private BitmapText cullHintText;

    public Geometry curveGeom;

    private final Spline spline = new Spline();

    private float yaw = 0f;

    private float roll = 0f;

    private float pitch = 0f;

    private final Quaternion absoluteRotation = new Quaternion();

    private Node boxNode;

    private Vector3f boxsCorner;





    public static void main( final String[] args ) {

    final SingleClassCurveCulling app = new SingleClassCurveCulling();

    app.setShowSettings( true );

    app.start();

    }







    /**
  • constructor

    */

    public SingleClassCurveCulling() {

    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 ( curveGeom.getCullHint().equals( CullHint.Never ) ) {

    curveGeom.setCullHint( CullHint.Dynamic );

    } else {

    curveGeom.setCullHint( CullHint.Never );

    }

    updateCHText();

    break;

    }

    }

    } while ( false );

    }// onAction

    };



    }





    @Override

    public void simpleUpdate( 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.setModelBound( new BoundingBox() );



    curveGeom.setLocalRotation( getAnnullingLocalRotationForWorldRotation( curveGeom, Quaternion.ZERO ) );

    curveGeom.setLocalScale( getAnnullingLocalScaleForWorldScale( curveGeom, Vector3f.UNIT_XYZ ) );

    curveGeom.setLocalTranslation( getAnnullingLocalTranslationForWorldTranslation( curveGeom, Vector3f.ZERO ) );





    repeatI–;

    }





    if ( isCulled( 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 " + 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 );



    final Material curveMat = new Material( assetManager, “Common/MatDefs/Misc/Unshaded.j3md” );

    curveMat.getAdditionalRenderState().setWireframe( true );

    curveMat.setColor( “Color”, ColorRGBA.Green );

    curveGeom = new Geometry( “trails” );

    curveGeom.setMaterial( curveMat );

    subNode.attachChild( curveGeom );



    final Vector3f boxCenter = new Vector3f( 2, 3, 4 );

    Box box = new Box( boxCenter, 0.5f, 0.9f, 1.3f );

    final Geometry boxGeom = new Geometry( “boxGeom for box”, box );

    // don’t set boxGeom’s transforms, instead do boxNode’s

    boxNode = new Node( “boxNode for box” );

    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”, ColorRGBA.Red );

    boxGeom.setMaterial( mat2 );

    subNode.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() );





    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();



    }





    /**
  • you must call this every time any of this Node’s parents change their
  • transform :confused:

    *
  • @param forNode
  • @param desiredWorldRotation
  •        will keep this local rotation, no matter what the inherited<br />
    
  •        (from parents) rotations are happening<br />
    
  • @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 :confused:

    *
  • @param forNode
  • @param desiredWorldScale
  •        ensure this scale stays fixed, disregarding all inherited<br />
    
  •        scales from parents<br />
    
  • @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 :confused: 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]



    but now I’m getting a jvm crash after I Esc out from the program (updated to svn 8365 from previously non-crashing rev8319 and I did delete and update the lwjgl dlls to nightly but it still crashes)

    seems it happens on dll unloading of OpenAL64.dll

    http://i.imgur.com/8nYmQ.png

I’m in rev. 8467

Looks like almost any jme3 app still crashes at the end,



However, I found out the problem with the culling is that I used Quaternion.ZERO instead of Quaternion.IDENTITY for rotation of curveGeom

If, I use ZERO then depending on the camera rotation, the curve gets culled at times, while sometimes is seen. If I use IDENTITY then this doesn’t seem to happen. Could someone explain that w=0 and w=1 difference? I guess my aim was to use no-rotation, but ZERO was the bad choice since IDENTITY was supposed to be used there.



Here’s the more simplified code, with ZERO (just change to IDENTITY if u wanna see the fixed version):

in this code the curve appears after like 10 seconds, because the cube keeps going and thus changing the curve length or so.



[java]/**

*

  • Copyright © 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.issues2;







    import com.jme3.app.
    ;

    import com.jme3.bounding.;

    import com.jme3.font.
    ;

    import com.jme3.input.;

    import com.jme3.input.controls.
    ;

    import com.jme3.material.;

    import com.jme3.math.
    ;

    import com.jme3.renderer.;

    import com.jme3.renderer.Camera.FrustumIntersect;

    import com.jme3.scene.
    ;

    import com.jme3.scene.Spatial.CullHint;

    import com.jme3.scene.shape.*;







    public class SingleClassCurveCulling 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 final ActionListener actionListener;

    private BitmapText isCulledText;

    private BitmapText cullHintText;

    private Geometry curveGeom;

    private final Spline spline = new Spline();

    private float yaw = 0f;

    private float roll = 0f;

    private float pitch = 0f;

    private final Quaternion absoluteRotation = new Quaternion();

    private Node boxNode;

    private Vector3f boxsCorner;





    public static void main( final String[] args ) {

    final SingleClassCurveCulling app = new SingleClassCurveCulling();

    app.setShowSettings( true );

    app.start();

    }







    /**
  • constructor

    */

    public SingleClassCurveCulling() {

    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 ( curveGeom.getCullHint().equals( CullHint.Never ) ) {

    curveGeom.setCullHint( CullHint.Dynamic );

    } else {

    curveGeom.setCullHint( CullHint.Never );

    }

    updateCHText();

    break;

    }

    }

    } while ( false );

    }// onAction

    };



    }





    @Override

    public void simpleUpdate( float tpf ) {



    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 ) );



    compute();





    if ( isCulled( curveGeom.getWorldBound(), cam ) ) {

    updateCText( “CULLED” );

    } else {

    updateCText( “SEEN” );

    }

    }





    private void compute() {

    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( Quaternion.ZERO // XXX: change to IDENTITY to see the curve culling fixed

    );

    }







    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 " + curveGeom.getCullHint().name()
  • " (press Left Alt key to change)" );

    }





    @Override

    public void simpleInitApp() {

    flyCam.setMoveSpeed( 20f + moveSpeed );

    cam.setRotation( new Quaternion( -0.097886816f, 0.85531867f, 0.17529537f, 0.4776188f ) );

    cam.setLocation( new Vector3f( 0.78897464f, 0.39651835f, 9.513835f ) );





    final Vector3f boxCenter = new Vector3f( 2, 3, 4 );

    Box box = new Box( boxCenter, 0.5f, 0.9f, 1.3f );

    final Geometry boxGeom = new Geometry( “boxGeom for box”, box );

    // don’t set boxGeom’s transforms, instead do boxNode’s

    boxNode = new Node( “boxNode for box” );

    boxNode.attachChild( boxGeom );



    final Material mat2 = new Material( assetManager, “Common/MatDefs/Misc/Unshaded.j3md” );

    mat2.getAdditionalRenderState().setWireframe( true );

    mat2.setColor( “Color”, ColorRGBA.Red );

    boxGeom.setMaterial( mat2 );

    // subNode.attachChild( boxNode );

    rootNode.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() );





    final Material curveMat = new Material( assetManager, “Common/MatDefs/Misc/Unshaded.j3md” );

    curveMat.getAdditionalRenderState().setWireframe( true );

    curveMat.setColor( “Color”, ColorRGBA.Green );

    curveGeom = new Geometry( “trails” );

    curveGeom.setMaterial( curveMat );

    // subNode.attachChild( curveGeom );

    rootNode.attachChild( curveGeom );

    compute();





    helperKeys();

    }





    private void helperKeys() {

    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]