diff options
author | neothemachine <[email protected]> | 2013-02-21 16:40:08 +0100 |
---|---|---|
committer | neothemachine <[email protected]> | 2013-02-21 16:40:08 +0100 |
commit | f774bd917e9dfd8cf46e5ed66d41aad64477a238 (patch) | |
tree | 8d33aa0cf24165710d420c677f3336641223cff8 /ardor3d-extras/src/main/java/com | |
parent | edbc5c757c66d56eb2cffffa4076f0f2bfd6e748 (diff) |
normalized line endings
see
http://www.hanselman.com/blog/YoureJustAnotherCarriageReturnLineFeedInTheWall.aspx
and https://help.github.com/articles/dealing-with-line-endings
Diffstat (limited to 'ardor3d-extras/src/main/java/com')
30 files changed, 6496 insertions, 6496 deletions
diff --git a/ardor3d-extras/src/main/java/com/ardor3d/extension/atlas/AtlasTextureParameter.java b/ardor3d-extras/src/main/java/com/ardor3d/extension/atlas/AtlasTextureParameter.java index 4c2d5a2..c45b4d6 100644 --- a/ardor3d-extras/src/main/java/com/ardor3d/extension/atlas/AtlasTextureParameter.java +++ b/ardor3d-extras/src/main/java/com/ardor3d/extension/atlas/AtlasTextureParameter.java @@ -1,24 +1,24 @@ -/**
- * Copyright (c) 2008-2012 Ardor Labs, Inc.
- *
- * This file is part of Ardor3D.
- *
- * Ardor3D is free software: you can redistribute it and/or modify it
- * under the terms of its license which may be found in the accompanying
- * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
- */
-
-package com.ardor3d.extension.atlas;
-
-import com.ardor3d.image.Texture.ApplyMode;
-import com.ardor3d.image.Texture.MagnificationFilter;
-import com.ardor3d.image.Texture.MinificationFilter;
-import com.ardor3d.image.Texture.WrapMode;
-
-public class AtlasTextureParameter {
- public MinificationFilter minificationFilter = MinificationFilter.Trilinear;
- public MagnificationFilter magnificationFilter = MagnificationFilter.Bilinear;
- public WrapMode wrapMode = WrapMode.EdgeClamp;
- public ApplyMode applyMode = ApplyMode.Modulate;
- public boolean compress = false;
+/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.atlas; + +import com.ardor3d.image.Texture.ApplyMode; +import com.ardor3d.image.Texture.MagnificationFilter; +import com.ardor3d.image.Texture.MinificationFilter; +import com.ardor3d.image.Texture.WrapMode; + +public class AtlasTextureParameter { + public MinificationFilter minificationFilter = MinificationFilter.Trilinear; + public MagnificationFilter magnificationFilter = MagnificationFilter.Bilinear; + public WrapMode wrapMode = WrapMode.EdgeClamp; + public ApplyMode applyMode = ApplyMode.Modulate; + public boolean compress = false; }
\ No newline at end of file diff --git a/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/InteractManager.java b/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/InteractManager.java index 68cb450..e49ad25 100644 --- a/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/InteractManager.java +++ b/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/InteractManager.java @@ -1,242 +1,242 @@ -/**
- * Copyright (c) 2008-2012 Ardor Labs, Inc.
- *
- * This file is part of Ardor3D.
- *
- * Ardor3D is free software: you can redistribute it and/or modify it
- * under the terms of its license which may be found in the accompanying
- * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
- */
-
-package com.ardor3d.extension.interact;
-
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import com.ardor3d.extension.interact.data.SpatialState;
-import com.ardor3d.extension.interact.filter.UpdateFilter;
-import com.ardor3d.extension.interact.widget.AbstractInteractWidget;
-import com.ardor3d.framework.Canvas;
-import com.ardor3d.input.PhysicalLayer;
-import com.ardor3d.input.logical.BasicTriggersApplier;
-import com.ardor3d.input.logical.InputTrigger;
-import com.ardor3d.input.logical.LogicalLayer;
-import com.ardor3d.input.logical.TriggerAction;
-import com.ardor3d.input.logical.TwoInputStates;
-import com.ardor3d.renderer.Renderer;
-import com.ardor3d.scenegraph.Spatial;
-import com.ardor3d.util.ReadOnlyTimer;
-import com.google.common.base.Predicate;
-import com.google.common.collect.Lists;
-
-public class InteractManager {
-
- /**
- * List of widgets currently managed by this manager.
- */
- protected final List<AbstractInteractWidget> _widgets = Lists.newArrayList();
-
- /**
- * The logical layer used by this manager to receive input events prior to forwarding them to the scene.
- */
- protected final LogicalLayer _logicalLayer = new LogicalLayer();
-
- /**
- * Internal flag indicating whether the last input event was consumed by the manager. This is used to decide if we
- * will forward the event to the next LogicalLayer.
- */
- protected AtomicBoolean _inputConsumed = new AtomicBoolean(false);
-
- /**
- * The widget currently active.
- */
- protected AbstractInteractWidget _activeWidget;
-
- /**
- * The current Spatial being targeted for interaction.
- */
- protected Spatial _spatialTarget;
-
- /**
- * Spatial state tracking.
- */
- protected SpatialState _state = new SpatialState();
-
- /**
- * List of filters to modify state prior to applying to a Spatial target.
- */
- protected List<UpdateFilter> _filters = Lists.newArrayList();
-
- public InteractManager() {
- setupLogicalLayer();
- }
-
- public void update(final ReadOnlyTimer timer) {
- for (final AbstractInteractWidget widget : _widgets) {
- if (!widget.isActiveUpdateOnly() || widget == _activeWidget) {
- widget.update(timer, this);
- }
- }
- }
-
- public void render(final Renderer renderer) {
- for (final AbstractInteractWidget widget : _widgets) {
- if (!widget.isActiveRenderOnly() || widget == _activeWidget) {
- widget.render(renderer, this);
- }
- }
- }
-
- protected void offerInputToWidgets(final Canvas source, final TwoInputStates inputStates) {
- if (_activeWidget != null) {
- _activeWidget.processInput(source, inputStates, _inputConsumed, this);
- }
-
- if (!_inputConsumed.get()) {
- for (final AbstractInteractWidget widget : _widgets) {
- if (widget != _activeWidget && !widget.isActiveInputOnly()) {
- widget.processInput(source, inputStates, _inputConsumed, this);
- if (_inputConsumed.get()) {
- break;
- }
- }
- }
- }
-
- if (_spatialTarget != null && _inputConsumed.get()) {
- // apply any filters to our state
- for (final UpdateFilter filter : _filters) {
- filter.applyFilter(this);
- }
-
- // apply state to target
- _state.applyState(_spatialTarget);
-
- // fire update event
- fireTargetDataUpdated();
- }
- }
-
- /**
- * Set up our logical layer with a trigger that hands input to the manager and saves whether it was "consumed".
- */
- private void setupLogicalLayer() {
- _logicalLayer.registerTrigger(new InputTrigger(new Predicate<TwoInputStates>() {
- public boolean apply(final TwoInputStates arg0) {
- // always trigger this.
- return true;
- }
- }, new TriggerAction() {
- public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
- if (_spatialTarget != null) {
- _state.getTransform().set(_spatialTarget.getTransform());
- }
- _inputConsumed.set(false);
- offerInputToWidgets(source, inputStates);
- }
- }));
- }
-
- /**
- * Convenience method for setting up the manager's connection to the Ardor3D input system, along with a forwarding
- * address for input events that the manager does not care about.
- *
- * @param canvas
- * the canvas to register with
- * @param physicalLayer
- * the physical layer to register with
- * @param forwardTo
- * a LogicalLayer to send unconsumed (by the manager) input events to.
- */
- public void setupInput(final Canvas canvas, final PhysicalLayer physicalLayer, final LogicalLayer forwardTo) {
- // Set up this logical layer to listen for events from the given canvas and PhysicalLayer
- _logicalLayer.registerInput(canvas, physicalLayer);
-
- // Set up forwarding for events not consumed.
- if (forwardTo != null) {
- _logicalLayer.setApplier(new BasicTriggersApplier() {
-
- @Override
- public void checkAndPerformTriggers(final Set<InputTrigger> triggers, final Canvas source,
- final TwoInputStates states, final double tpf) {
- super.checkAndPerformTriggers(triggers, source, states, tpf);
-
- if (!_inputConsumed.get()) {
- // nothing consumed
- forwardTo.getApplier().checkAndPerformTriggers(forwardTo.getTriggers(), source, states, tpf);
- } else {
- // consumed, do nothing.
- }
- }
- });
- }
- }
-
- public void addWidget(final AbstractInteractWidget widget) {
- _widgets.add(widget);
- }
-
- public void removeWidget(final AbstractInteractWidget widget) {
- if (_activeWidget == widget) {
- _activeWidget = _widgets.isEmpty() ? null : _widgets.get(0);
- }
- _widgets.remove(widget);
- }
-
- public void clearWidgets() {
- _widgets.clear();
- }
-
- public void addFilter(final UpdateFilter filter) {
- _filters.add(filter);
- }
-
- public void removeFilter(final UpdateFilter filter) {
- _filters.remove(filter);
- }
-
- public void clearFilters() {
- _filters.clear();
- }
-
- public LogicalLayer getLogicalLayer() {
- return _logicalLayer;
- }
-
- public void setActiveWidget(final AbstractInteractWidget widget) {
- _activeWidget = widget;
- }
-
- public AbstractInteractWidget getActiveWidget() {
- return _activeWidget;
- }
-
- public void setSpatialTarget(final Spatial target) {
- if (_spatialTarget != target) {
- _spatialTarget = target;
- fireTargetChanged();
- }
- }
-
- public void fireTargetChanged() {
- for (final AbstractInteractWidget widget : _widgets) {
- widget.targetChanged(this);
- }
- }
-
- public void fireTargetDataUpdated() {
- for (final AbstractInteractWidget widget : _widgets) {
- widget.targetDataUpdated(this);
- }
- }
-
- public Spatial getSpatialTarget() {
- return _spatialTarget;
- }
-
- public SpatialState getSpatialState() {
- return _state;
- }
-
-}
+/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.interact; + +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; + +import com.ardor3d.extension.interact.data.SpatialState; +import com.ardor3d.extension.interact.filter.UpdateFilter; +import com.ardor3d.extension.interact.widget.AbstractInteractWidget; +import com.ardor3d.framework.Canvas; +import com.ardor3d.input.PhysicalLayer; +import com.ardor3d.input.logical.BasicTriggersApplier; +import com.ardor3d.input.logical.InputTrigger; +import com.ardor3d.input.logical.LogicalLayer; +import com.ardor3d.input.logical.TriggerAction; +import com.ardor3d.input.logical.TwoInputStates; +import com.ardor3d.renderer.Renderer; +import com.ardor3d.scenegraph.Spatial; +import com.ardor3d.util.ReadOnlyTimer; +import com.google.common.base.Predicate; +import com.google.common.collect.Lists; + +public class InteractManager { + + /** + * List of widgets currently managed by this manager. + */ + protected final List<AbstractInteractWidget> _widgets = Lists.newArrayList(); + + /** + * The logical layer used by this manager to receive input events prior to forwarding them to the scene. + */ + protected final LogicalLayer _logicalLayer = new LogicalLayer(); + + /** + * Internal flag indicating whether the last input event was consumed by the manager. This is used to decide if we + * will forward the event to the next LogicalLayer. + */ + protected AtomicBoolean _inputConsumed = new AtomicBoolean(false); + + /** + * The widget currently active. + */ + protected AbstractInteractWidget _activeWidget; + + /** + * The current Spatial being targeted for interaction. + */ + protected Spatial _spatialTarget; + + /** + * Spatial state tracking. + */ + protected SpatialState _state = new SpatialState(); + + /** + * List of filters to modify state prior to applying to a Spatial target. + */ + protected List<UpdateFilter> _filters = Lists.newArrayList(); + + public InteractManager() { + setupLogicalLayer(); + } + + public void update(final ReadOnlyTimer timer) { + for (final AbstractInteractWidget widget : _widgets) { + if (!widget.isActiveUpdateOnly() || widget == _activeWidget) { + widget.update(timer, this); + } + } + } + + public void render(final Renderer renderer) { + for (final AbstractInteractWidget widget : _widgets) { + if (!widget.isActiveRenderOnly() || widget == _activeWidget) { + widget.render(renderer, this); + } + } + } + + protected void offerInputToWidgets(final Canvas source, final TwoInputStates inputStates) { + if (_activeWidget != null) { + _activeWidget.processInput(source, inputStates, _inputConsumed, this); + } + + if (!_inputConsumed.get()) { + for (final AbstractInteractWidget widget : _widgets) { + if (widget != _activeWidget && !widget.isActiveInputOnly()) { + widget.processInput(source, inputStates, _inputConsumed, this); + if (_inputConsumed.get()) { + break; + } + } + } + } + + if (_spatialTarget != null && _inputConsumed.get()) { + // apply any filters to our state + for (final UpdateFilter filter : _filters) { + filter.applyFilter(this); + } + + // apply state to target + _state.applyState(_spatialTarget); + + // fire update event + fireTargetDataUpdated(); + } + } + + /** + * Set up our logical layer with a trigger that hands input to the manager and saves whether it was "consumed". + */ + private void setupLogicalLayer() { + _logicalLayer.registerTrigger(new InputTrigger(new Predicate<TwoInputStates>() { + public boolean apply(final TwoInputStates arg0) { + // always trigger this. + return true; + } + }, new TriggerAction() { + public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) { + if (_spatialTarget != null) { + _state.getTransform().set(_spatialTarget.getTransform()); + } + _inputConsumed.set(false); + offerInputToWidgets(source, inputStates); + } + })); + } + + /** + * Convenience method for setting up the manager's connection to the Ardor3D input system, along with a forwarding + * address for input events that the manager does not care about. + * + * @param canvas + * the canvas to register with + * @param physicalLayer + * the physical layer to register with + * @param forwardTo + * a LogicalLayer to send unconsumed (by the manager) input events to. + */ + public void setupInput(final Canvas canvas, final PhysicalLayer physicalLayer, final LogicalLayer forwardTo) { + // Set up this logical layer to listen for events from the given canvas and PhysicalLayer + _logicalLayer.registerInput(canvas, physicalLayer); + + // Set up forwarding for events not consumed. + if (forwardTo != null) { + _logicalLayer.setApplier(new BasicTriggersApplier() { + + @Override + public void checkAndPerformTriggers(final Set<InputTrigger> triggers, final Canvas source, + final TwoInputStates states, final double tpf) { + super.checkAndPerformTriggers(triggers, source, states, tpf); + + if (!_inputConsumed.get()) { + // nothing consumed + forwardTo.getApplier().checkAndPerformTriggers(forwardTo.getTriggers(), source, states, tpf); + } else { + // consumed, do nothing. + } + } + }); + } + } + + public void addWidget(final AbstractInteractWidget widget) { + _widgets.add(widget); + } + + public void removeWidget(final AbstractInteractWidget widget) { + if (_activeWidget == widget) { + _activeWidget = _widgets.isEmpty() ? null : _widgets.get(0); + } + _widgets.remove(widget); + } + + public void clearWidgets() { + _widgets.clear(); + } + + public void addFilter(final UpdateFilter filter) { + _filters.add(filter); + } + + public void removeFilter(final UpdateFilter filter) { + _filters.remove(filter); + } + + public void clearFilters() { + _filters.clear(); + } + + public LogicalLayer getLogicalLayer() { + return _logicalLayer; + } + + public void setActiveWidget(final AbstractInteractWidget widget) { + _activeWidget = widget; + } + + public AbstractInteractWidget getActiveWidget() { + return _activeWidget; + } + + public void setSpatialTarget(final Spatial target) { + if (_spatialTarget != target) { + _spatialTarget = target; + fireTargetChanged(); + } + } + + public void fireTargetChanged() { + for (final AbstractInteractWidget widget : _widgets) { + widget.targetChanged(this); + } + } + + public void fireTargetDataUpdated() { + for (final AbstractInteractWidget widget : _widgets) { + widget.targetDataUpdated(this); + } + } + + public Spatial getSpatialTarget() { + return _spatialTarget; + } + + public SpatialState getSpatialState() { + return _state; + } + +} diff --git a/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/data/SpatialState.java b/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/data/SpatialState.java index d96ab5a..4ba58ea 100644 --- a/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/data/SpatialState.java +++ b/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/data/SpatialState.java @@ -1,28 +1,28 @@ -/**
- * Copyright (c) 2008-2012 Ardor Labs, Inc.
- *
- * This file is part of Ardor3D.
- *
- * Ardor3D is free software: you can redistribute it and/or modify it
- * under the terms of its license which may be found in the accompanying
- * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
- */
-
-package com.ardor3d.extension.interact.data;
-
-import com.ardor3d.math.Transform;
-import com.ardor3d.scenegraph.Spatial;
-
-public class SpatialState {
-
- protected Transform _transform = new Transform();
-
- public Transform getTransform() {
- return _transform;
- }
-
- public void applyState(final Spatial target) {
- target.setTransform(_transform);
- }
-
-}
+/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.interact.data; + +import com.ardor3d.math.Transform; +import com.ardor3d.scenegraph.Spatial; + +public class SpatialState { + + protected Transform _transform = new Transform(); + + public Transform getTransform() { + return _transform; + } + + public void applyState(final Spatial target) { + target.setTransform(_transform); + } + +} diff --git a/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/filter/AllowScaleFilter.java b/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/filter/AllowScaleFilter.java index a9798ce..d4707ac 100644 --- a/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/filter/AllowScaleFilter.java +++ b/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/filter/AllowScaleFilter.java @@ -1,44 +1,44 @@ -/**
- * Copyright (c) 2008-2012 Ardor Labs, Inc.
- *
- * This file is part of Ardor3D.
- *
- * Ardor3D is free software: you can redistribute it and/or modify it
- * under the terms of its license which may be found in the accompanying
- * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
- */
-
-package com.ardor3d.extension.interact.filter;
-
-import com.ardor3d.extension.interact.InteractManager;
-import com.ardor3d.extension.interact.data.SpatialState;
-import com.ardor3d.math.type.ReadOnlyVector3;
-
-public class AllowScaleFilter implements UpdateFilter {
-
- protected boolean _xAxis, _yAxis, _zAxis;
-
- public AllowScaleFilter(final boolean xAxis, final boolean yAxis, final boolean zAxis) {
- _xAxis = xAxis;
- _yAxis = yAxis;
- _zAxis = zAxis;
- }
-
- @Override
- public void applyFilter(final InteractManager manager) {
- final ReadOnlyVector3 oldScale = manager.getSpatialTarget().getScale();
- final SpatialState state = manager.getSpatialState();
- final ReadOnlyVector3 scale = state.getTransform().getScale();
-
- state.getTransform().setScale( //
- _xAxis ? scale.getX() : oldScale.getX(), //
- _yAxis ? scale.getY() : oldScale.getY(), //
- _zAxis ? scale.getZ() : oldScale.getZ());
- }
-
- @Override
- public void beginDrag(final InteractManager manager) {}
-
- @Override
- public void endDrag(final InteractManager manager) {}
-}
+/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.interact.filter; + +import com.ardor3d.extension.interact.InteractManager; +import com.ardor3d.extension.interact.data.SpatialState; +import com.ardor3d.math.type.ReadOnlyVector3; + +public class AllowScaleFilter implements UpdateFilter { + + protected boolean _xAxis, _yAxis, _zAxis; + + public AllowScaleFilter(final boolean xAxis, final boolean yAxis, final boolean zAxis) { + _xAxis = xAxis; + _yAxis = yAxis; + _zAxis = zAxis; + } + + @Override + public void applyFilter(final InteractManager manager) { + final ReadOnlyVector3 oldScale = manager.getSpatialTarget().getScale(); + final SpatialState state = manager.getSpatialState(); + final ReadOnlyVector3 scale = state.getTransform().getScale(); + + state.getTransform().setScale( // + _xAxis ? scale.getX() : oldScale.getX(), // + _yAxis ? scale.getY() : oldScale.getY(), // + _zAxis ? scale.getZ() : oldScale.getZ()); + } + + @Override + public void beginDrag(final InteractManager manager) {} + + @Override + public void endDrag(final InteractManager manager) {} +} diff --git a/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/filter/MinMaxScaleFilter.java b/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/filter/MinMaxScaleFilter.java index 7336bab..f331742 100644 --- a/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/filter/MinMaxScaleFilter.java +++ b/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/filter/MinMaxScaleFilter.java @@ -1,49 +1,49 @@ -/**
- * Copyright (c) 2008-2012 Ardor Labs, Inc.
- *
- * This file is part of Ardor3D.
- *
- * Ardor3D is free software: you can redistribute it and/or modify it
- * under the terms of its license which may be found in the accompanying
- * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
- */
-
-package com.ardor3d.extension.interact.filter;
-
-import com.ardor3d.extension.interact.InteractManager;
-import com.ardor3d.extension.interact.data.SpatialState;
-import com.ardor3d.math.MathUtils;
-import com.ardor3d.math.Vector3;
-import com.ardor3d.math.type.ReadOnlyVector3;
-
-public class MinMaxScaleFilter implements UpdateFilter {
- protected Vector3 _minScale = new Vector3();
- protected Vector3 _maxScale = new Vector3();
-
- public MinMaxScaleFilter(final double min, final double max) {
- _minScale.set(min, min, min);
- _maxScale.set(max, max, max);
- }
-
- public MinMaxScaleFilter(final ReadOnlyVector3 min, final ReadOnlyVector3 max) {
- _minScale.set(min);
- _maxScale.set(max);
- }
-
- @Override
- public void applyFilter(final InteractManager manager) {
- final SpatialState state = manager.getSpatialState();
- final ReadOnlyVector3 scale = state.getTransform().getScale();
- final double x = MathUtils.clamp(scale.getX(), _minScale.getX(), _maxScale.getX());
- final double y = MathUtils.clamp(scale.getY(), _minScale.getY(), _maxScale.getY());
- final double z = MathUtils.clamp(scale.getZ(), _minScale.getZ(), _maxScale.getZ());
-
- state.getTransform().setScale(x, y, z);
- }
-
- @Override
- public void beginDrag(final InteractManager manager) {}
-
- @Override
- public void endDrag(final InteractManager manager) {}
-}
+/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.interact.filter; + +import com.ardor3d.extension.interact.InteractManager; +import com.ardor3d.extension.interact.data.SpatialState; +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyVector3; + +public class MinMaxScaleFilter implements UpdateFilter { + protected Vector3 _minScale = new Vector3(); + protected Vector3 _maxScale = new Vector3(); + + public MinMaxScaleFilter(final double min, final double max) { + _minScale.set(min, min, min); + _maxScale.set(max, max, max); + } + + public MinMaxScaleFilter(final ReadOnlyVector3 min, final ReadOnlyVector3 max) { + _minScale.set(min); + _maxScale.set(max); + } + + @Override + public void applyFilter(final InteractManager manager) { + final SpatialState state = manager.getSpatialState(); + final ReadOnlyVector3 scale = state.getTransform().getScale(); + final double x = MathUtils.clamp(scale.getX(), _minScale.getX(), _maxScale.getX()); + final double y = MathUtils.clamp(scale.getY(), _minScale.getY(), _maxScale.getY()); + final double z = MathUtils.clamp(scale.getZ(), _minScale.getZ(), _maxScale.getZ()); + + state.getTransform().setScale(x, y, z); + } + + @Override + public void beginDrag(final InteractManager manager) {} + + @Override + public void endDrag(final InteractManager manager) {} +} diff --git a/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/filter/PlaneBoundaryFilter.java b/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/filter/PlaneBoundaryFilter.java index 00f28c5..cbff414 100644 --- a/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/filter/PlaneBoundaryFilter.java +++ b/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/filter/PlaneBoundaryFilter.java @@ -1,50 +1,50 @@ -/**
- * Copyright (c) 2008-2012 Ardor Labs, Inc.
- *
- * This file is part of Ardor3D.
- *
- * Ardor3D is free software: you can redistribute it and/or modify it
- * under the terms of its license which may be found in the accompanying
- * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
- */
-
-package com.ardor3d.extension.interact.filter;
-
-import java.util.Arrays;
-
-import com.ardor3d.extension.interact.InteractManager;
-import com.ardor3d.extension.interact.data.SpatialState;
-import com.ardor3d.math.Vector3;
-import com.ardor3d.math.type.ReadOnlyPlane;
-
-public class PlaneBoundaryFilter implements UpdateFilter {
- private final ReadOnlyPlane[] _planes;
- private final Vector3 _calcVectorA = new Vector3();
- private final Vector3 _calcVectorB = new Vector3();
-
- public PlaneBoundaryFilter(final ReadOnlyPlane... planes) {
- _planes = Arrays.copyOf(planes, planes.length);
- }
-
- @Override
- public void applyFilter(final InteractManager manager) {
- final SpatialState state = manager.getSpatialState();
- _calcVectorA.set(state.getTransform().getTranslation());
- for (final ReadOnlyPlane plane : _planes) {
- final double distance = plane.pseudoDistance(_calcVectorA);
- if (distance < 0) {
- // push us back to the plane.
- _calcVectorB.set(plane.getNormal()).multiplyLocal(-distance);
- _calcVectorA.addLocal(_calcVectorB);
- }
- }
-
- state.getTransform().setTranslation(_calcVectorA);
- }
-
- @Override
- public void beginDrag(final InteractManager manager) {}
-
- @Override
- public void endDrag(final InteractManager manager) {}
-}
+/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.interact.filter; + +import java.util.Arrays; + +import com.ardor3d.extension.interact.InteractManager; +import com.ardor3d.extension.interact.data.SpatialState; +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyPlane; + +public class PlaneBoundaryFilter implements UpdateFilter { + private final ReadOnlyPlane[] _planes; + private final Vector3 _calcVectorA = new Vector3(); + private final Vector3 _calcVectorB = new Vector3(); + + public PlaneBoundaryFilter(final ReadOnlyPlane... planes) { + _planes = Arrays.copyOf(planes, planes.length); + } + + @Override + public void applyFilter(final InteractManager manager) { + final SpatialState state = manager.getSpatialState(); + _calcVectorA.set(state.getTransform().getTranslation()); + for (final ReadOnlyPlane plane : _planes) { + final double distance = plane.pseudoDistance(_calcVectorA); + if (distance < 0) { + // push us back to the plane. + _calcVectorB.set(plane.getNormal()).multiplyLocal(-distance); + _calcVectorA.addLocal(_calcVectorB); + } + } + + state.getTransform().setTranslation(_calcVectorA); + } + + @Override + public void beginDrag(final InteractManager manager) {} + + @Override + public void endDrag(final InteractManager manager) {} +} diff --git a/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/filter/UpdateFilter.java b/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/filter/UpdateFilter.java index 5f1e336..20d1644 100644 --- a/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/filter/UpdateFilter.java +++ b/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/filter/UpdateFilter.java @@ -1,43 +1,43 @@ -/**
- * Copyright (c) 2008-2012 Ardor Labs, Inc.
- *
- * This file is part of Ardor3D.
- *
- * Ardor3D is free software: you can redistribute it and/or modify it
- * under the terms of its license which may be found in the accompanying
- * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
- */
-
-package com.ardor3d.extension.interact.filter;
-
-import com.ardor3d.extension.interact.InteractManager;
-import com.ardor3d.extension.interact.data.SpatialState;
-
-/**
- * Filter used to modify {@link SpatialState} information prior to it being applied to a Spatial by the
- * {@link InteractManager}.
- */
-public interface UpdateFilter {
-
- /**
- * Called after a successful application of mouse/key input.
- *
- * @param manager
- */
- void applyFilter(InteractManager manager);
-
- /**
- * Callback for when a control begins a drag operation.
- *
- * @param manager
- */
- void beginDrag(InteractManager manager);
-
- /**
- * Callback for when a control ends a drag operation.
- *
- * @param manager
- */
- void endDrag(InteractManager manager);
-
-}
+/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.interact.filter; + +import com.ardor3d.extension.interact.InteractManager; +import com.ardor3d.extension.interact.data.SpatialState; + +/** + * Filter used to modify {@link SpatialState} information prior to it being applied to a Spatial by the + * {@link InteractManager}. + */ +public interface UpdateFilter { + + /** + * Called after a successful application of mouse/key input. + * + * @param manager + */ + void applyFilter(InteractManager manager); + + /** + * Callback for when a control begins a drag operation. + * + * @param manager + */ + void beginDrag(InteractManager manager); + + /** + * Callback for when a control ends a drag operation. + * + * @param manager + */ + void endDrag(InteractManager manager); + +} diff --git a/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/widget/AbstractInteractWidget.java b/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/widget/AbstractInteractWidget.java index 39fbbc5..af00b28 100644 --- a/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/widget/AbstractInteractWidget.java +++ b/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/widget/AbstractInteractWidget.java @@ -1,197 +1,197 @@ -/**
- * Copyright (c) 2008-2012 Ardor Labs, Inc.
- *
- * This file is part of Ardor3D.
- *
- * Ardor3D is free software: you can redistribute it and/or modify it
- * under the terms of its license which may be found in the accompanying
- * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
- */
-
-package com.ardor3d.extension.interact.widget;
-
-import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import com.ardor3d.extension.interact.InteractManager;
-import com.ardor3d.extension.interact.filter.UpdateFilter;
-import com.ardor3d.framework.Canvas;
-import com.ardor3d.input.MouseButton;
-import com.ardor3d.input.logical.TwoInputStates;
-import com.ardor3d.intersection.PickingUtil;
-import com.ardor3d.intersection.PrimitivePickResults;
-import com.ardor3d.math.Ray3;
-import com.ardor3d.math.Vector2;
-import com.ardor3d.math.Vector3;
-import com.ardor3d.renderer.Camera;
-import com.ardor3d.renderer.Renderer;
-import com.ardor3d.scenegraph.Node;
-import com.ardor3d.util.ReadOnlyTimer;
-import com.google.common.collect.Lists;
-
-public abstract class AbstractInteractWidget {
-
- protected Node _handle;
- protected boolean _flipPickRay, _dragging = false;
- protected MouseButton _dragButton = MouseButton.LEFT;
-
- protected boolean _activeInputOnly = true;
- protected boolean _activeRenderOnly = true;
- protected boolean _activeUpdateOnly = true;
-
- protected Ray3 _calcRay = new Ray3();
- protected final Vector3 _calcVec3A = new Vector3();
- protected final Vector3 _calcVec3B = new Vector3();
- protected final Vector3 _calcVec3C = new Vector3();
- protected final Vector3 _calcVec3D = new Vector3();
- protected PrimitivePickResults _results = new PrimitivePickResults();
-
- protected InteractMatrix _interactMatrix = InteractMatrix.World;
-
- /**
- * List of filters to modify state after applying input.
- */
- protected List<UpdateFilter> _filters = Lists.newArrayList();
-
- public AbstractInteractWidget() {
- _results.setCheckDistance(true);
- }
-
- /**
- * Use the given inputstates to determine if and how to activate this widget. If the widget uses the given input,
- * inputConsumed should be set to "true" and applyFilters should be called by this method.
- *
- * @param source
- * the canvas that is our input source.
- * @param inputStates
- * the current and previous state of our input devices.
- * @param inputConsumed
- * an atomic boolean used to indicate back to the caller of this function that we have consumed the given
- * inputStates. If set to true, no other widgets will be offered this input, nor will any other scene
- * input triggers attached to the manager.
- * @param manager
- * our interact manager.
- */
- public void processInput(final Canvas source, final TwoInputStates inputStates, final AtomicBoolean inputConsumed,
- final InteractManager manager) {}
-
- protected void applyFilters(final InteractManager manager) {
- // apply any filters to our state
- for (final UpdateFilter filter : _filters) {
- filter.applyFilter(manager);
- }
- }
-
- public void beginDrag(final InteractManager manager) {
- _dragging = true;
- for (final UpdateFilter filter : _filters) {
- filter.beginDrag(manager);
- }
- }
-
- public void endDrag(final InteractManager manager) {
- _dragging = false;
- for (final UpdateFilter filter : _filters) {
- filter.endDrag(manager);
- }
- }
-
- public void update(final ReadOnlyTimer timer, final InteractManager manager) {
- _handle.updateGeometricState(timer.getTimePerFrame());
- }
-
- public void render(final Renderer renderer, final InteractManager manager) {}
-
- public void targetChanged(final InteractManager manager) {}
-
- public void targetDataUpdated(final InteractManager manager) {}
-
- public void receivedControl(final InteractManager manager) {
- if (_dragging) {
- endDrag(manager);
- }
- }
-
- public void lostControl(final InteractManager manager) {}
-
- public boolean isActiveInputOnly() {
- return _activeInputOnly;
- }
-
- public void setActiveInputOnly(final boolean activeOnly) {
- _activeInputOnly = activeOnly;
- }
-
- public boolean isActiveRenderOnly() {
- return _activeRenderOnly;
- }
-
- public void setActiveRenderOnly(final boolean activeOnly) {
- _activeRenderOnly = activeOnly;
- }
-
- public boolean isActiveUpdateOnly() {
- return _activeUpdateOnly;
- }
-
- public void setActiveUpdateOnly(final boolean activeOnly) {
- _activeUpdateOnly = activeOnly;
- }
-
- public boolean isFlipPickRay() {
- return _flipPickRay;
- }
-
- public void setFlipPickRay(final boolean flip) {
- _flipPickRay = flip;
- }
-
- public MouseButton getDragButton() {
- return _dragButton;
- }
-
- public void setDragButton(final MouseButton button) {
- _dragButton = button;
- }
-
- public Node getHandle() {
- return _handle;
- }
-
- protected Vector3 getLastPick() {
- if (_results.getNumber() > 0 && _results.getPickData(0).getIntersectionRecord().getNumberOfIntersections() > 0) {
- return _results.getPickData(0).getIntersectionRecord().getIntersectionPoint(0);
- }
- return null;
- }
-
- protected void findPick(final Vector2 mouseLoc, final Camera camera) {
- getPickRay(mouseLoc, camera);
- _results.clear();
- PickingUtil.findPick(_handle, _calcRay, _results);
- }
-
- protected void getPickRay(final Vector2 mouseLoc, final Camera camera) {
- camera.getPickRay(mouseLoc, _flipPickRay, _calcRay);
- }
-
- public void setInteractMatrix(final InteractMatrix matrix) {
- _interactMatrix = matrix;
- }
-
- public InteractMatrix getInteractMatrix() {
- return _interactMatrix;
- }
-
- public void addFilter(final UpdateFilter filter) {
- _filters.add(filter);
- }
-
- public void removeFilter(final UpdateFilter filter) {
- _filters.remove(filter);
- }
-
- public void clearFilters() {
- _filters.clear();
- }
-}
+/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.interact.widget; + +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +import com.ardor3d.extension.interact.InteractManager; +import com.ardor3d.extension.interact.filter.UpdateFilter; +import com.ardor3d.framework.Canvas; +import com.ardor3d.input.MouseButton; +import com.ardor3d.input.logical.TwoInputStates; +import com.ardor3d.intersection.PickingUtil; +import com.ardor3d.intersection.PrimitivePickResults; +import com.ardor3d.math.Ray3; +import com.ardor3d.math.Vector2; +import com.ardor3d.math.Vector3; +import com.ardor3d.renderer.Camera; +import com.ardor3d.renderer.Renderer; +import com.ardor3d.scenegraph.Node; +import com.ardor3d.util.ReadOnlyTimer; +import com.google.common.collect.Lists; + +public abstract class AbstractInteractWidget { + + protected Node _handle; + protected boolean _flipPickRay, _dragging = false; + protected MouseButton _dragButton = MouseButton.LEFT; + + protected boolean _activeInputOnly = true; + protected boolean _activeRenderOnly = true; + protected boolean _activeUpdateOnly = true; + + protected Ray3 _calcRay = new Ray3(); + protected final Vector3 _calcVec3A = new Vector3(); + protected final Vector3 _calcVec3B = new Vector3(); + protected final Vector3 _calcVec3C = new Vector3(); + protected final Vector3 _calcVec3D = new Vector3(); + protected PrimitivePickResults _results = new PrimitivePickResults(); + + protected InteractMatrix _interactMatrix = InteractMatrix.World; + + /** + * List of filters to modify state after applying input. + */ + protected List<UpdateFilter> _filters = Lists.newArrayList(); + + public AbstractInteractWidget() { + _results.setCheckDistance(true); + } + + /** + * Use the given inputstates to determine if and how to activate this widget. If the widget uses the given input, + * inputConsumed should be set to "true" and applyFilters should be called by this method. + * + * @param source + * the canvas that is our input source. + * @param inputStates + * the current and previous state of our input devices. + * @param inputConsumed + * an atomic boolean used to indicate back to the caller of this function that we have consumed the given + * inputStates. If set to true, no other widgets will be offered this input, nor will any other scene + * input triggers attached to the manager. + * @param manager + * our interact manager. + */ + public void processInput(final Canvas source, final TwoInputStates inputStates, final AtomicBoolean inputConsumed, + final InteractManager manager) {} + + protected void applyFilters(final InteractManager manager) { + // apply any filters to our state + for (final UpdateFilter filter : _filters) { + filter.applyFilter(manager); + } + } + + public void beginDrag(final InteractManager manager) { + _dragging = true; + for (final UpdateFilter filter : _filters) { + filter.beginDrag(manager); + } + } + + public void endDrag(final InteractManager manager) { + _dragging = false; + for (final UpdateFilter filter : _filters) { + filter.endDrag(manager); + } + } + + public void update(final ReadOnlyTimer timer, final InteractManager manager) { + _handle.updateGeometricState(timer.getTimePerFrame()); + } + + public void render(final Renderer renderer, final InteractManager manager) {} + + public void targetChanged(final InteractManager manager) {} + + public void targetDataUpdated(final InteractManager manager) {} + + public void receivedControl(final InteractManager manager) { + if (_dragging) { + endDrag(manager); + } + } + + public void lostControl(final InteractManager manager) {} + + public boolean isActiveInputOnly() { + return _activeInputOnly; + } + + public void setActiveInputOnly(final boolean activeOnly) { + _activeInputOnly = activeOnly; + } + + public boolean isActiveRenderOnly() { + return _activeRenderOnly; + } + + public void setActiveRenderOnly(final boolean activeOnly) { + _activeRenderOnly = activeOnly; + } + + public boolean isActiveUpdateOnly() { + return _activeUpdateOnly; + } + + public void setActiveUpdateOnly(final boolean activeOnly) { + _activeUpdateOnly = activeOnly; + } + + public boolean isFlipPickRay() { + return _flipPickRay; + } + + public void setFlipPickRay(final boolean flip) { + _flipPickRay = flip; + } + + public MouseButton getDragButton() { + return _dragButton; + } + + public void setDragButton(final MouseButton button) { + _dragButton = button; + } + + public Node getHandle() { + return _handle; + } + + protected Vector3 getLastPick() { + if (_results.getNumber() > 0 && _results.getPickData(0).getIntersectionRecord().getNumberOfIntersections() > 0) { + return _results.getPickData(0).getIntersectionRecord().getIntersectionPoint(0); + } + return null; + } + + protected void findPick(final Vector2 mouseLoc, final Camera camera) { + getPickRay(mouseLoc, camera); + _results.clear(); + PickingUtil.findPick(_handle, _calcRay, _results); + } + + protected void getPickRay(final Vector2 mouseLoc, final Camera camera) { + camera.getPickRay(mouseLoc, _flipPickRay, _calcRay); + } + + public void setInteractMatrix(final InteractMatrix matrix) { + _interactMatrix = matrix; + } + + public InteractMatrix getInteractMatrix() { + return _interactMatrix; + } + + public void addFilter(final UpdateFilter filter) { + _filters.add(filter); + } + + public void removeFilter(final UpdateFilter filter) { + _filters.remove(filter); + } + + public void clearFilters() { + _filters.clear(); + } +} diff --git a/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/widget/CompoundInteractWidget.java b/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/widget/CompoundInteractWidget.java index 2018f8c..ea41735 100644 --- a/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/widget/CompoundInteractWidget.java +++ b/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/widget/CompoundInteractWidget.java @@ -1,341 +1,341 @@ -/**
- * Copyright (c) 2008-2012 Ardor Labs, Inc.
- *
- * This file is part of Ardor3D.
- *
- * Ardor3D is free software: you can redistribute it and/or modify it
- * under the terms of its license which may be found in the accompanying
- * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
- */
-
-package com.ardor3d.extension.interact.widget;
-
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import com.ardor3d.extension.interact.InteractManager;
-import com.ardor3d.extension.interact.filter.UpdateFilter;
-import com.ardor3d.extension.interact.widget.MovePlanarWidget.MovePlane;
-import com.ardor3d.framework.Canvas;
-import com.ardor3d.image.Texture2D;
-import com.ardor3d.input.ButtonState;
-import com.ardor3d.input.MouseState;
-import com.ardor3d.input.logical.TwoInputStates;
-import com.ardor3d.math.Vector2;
-import com.ardor3d.math.type.ReadOnlyColorRGBA;
-import com.ardor3d.renderer.Camera;
-import com.ardor3d.renderer.Renderer;
-import com.ardor3d.scenegraph.Node;
-import com.ardor3d.scenegraph.Spatial;
-import com.ardor3d.util.ReadOnlyTimer;
-import com.google.common.collect.Maps;
-
-public class CompoundInteractWidget extends AbstractInteractWidget {
- private static final String MOVE_KEY = "Move";
- private static final String ROTATE_KEY = "Rotate";
- private static final String MOVE_PLANAR_KEY = "MovePlanar";
- private static final String MOVE_MULTIPLANAR_KEY = "MoveMultiPlanar";
-
- public static double MIN_SCALE = 0.000001;
-
- protected Map<String, AbstractInteractWidget> _widgets = Maps.newHashMap();
-
- protected AbstractInteractWidget _lastInputWidget = null;
-
- protected InteractMatrix _interactMatrix;
-
- public CompoundInteractWidget() {
- _handle = new Node("handleRoot");
- }
-
- @Override
- public void addFilter(final UpdateFilter filter) {
- for(final AbstractInteractWidget widget : _widgets.values()) {
- widget.addFilter(filter);
- }
- super.addFilter(filter);
- }
-
- @Override
- public void removeFilter(final UpdateFilter filter) {
- for(final AbstractInteractWidget widget : _widgets.values()) {
- widget.removeFilter(filter);
- }
- super.removeFilter(filter);
- }
-
- @Override
- public void clearFilters() {
- for(final AbstractInteractWidget widget : _widgets.values()) {
- widget.clearFilters();
- }
- super.clearFilters();
- }
-
- public CompoundInteractWidget withMoveXAxis() {
- verifyMoveWidget().withXAxis();
- return this;
- }
-
- public CompoundInteractWidget withMoveXAxis(final ReadOnlyColorRGBA color) {
- verifyMoveWidget().withXAxis(color);
- return this;
- }
-
- public CompoundInteractWidget withMoveXAxis(final ReadOnlyColorRGBA color, final double scale, final double width,
- final double lengthGap, final double tipGap) {
- verifyMoveWidget().withXAxis(color, scale, width, lengthGap, tipGap);
- return this;
- }
-
- public CompoundInteractWidget withMoveYAxis() {
- verifyMoveWidget().withYAxis();
- return this;
- }
-
- public CompoundInteractWidget withMoveYAxis(final ReadOnlyColorRGBA color) {
- verifyMoveWidget().withYAxis(color);
- return this;
- }
-
- public CompoundInteractWidget withMoveYAxis(final ReadOnlyColorRGBA color, final double scale, final double width,
- final double lengthGap, final double tipGap) {
- verifyMoveWidget().withYAxis(color, scale, width, lengthGap, tipGap);
- return this;
- }
-
- public CompoundInteractWidget withMoveZAxis() {
- verifyMoveWidget().withZAxis();
- return this;
- }
-
- public CompoundInteractWidget withMoveZAxis(final ReadOnlyColorRGBA color) {
- verifyMoveWidget().withZAxis(color);
- return this;
- }
-
- public CompoundInteractWidget withMoveZAxis(final ReadOnlyColorRGBA color, final double scale, final double width,
- final double lengthGap, final double tipGap) {
- verifyMoveWidget().withZAxis(color, scale, width, lengthGap, tipGap);
- return this;
- }
-
- public CompoundInteractWidget withRotateXAxis() {
- verifyRotateWidget().withXAxis();
- return this;
- }
-
- public CompoundInteractWidget withRotateXAxis(final ReadOnlyColorRGBA color) {
- verifyRotateWidget().withXAxis(color);
- return this;
- }
-
- public CompoundInteractWidget withRotateXAxis(final ReadOnlyColorRGBA color, final float scale, final float width) {
- verifyRotateWidget().withXAxis(color, scale, width);
- return this;
- }
-
- public CompoundInteractWidget withRotateYAxis() {
- verifyRotateWidget().withYAxis();
- return this;
- }
-
- public CompoundInteractWidget withRotateYAxis(final ReadOnlyColorRGBA color) {
- verifyRotateWidget().withYAxis(color);
- return this;
- }
-
- public CompoundInteractWidget withRotateYAxis(final ReadOnlyColorRGBA color, final float scale, final float width) {
- verifyRotateWidget().withYAxis(color, scale, width);
- return this;
- }
-
- public CompoundInteractWidget withRotateZAxis() {
- verifyRotateWidget().withZAxis();
- return this;
- }
-
- public CompoundInteractWidget withRotateZAxis(final ReadOnlyColorRGBA color) {
- verifyRotateWidget().withZAxis(color);
- return this;
- }
-
- public CompoundInteractWidget withRotateZAxis(final ReadOnlyColorRGBA color, final float scale, final float width) {
- verifyRotateWidget().withZAxis(color, scale, width);
- return this;
- }
-
- public CompoundInteractWidget withRingTexture(final Texture2D texture) {
- verifyRotateWidget().setTexture(texture);
- return this;
- }
-
- public CompoundInteractWidget withMultiPlanarHandle() {
- MoveMultiPlanarWidget widget = (MoveMultiPlanarWidget) _widgets
- .get(CompoundInteractWidget.MOVE_MULTIPLANAR_KEY);
- if (widget != null) {
- widget.getHandle().removeFromParent();
- }
-
- widget = new MoveMultiPlanarWidget();
- _widgets.put(CompoundInteractWidget.MOVE_MULTIPLANAR_KEY, widget);
- _handle.attachChild(widget.getHandle());
-
- return this;
- }
-
- public CompoundInteractWidget withMultiPlanarHandle(final double extent) {
- MoveMultiPlanarWidget widget = (MoveMultiPlanarWidget) _widgets
- .get(CompoundInteractWidget.MOVE_MULTIPLANAR_KEY);
- if (widget != null) {
- widget.getHandle().removeFromParent();
- }
-
- widget = new MoveMultiPlanarWidget(extent);
- _widgets.put(CompoundInteractWidget.MOVE_MULTIPLANAR_KEY, widget);
- _handle.attachChild(widget.getHandle());
-
- return this;
- }
-
- public CompoundInteractWidget withPlanarHandle(final MovePlane plane, final ReadOnlyColorRGBA color) {
- MovePlanarWidget widget = (MovePlanarWidget) _widgets.get(CompoundInteractWidget.MOVE_PLANAR_KEY);
- if (widget != null) {
- widget.getHandle().removeFromParent();
- }
-
- widget = new MovePlanarWidget().withPlane(plane).withDefaultHandle(.5, .25, color);
- _widgets.put(CompoundInteractWidget.MOVE_PLANAR_KEY, widget);
- _handle.attachChild(widget.getHandle());
-
- return this;
- }
-
- public CompoundInteractWidget withPlanarHandle(final MovePlane plane, final double radius, final double height,
- final ReadOnlyColorRGBA color) {
- MovePlanarWidget widget = (MovePlanarWidget) _widgets.get(CompoundInteractWidget.MOVE_PLANAR_KEY);
- if (widget != null) {
- widget.getHandle().removeFromParent();
- }
-
- widget = new MovePlanarWidget().withPlane(plane).withDefaultHandle(radius, height, color);
- _widgets.put(CompoundInteractWidget.MOVE_PLANAR_KEY, widget);
- _handle.attachChild(widget.getHandle());
-
- return this;
- }
-
- private MoveWidget verifyMoveWidget() {
- MoveWidget moveWidget = (MoveWidget) _widgets.get(CompoundInteractWidget.MOVE_KEY);
- if (moveWidget == null) {
- moveWidget = new MoveWidget();
- _widgets.put(CompoundInteractWidget.MOVE_KEY, moveWidget);
- _handle.attachChild(moveWidget.getHandle());
- }
- return moveWidget;
- }
-
- private RotateWidget verifyRotateWidget() {
- RotateWidget rotateWidget = (RotateWidget) _widgets.get(CompoundInteractWidget.ROTATE_KEY);
- if (rotateWidget == null) {
- rotateWidget = new RotateWidget();
- _widgets.put(CompoundInteractWidget.ROTATE_KEY, rotateWidget);
- _handle.attachChild(rotateWidget.getHandle());
- }
- return rotateWidget;
- }
-
- @Override
- public void targetChanged(final InteractManager manager) {
- for (final AbstractInteractWidget widget : _widgets.values()) {
- widget.targetChanged(manager);
- }
- }
-
- @Override
- public void targetDataUpdated(final InteractManager manager) {
- for (final AbstractInteractWidget widget : _widgets.values()) {
- widget.targetDataUpdated(manager);
- }
- }
-
- @Override
- public void receivedControl(final InteractManager manager) {
- for (final AbstractInteractWidget widget : _widgets.values()) {
- widget.receivedControl(manager);
- }
- }
-
- @Override
- public void render(final Renderer renderer, final InteractManager manager) {
- for (final AbstractInteractWidget widget : _widgets.values()) {
- widget.render(renderer, manager);
- }
- }
-
- @Override
- public void processInput(final Canvas source, final TwoInputStates inputStates, final AtomicBoolean inputConsumed,
- final InteractManager manager) {
- // Make sure we have something to modify
- if (manager.getSpatialTarget() == null) {
- return;
- }
- // Make sure we are dragging.
- final MouseState current = inputStates.getCurrent().getMouseState();
- final MouseState previous = inputStates.getPrevious().getMouseState();
-
- if (current.getButtonState(_dragButton) != ButtonState.DOWN) {
- if (_lastInputWidget != null) {
- _lastInputWidget.processInput(source, inputStates, inputConsumed, manager);
- _lastInputWidget = null;
- }
- return;
- }
-
- if (_lastInputWidget == null) {
- final Camera camera = source.getCanvasRenderer().getCamera();
- final Vector2 oldMouse = new Vector2(previous.getX(), previous.getY());
- findPick(oldMouse, camera);
- if (_results.getNumber() <= 0) {
- return;
- }
-
- final Spatial picked = (Spatial) _results.getPickData(0).getTarget();
- if (picked == null) {
- return;
- }
-
- for (final AbstractInteractWidget widget : _widgets.values()) {
- if (picked.hasAncestor(widget.getHandle())) {
- _lastInputWidget = widget;
- break;
- }
- }
- }
- _lastInputWidget.processInput(source, inputStates, inputConsumed, manager);
-
- // apply our filters, if any, now that we've made updates.
- applyFilters(manager);
- }
-
- @Override
- public void setInteractMatrix(final InteractMatrix matrix) {
- _interactMatrix = matrix;
- for (final AbstractInteractWidget widget : _widgets.values()) {
- widget.setInteractMatrix(matrix);
- }
- }
-
- @Override
- public InteractMatrix getInteractMatrix() {
- return _interactMatrix;
- }
-
- @Override
- public void update(final ReadOnlyTimer timer, final InteractManager manager) {
- for (final AbstractInteractWidget widget : _widgets.values()) {
- widget.update(timer, manager);
- }
- }
-
-}
+/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.interact.widget; + +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; + +import com.ardor3d.extension.interact.InteractManager; +import com.ardor3d.extension.interact.filter.UpdateFilter; +import com.ardor3d.extension.interact.widget.MovePlanarWidget.MovePlane; +import com.ardor3d.framework.Canvas; +import com.ardor3d.image.Texture2D; +import com.ardor3d.input.ButtonState; +import com.ardor3d.input.MouseState; +import com.ardor3d.input.logical.TwoInputStates; +import com.ardor3d.math.Vector2; +import com.ardor3d.math.type.ReadOnlyColorRGBA; +import com.ardor3d.renderer.Camera; +import com.ardor3d.renderer.Renderer; +import com.ardor3d.scenegraph.Node; +import com.ardor3d.scenegraph.Spatial; +import com.ardor3d.util.ReadOnlyTimer; +import com.google.common.collect.Maps; + +public class CompoundInteractWidget extends AbstractInteractWidget { + private static final String MOVE_KEY = "Move"; + private static final String ROTATE_KEY = "Rotate"; + private static final String MOVE_PLANAR_KEY = "MovePlanar"; + private static final String MOVE_MULTIPLANAR_KEY = "MoveMultiPlanar"; + + public static double MIN_SCALE = 0.000001; + + protected Map<String, AbstractInteractWidget> _widgets = Maps.newHashMap(); + + protected AbstractInteractWidget _lastInputWidget = null; + + protected InteractMatrix _interactMatrix; + + public CompoundInteractWidget() { + _handle = new Node("handleRoot"); + } + + @Override + public void addFilter(final UpdateFilter filter) { + for(final AbstractInteractWidget widget : _widgets.values()) { + widget.addFilter(filter); + } + super.addFilter(filter); + } + + @Override + public void removeFilter(final UpdateFilter filter) { + for(final AbstractInteractWidget widget : _widgets.values()) { + widget.removeFilter(filter); + } + super.removeFilter(filter); + } + + @Override + public void clearFilters() { + for(final AbstractInteractWidget widget : _widgets.values()) { + widget.clearFilters(); + } + super.clearFilters(); + } + + public CompoundInteractWidget withMoveXAxis() { + verifyMoveWidget().withXAxis(); + return this; + } + + public CompoundInteractWidget withMoveXAxis(final ReadOnlyColorRGBA color) { + verifyMoveWidget().withXAxis(color); + return this; + } + + public CompoundInteractWidget withMoveXAxis(final ReadOnlyColorRGBA color, final double scale, final double width, + final double lengthGap, final double tipGap) { + verifyMoveWidget().withXAxis(color, scale, width, lengthGap, tipGap); + return this; + } + + public CompoundInteractWidget withMoveYAxis() { + verifyMoveWidget().withYAxis(); + return this; + } + + public CompoundInteractWidget withMoveYAxis(final ReadOnlyColorRGBA color) { + verifyMoveWidget().withYAxis(color); + return this; + } + + public CompoundInteractWidget withMoveYAxis(final ReadOnlyColorRGBA color, final double scale, final double width, + final double lengthGap, final double tipGap) { + verifyMoveWidget().withYAxis(color, scale, width, lengthGap, tipGap); + return this; + } + + public CompoundInteractWidget withMoveZAxis() { + verifyMoveWidget().withZAxis(); + return this; + } + + public CompoundInteractWidget withMoveZAxis(final ReadOnlyColorRGBA color) { + verifyMoveWidget().withZAxis(color); + return this; + } + + public CompoundInteractWidget withMoveZAxis(final ReadOnlyColorRGBA color, final double scale, final double width, + final double lengthGap, final double tipGap) { + verifyMoveWidget().withZAxis(color, scale, width, lengthGap, tipGap); + return this; + } + + public CompoundInteractWidget withRotateXAxis() { + verifyRotateWidget().withXAxis(); + return this; + } + + public CompoundInteractWidget withRotateXAxis(final ReadOnlyColorRGBA color) { + verifyRotateWidget().withXAxis(color); + return this; + } + + public CompoundInteractWidget withRotateXAxis(final ReadOnlyColorRGBA color, final float scale, final float width) { + verifyRotateWidget().withXAxis(color, scale, width); + return this; + } + + public CompoundInteractWidget withRotateYAxis() { + verifyRotateWidget().withYAxis(); + return this; + } + + public CompoundInteractWidget withRotateYAxis(final ReadOnlyColorRGBA color) { + verifyRotateWidget().withYAxis(color); + return this; + } + + public CompoundInteractWidget withRotateYAxis(final ReadOnlyColorRGBA color, final float scale, final float width) { + verifyRotateWidget().withYAxis(color, scale, width); + return this; + } + + public CompoundInteractWidget withRotateZAxis() { + verifyRotateWidget().withZAxis(); + return this; + } + + public CompoundInteractWidget withRotateZAxis(final ReadOnlyColorRGBA color) { + verifyRotateWidget().withZAxis(color); + return this; + } + + public CompoundInteractWidget withRotateZAxis(final ReadOnlyColorRGBA color, final float scale, final float width) { + verifyRotateWidget().withZAxis(color, scale, width); + return this; + } + + public CompoundInteractWidget withRingTexture(final Texture2D texture) { + verifyRotateWidget().setTexture(texture); + return this; + } + + public CompoundInteractWidget withMultiPlanarHandle() { + MoveMultiPlanarWidget widget = (MoveMultiPlanarWidget) _widgets + .get(CompoundInteractWidget.MOVE_MULTIPLANAR_KEY); + if (widget != null) { + widget.getHandle().removeFromParent(); + } + + widget = new MoveMultiPlanarWidget(); + _widgets.put(CompoundInteractWidget.MOVE_MULTIPLANAR_KEY, widget); + _handle.attachChild(widget.getHandle()); + + return this; + } + + public CompoundInteractWidget withMultiPlanarHandle(final double extent) { + MoveMultiPlanarWidget widget = (MoveMultiPlanarWidget) _widgets + .get(CompoundInteractWidget.MOVE_MULTIPLANAR_KEY); + if (widget != null) { + widget.getHandle().removeFromParent(); + } + + widget = new MoveMultiPlanarWidget(extent); + _widgets.put(CompoundInteractWidget.MOVE_MULTIPLANAR_KEY, widget); + _handle.attachChild(widget.getHandle()); + + return this; + } + + public CompoundInteractWidget withPlanarHandle(final MovePlane plane, final ReadOnlyColorRGBA color) { + MovePlanarWidget widget = (MovePlanarWidget) _widgets.get(CompoundInteractWidget.MOVE_PLANAR_KEY); + if (widget != null) { + widget.getHandle().removeFromParent(); + } + + widget = new MovePlanarWidget().withPlane(plane).withDefaultHandle(.5, .25, color); + _widgets.put(CompoundInteractWidget.MOVE_PLANAR_KEY, widget); + _handle.attachChild(widget.getHandle()); + + return this; + } + + public CompoundInteractWidget withPlanarHandle(final MovePlane plane, final double radius, final double height, + final ReadOnlyColorRGBA color) { + MovePlanarWidget widget = (MovePlanarWidget) _widgets.get(CompoundInteractWidget.MOVE_PLANAR_KEY); + if (widget != null) { + widget.getHandle().removeFromParent(); + } + + widget = new MovePlanarWidget().withPlane(plane).withDefaultHandle(radius, height, color); + _widgets.put(CompoundInteractWidget.MOVE_PLANAR_KEY, widget); + _handle.attachChild(widget.getHandle()); + + return this; + } + + private MoveWidget verifyMoveWidget() { + MoveWidget moveWidget = (MoveWidget) _widgets.get(CompoundInteractWidget.MOVE_KEY); + if (moveWidget == null) { + moveWidget = new MoveWidget(); + _widgets.put(CompoundInteractWidget.MOVE_KEY, moveWidget); + _handle.attachChild(moveWidget.getHandle()); + } + return moveWidget; + } + + private RotateWidget verifyRotateWidget() { + RotateWidget rotateWidget = (RotateWidget) _widgets.get(CompoundInteractWidget.ROTATE_KEY); + if (rotateWidget == null) { + rotateWidget = new RotateWidget(); + _widgets.put(CompoundInteractWidget.ROTATE_KEY, rotateWidget); + _handle.attachChild(rotateWidget.getHandle()); + } + return rotateWidget; + } + + @Override + public void targetChanged(final InteractManager manager) { + for (final AbstractInteractWidget widget : _widgets.values()) { + widget.targetChanged(manager); + } + } + + @Override + public void targetDataUpdated(final InteractManager manager) { + for (final AbstractInteractWidget widget : _widgets.values()) { + widget.targetDataUpdated(manager); + } + } + + @Override + public void receivedControl(final InteractManager manager) { + for (final AbstractInteractWidget widget : _widgets.values()) { + widget.receivedControl(manager); + } + } + + @Override + public void render(final Renderer renderer, final InteractManager manager) { + for (final AbstractInteractWidget widget : _widgets.values()) { + widget.render(renderer, manager); + } + } + + @Override + public void processInput(final Canvas source, final TwoInputStates inputStates, final AtomicBoolean inputConsumed, + final InteractManager manager) { + // Make sure we have something to modify + if (manager.getSpatialTarget() == null) { + return; + } + // Make sure we are dragging. + final MouseState current = inputStates.getCurrent().getMouseState(); + final MouseState previous = inputStates.getPrevious().getMouseState(); + + if (current.getButtonState(_dragButton) != ButtonState.DOWN) { + if (_lastInputWidget != null) { + _lastInputWidget.processInput(source, inputStates, inputConsumed, manager); + _lastInputWidget = null; + } + return; + } + + if (_lastInputWidget == null) { + final Camera camera = source.getCanvasRenderer().getCamera(); + final Vector2 oldMouse = new Vector2(previous.getX(), previous.getY()); + findPick(oldMouse, camera); + if (_results.getNumber() <= 0) { + return; + } + + final Spatial picked = (Spatial) _results.getPickData(0).getTarget(); + if (picked == null) { + return; + } + + for (final AbstractInteractWidget widget : _widgets.values()) { + if (picked.hasAncestor(widget.getHandle())) { + _lastInputWidget = widget; + break; + } + } + } + _lastInputWidget.processInput(source, inputStates, inputConsumed, manager); + + // apply our filters, if any, now that we've made updates. + applyFilters(manager); + } + + @Override + public void setInteractMatrix(final InteractMatrix matrix) { + _interactMatrix = matrix; + for (final AbstractInteractWidget widget : _widgets.values()) { + widget.setInteractMatrix(matrix); + } + } + + @Override + public InteractMatrix getInteractMatrix() { + return _interactMatrix; + } + + @Override + public void update(final ReadOnlyTimer timer, final InteractManager manager) { + for (final AbstractInteractWidget widget : _widgets.values()) { + widget.update(timer, manager); + } + } + +} diff --git a/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/widget/InteractMatrix.java b/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/widget/InteractMatrix.java index 2574770..944bd9d 100644 --- a/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/widget/InteractMatrix.java +++ b/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/widget/InteractMatrix.java @@ -1,15 +1,15 @@ -/**
- * Copyright (c) 2008-2012 Ardor Labs, Inc.
- *
- * This file is part of Ardor3D.
- *
- * Ardor3D is free software: you can redistribute it and/or modify it
- * under the terms of its license which may be found in the accompanying
- * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
- */
-
-package com.ardor3d.extension.interact.widget;
-
-public enum InteractMatrix {
- World, Local;
-}
+/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.interact.widget; + +public enum InteractMatrix { + World, Local; +} diff --git a/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/widget/InteractRing.java b/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/widget/InteractRing.java index be5a53f..983017a 100644 --- a/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/widget/InteractRing.java +++ b/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/widget/InteractRing.java @@ -1,188 +1,188 @@ -/**
- * Copyright (c) 2008-2012 Ardor Labs, Inc.
- *
- * This file is part of Ardor3D.
- *
- * Ardor3D is free software: you can redistribute it and/or modify it
- * under the terms of its license which may be found in the accompanying
- * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
- */
-
-package com.ardor3d.extension.interact.widget;
-
-import java.io.IOException;
-import java.nio.FloatBuffer;
-
-import com.ardor3d.image.Texture2D;
-import com.ardor3d.math.MathUtils;
-import com.ardor3d.renderer.IndexMode;
-import com.ardor3d.renderer.state.RenderState;
-import com.ardor3d.renderer.state.TextureState;
-import com.ardor3d.scenegraph.FloatBufferData;
-import com.ardor3d.scenegraph.Mesh;
-import com.ardor3d.util.export.InputCapsule;
-import com.ardor3d.util.export.OutputCapsule;
-import com.ardor3d.util.geom.BufferUtils;
-
-/**
- * Textured ring geometry, intended for use as a rotational handle.
- */
-public class InteractRing extends Mesh {
- protected float _innerRadius, _outerRadius;
- protected int _tessRings = 2;
- protected int _tessSteps = 32;
- protected float _texMul = 4.0f;
- protected float _concaveValue = 0;
-
- public InteractRing() {}
-
- public InteractRing(final String name, final int tessRings, final int tessSteps, final float radius,
- final float width) {
- this(name, tessRings, tessSteps, radius, width, 0);
- }
-
- public InteractRing(final String name, final int tessRings, final int tessSteps, final float radius,
- final float width, final float concaveValue) {
- super(name);
- _tessRings = tessRings;
- _tessSteps = tessSteps;
- _concaveValue = concaveValue;
- setRadius(radius, width);
- }
-
- public void setRadius(final float radius, final float width) {
- _innerRadius = radius;
- _outerRadius = radius + width;
- updateGeometry();
- }
-
- /**
- * @param vMult
- * new multiplier for v direction of texture coords (around ring)
- */
- public void setTextureMultiplier(final float vMult) {
- _texMul = vMult;
- updateGeometry();
- }
-
- public void setConcaveValue(final float value) {
- _concaveValue = value;
- updateGeometry();
- }
-
- /**
- * Convenience method for setting texture without managing TextureState.
- *
- * @param texture
- * the new texture to set on unit 0.
- */
- public void setTexture(final Texture2D texture) {
- TextureState ts = (TextureState) getLocalRenderState(RenderState.StateType.Texture);
- if (ts == null) {
- ts = new TextureState();
- ts.setEnabled(true);
- setRenderState(ts);
- }
- ts.setTexture(texture, 0);
- }
-
- /**
- *
- */
- public void updateGeometry() {
- final int numPairs = _tessSteps + 1;
- final int totalVerts = _tessRings * numPairs * 2;
-
- FloatBuffer crdBuf = getMeshData().getVertexBuffer();
- if (crdBuf == null || totalVerts != crdBuf.limit() / 3) { // allocate new buffers
- getMeshData().setVertexBuffer(BufferUtils.createFloatBuffer(totalVerts * 3));
- getMeshData().setNormalBuffer(BufferUtils.createFloatBuffer(totalVerts * 3));
- getMeshData().setTextureCoords(new FloatBufferData(BufferUtils.createFloatBuffer(totalVerts * 2), 2), 0);
- crdBuf = getMeshData().getVertexBuffer();
- }
- final FloatBuffer nrmBuf = getMeshData().getNormalBuffer();
- final FloatBufferData tc = getMeshData().getTextureCoords(0);
- final FloatBuffer txcBuf = tc.getBuffer();
- calculateVertexData(_tessRings, numPairs, totalVerts, crdBuf, nrmBuf, txcBuf);
-
- updateModelBound();
- }
-
- protected void normalize(final int i, final float[] nrm) {
- final float length = (float) MathUtils
- .sqrt(nrm[i] * nrm[i] + nrm[i + 1] * nrm[i + 1] + nrm[i + 2] * nrm[i + 2]);
- nrm[i] /= length;
- nrm[i + 1] /= length;
- nrm[i + 2] /= length;
- }
-
- protected void calculateVertexData(final int numStrips, final int numPairs, final int totalVerts,
- final FloatBuffer crdBuf, final FloatBuffer nrmBuf, final FloatBuffer txcBuf) {
- // we are generating strips
- getMeshData().setIndexMode(IndexMode.TriangleStrip);
-
- final float astep = (float) (Math.PI * 2 / _tessSteps);
- final float sstep = 1.0f / numStrips;
- final float rrange = _outerRadius - _innerRadius;
- final float rstep = rrange / numStrips;
- float xa, ya;
- float r0, r1;
- float nadd0, nadd1;
- float tc;
- final float up = 1;
- final float[] nrm = new float[6];
- crdBuf.rewind();
- nrmBuf.rewind();
- txcBuf.rewind();
- for (int s = 0; s < numStrips; s++) {
- nadd0 = _concaveValue * (s + 0 - numStrips * 0.5f) / numStrips;
- nadd1 = _concaveValue * (s + 1 - numStrips * 0.5f) / numStrips;
- for (int a = 0; a < numPairs; a++) {
- xa = (float) Math.cos(a * astep);
- ya = (float) Math.sin(a * astep);
- r0 = _innerRadius + (s + 0) * rstep;
- r1 = _innerRadius + (s + 1) * rstep;
-
- crdBuf.put(xa * r0).put(ya * r0).put(0);
- crdBuf.put(xa * r1).put(ya * r1).put(0);
-
- nrm[0] = nadd0 * xa;
- nrm[1] = nadd0 * ya;
- nrm[2] = up;
- nrm[3] = nadd1 * xa;
- nrm[4] = nadd1 * ya;
- nrm[5] = up;
- normalize(0, nrm);
- normalize(3, nrm);
- nrmBuf.put(nrm[0]).put(nrm[1]).put(nrm[2]);
- nrmBuf.put(nrm[3]).put(nrm[4]).put(nrm[5]);
-
- tc = a * _texMul / _tessSteps;
- txcBuf.put((s + 0) * sstep).put(tc);
- txcBuf.put((s + 1) * sstep).put(tc);
- }
- }
- }
-
- @Override
- public void write(final OutputCapsule capsule) throws IOException {
- super.write(capsule);
- capsule.write(_innerRadius, "innerRadius", 0f);
- capsule.write(_outerRadius, "outerRadius", 0f);
- capsule.write(_tessRings, "tessRings", 2);
- capsule.write(_tessSteps, "tessSteps", 16);
- capsule.write(_texMul, "texMul", 1f);
- capsule.write(_concaveValue, "concaveValue", 0f);
- }
-
- @Override
- public void read(final InputCapsule capsule) throws IOException {
- super.read(capsule);
- _innerRadius = capsule.readFloat("innerRadius", 0f);
- _outerRadius = capsule.readFloat("outerRadius", 0f);
- _tessRings = capsule.readInt("tessRings", 2);
- _tessSteps = capsule.readInt("tessSteps", 16);
- _texMul = capsule.readFloat("texMul", 1f);
- _concaveValue = capsule.readFloat("concaveValue", 0f);
- }
-}
+/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.interact.widget; + +import java.io.IOException; +import java.nio.FloatBuffer; + +import com.ardor3d.image.Texture2D; +import com.ardor3d.math.MathUtils; +import com.ardor3d.renderer.IndexMode; +import com.ardor3d.renderer.state.RenderState; +import com.ardor3d.renderer.state.TextureState; +import com.ardor3d.scenegraph.FloatBufferData; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.geom.BufferUtils; + +/** + * Textured ring geometry, intended for use as a rotational handle. + */ +public class InteractRing extends Mesh { + protected float _innerRadius, _outerRadius; + protected int _tessRings = 2; + protected int _tessSteps = 32; + protected float _texMul = 4.0f; + protected float _concaveValue = 0; + + public InteractRing() {} + + public InteractRing(final String name, final int tessRings, final int tessSteps, final float radius, + final float width) { + this(name, tessRings, tessSteps, radius, width, 0); + } + + public InteractRing(final String name, final int tessRings, final int tessSteps, final float radius, + final float width, final float concaveValue) { + super(name); + _tessRings = tessRings; + _tessSteps = tessSteps; + _concaveValue = concaveValue; + setRadius(radius, width); + } + + public void setRadius(final float radius, final float width) { + _innerRadius = radius; + _outerRadius = radius + width; + updateGeometry(); + } + + /** + * @param vMult + * new multiplier for v direction of texture coords (around ring) + */ + public void setTextureMultiplier(final float vMult) { + _texMul = vMult; + updateGeometry(); + } + + public void setConcaveValue(final float value) { + _concaveValue = value; + updateGeometry(); + } + + /** + * Convenience method for setting texture without managing TextureState. + * + * @param texture + * the new texture to set on unit 0. + */ + public void setTexture(final Texture2D texture) { + TextureState ts = (TextureState) getLocalRenderState(RenderState.StateType.Texture); + if (ts == null) { + ts = new TextureState(); + ts.setEnabled(true); + setRenderState(ts); + } + ts.setTexture(texture, 0); + } + + /** + * + */ + public void updateGeometry() { + final int numPairs = _tessSteps + 1; + final int totalVerts = _tessRings * numPairs * 2; + + FloatBuffer crdBuf = getMeshData().getVertexBuffer(); + if (crdBuf == null || totalVerts != crdBuf.limit() / 3) { // allocate new buffers + getMeshData().setVertexBuffer(BufferUtils.createFloatBuffer(totalVerts * 3)); + getMeshData().setNormalBuffer(BufferUtils.createFloatBuffer(totalVerts * 3)); + getMeshData().setTextureCoords(new FloatBufferData(BufferUtils.createFloatBuffer(totalVerts * 2), 2), 0); + crdBuf = getMeshData().getVertexBuffer(); + } + final FloatBuffer nrmBuf = getMeshData().getNormalBuffer(); + final FloatBufferData tc = getMeshData().getTextureCoords(0); + final FloatBuffer txcBuf = tc.getBuffer(); + calculateVertexData(_tessRings, numPairs, totalVerts, crdBuf, nrmBuf, txcBuf); + + updateModelBound(); + } + + protected void normalize(final int i, final float[] nrm) { + final float length = (float) MathUtils + .sqrt(nrm[i] * nrm[i] + nrm[i + 1] * nrm[i + 1] + nrm[i + 2] * nrm[i + 2]); + nrm[i] /= length; + nrm[i + 1] /= length; + nrm[i + 2] /= length; + } + + protected void calculateVertexData(final int numStrips, final int numPairs, final int totalVerts, + final FloatBuffer crdBuf, final FloatBuffer nrmBuf, final FloatBuffer txcBuf) { + // we are generating strips + getMeshData().setIndexMode(IndexMode.TriangleStrip); + + final float astep = (float) (Math.PI * 2 / _tessSteps); + final float sstep = 1.0f / numStrips; + final float rrange = _outerRadius - _innerRadius; + final float rstep = rrange / numStrips; + float xa, ya; + float r0, r1; + float nadd0, nadd1; + float tc; + final float up = 1; + final float[] nrm = new float[6]; + crdBuf.rewind(); + nrmBuf.rewind(); + txcBuf.rewind(); + for (int s = 0; s < numStrips; s++) { + nadd0 = _concaveValue * (s + 0 - numStrips * 0.5f) / numStrips; + nadd1 = _concaveValue * (s + 1 - numStrips * 0.5f) / numStrips; + for (int a = 0; a < numPairs; a++) { + xa = (float) Math.cos(a * astep); + ya = (float) Math.sin(a * astep); + r0 = _innerRadius + (s + 0) * rstep; + r1 = _innerRadius + (s + 1) * rstep; + + crdBuf.put(xa * r0).put(ya * r0).put(0); + crdBuf.put(xa * r1).put(ya * r1).put(0); + + nrm[0] = nadd0 * xa; + nrm[1] = nadd0 * ya; + nrm[2] = up; + nrm[3] = nadd1 * xa; + nrm[4] = nadd1 * ya; + nrm[5] = up; + normalize(0, nrm); + normalize(3, nrm); + nrmBuf.put(nrm[0]).put(nrm[1]).put(nrm[2]); + nrmBuf.put(nrm[3]).put(nrm[4]).put(nrm[5]); + + tc = a * _texMul / _tessSteps; + txcBuf.put((s + 0) * sstep).put(tc); + txcBuf.put((s + 1) * sstep).put(tc); + } + } + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_innerRadius, "innerRadius", 0f); + capsule.write(_outerRadius, "outerRadius", 0f); + capsule.write(_tessRings, "tessRings", 2); + capsule.write(_tessSteps, "tessSteps", 16); + capsule.write(_texMul, "texMul", 1f); + capsule.write(_concaveValue, "concaveValue", 0f); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _innerRadius = capsule.readFloat("innerRadius", 0f); + _outerRadius = capsule.readFloat("outerRadius", 0f); + _tessRings = capsule.readInt("tessRings", 2); + _tessSteps = capsule.readInt("tessSteps", 16); + _texMul = capsule.readFloat("texMul", 1f); + _concaveValue = capsule.readFloat("concaveValue", 0f); + } +} diff --git a/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/widget/MoveMultiPlanarWidget.java b/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/widget/MoveMultiPlanarWidget.java index 7cf7dd4..019c3ed 100644 --- a/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/widget/MoveMultiPlanarWidget.java +++ b/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/widget/MoveMultiPlanarWidget.java @@ -1,230 +1,230 @@ -/**
- * Copyright (c) 2008-2012 Ardor Labs, Inc.
- *
- * This file is part of Ardor3D.
- *
- * Ardor3D is free software: you can redistribute it and/or modify it
- * under the terms of its license which may be found in the accompanying
- * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
- */
-
-package com.ardor3d.extension.interact.widget;
-
-import java.nio.FloatBuffer;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import com.ardor3d.extension.interact.InteractManager;
-import com.ardor3d.framework.Canvas;
-import com.ardor3d.input.ButtonState;
-import com.ardor3d.input.MouseState;
-import com.ardor3d.input.logical.TwoInputStates;
-import com.ardor3d.intersection.PickData;
-import com.ardor3d.intersection.PrimitiveKey;
-import com.ardor3d.math.ColorRGBA;
-import com.ardor3d.math.Matrix3;
-import com.ardor3d.math.Plane;
-import com.ardor3d.math.Transform;
-import com.ardor3d.math.Vector2;
-import com.ardor3d.math.Vector3;
-import com.ardor3d.renderer.Camera;
-import com.ardor3d.renderer.Renderer;
-import com.ardor3d.renderer.queue.RenderBucketType;
-import com.ardor3d.renderer.state.BlendState;
-import com.ardor3d.renderer.state.MaterialState;
-import com.ardor3d.renderer.state.MaterialState.ColorMaterial;
-import com.ardor3d.renderer.state.ShadingState;
-import com.ardor3d.renderer.state.ShadingState.ShadingMode;
-import com.ardor3d.renderer.state.ZBufferState;
-import com.ardor3d.renderer.state.ZBufferState.TestFunction;
-import com.ardor3d.scenegraph.Mesh;
-import com.ardor3d.scenegraph.Node;
-import com.ardor3d.scenegraph.Spatial;
-import com.ardor3d.scenegraph.shape.Box;
-import com.ardor3d.util.geom.BufferUtils;
-
-public class MoveMultiPlanarWidget extends AbstractInteractWidget {
- public static double MIN_SCALE = 0.000001;
-
- public MoveMultiPlanarWidget() {
- this(0.5);
- }
-
- public MoveMultiPlanarWidget(final double extent) {
- _handle = new Node("moveHandle");
-
- final BlendState blend = new BlendState();
- blend.setBlendEnabled(true);
- _handle.setRenderState(blend);
-
- final ZBufferState zstate = new ZBufferState();
- zstate.setFunction(TestFunction.LessThanOrEqualTo);
- _handle.setRenderState(zstate);
-
- _handle.getSceneHints().setRenderBucketType(RenderBucketType.Transparent);
- _handle.updateGeometricState(0);
-
- createDefaultHandle(extent);
- }
-
- protected void createDefaultHandle(final double extent) {
- final Box grip = new Box("grip", Vector3.ZERO, extent, extent, extent);
- grip.updateModelBound();
- _handle.attachChild(grip);
-
- // setup some colors, just at the corner of the primitives since we will use flat shading.
- grip.setSolidColor(ColorRGBA.WHITE);
- final FloatBuffer colors = grip.getMeshData().getColorBuffer();
- BufferUtils.setInBuffer(ColorRGBA.MAGENTA, colors, 0);
- BufferUtils.setInBuffer(ColorRGBA.CYAN, colors, 4);
- BufferUtils.setInBuffer(ColorRGBA.MAGENTA, colors, 8);
- BufferUtils.setInBuffer(ColorRGBA.CYAN, colors, 12);
- BufferUtils.setInBuffer(ColorRGBA.YELLOW, colors, 16);
- BufferUtils.setInBuffer(ColorRGBA.YELLOW, colors, 20);
-
- // set flat shading
- final ShadingState shade = new ShadingState();
- shade.setShadingMode(ShadingMode.Flat);
- grip.setRenderState(shade);
-
- // setup a material state to use the colors from the vertices.
- final MaterialState material = new MaterialState();
- material.setColorMaterial(ColorMaterial.Diffuse);
- grip.setRenderState(material);
- }
-
- @Override
- public void targetChanged(final InteractManager manager) {
- if (_dragging) {
- endDrag(manager);
- }
- final Spatial target = manager.getSpatialTarget();
- if (target != null) {
- _handle.setScale(Math.max(MoveMultiPlanarWidget.MIN_SCALE, target.getWorldBound().getRadius()
- + target.getWorldTranslation().subtract(target.getWorldBound().getCenter(), _calcVec3A).length()));
- }
- targetDataUpdated(manager);
- }
-
- @Override
- public void targetDataUpdated(final InteractManager manager) {
- final Spatial target = manager.getSpatialTarget();
- if (target == null) {
- _handle.setScale(1.0);
- _handle.setRotation(Matrix3.IDENTITY);
- } else {
- // update scale of widget using bounding radius
- target.updateGeometricState(0);
-
- // update arrow rotations from target
- if (_interactMatrix == InteractMatrix.Local) {
- _handle.setRotation(target.getWorldRotation());
- } else {
- _handle.setRotation(Matrix3.IDENTITY);
- }
- }
- }
-
- @Override
- public void render(final Renderer renderer, final InteractManager manager) {
- final Spatial spat = manager.getSpatialTarget();
- if (spat == null) {
- return;
- }
-
- _handle.setTranslation(spat.getWorldTranslation());
- _handle.updateGeometricState(0);
-
- renderer.draw(_handle);
- }
-
- @Override
- public void processInput(final Canvas source, final TwoInputStates inputStates, final AtomicBoolean inputConsumed,
- final InteractManager manager) {
- // Make sure we have something to modify
- if (manager.getSpatialTarget() == null) {
- return;
- }
-
- // Make sure we are dragging.
- final MouseState current = inputStates.getCurrent().getMouseState();
- final MouseState previous = inputStates.getPrevious().getMouseState();
-
- if (current.getButtonState(_dragButton) != ButtonState.DOWN) {
- if (_dragging) {
- endDrag(manager);
- }
- return;
- }
- // if we're already dragging, make sure we only act on drags that started with a positive pick.
- else if (!current.getButtonsPressedSince(previous).contains(_dragButton) && !_dragging) {
- return;
- }
-
- final Camera camera = source.getCanvasRenderer().getCamera();
- final Vector2 oldMouse = new Vector2(previous.getX(), previous.getY());
- // Make sure we are dragging over the handle
- if (!_dragging) {
- findPick(oldMouse, camera);
- final Vector3 lastPick = getLastPick();
- if (lastPick == null) {
- return;
- } else {
- beginDrag(manager);
- }
- }
-
- // we've established that our mouse is being held down, and started over our arrow. So consume.
- inputConsumed.set(true);
-
- // check if we've moved at all
- if (current == previous || current.getDx() == 0 && current.getDy() == 0) {
- return;
- }
-
- // act on drag
- final PickData pickData = _results.getPickData(0);
- final Spatial picked = (Spatial) pickData.getTarget();
- if (picked instanceof Mesh && pickData.getIntersectionRecord().getNumberOfIntersections() > 0) {
- final PrimitiveKey key = pickData.getIntersectionRecord().getIntersectionPrimitive(0);
- ((Mesh) picked).getMeshData().getPrimitiveVertices(key.getPrimitiveIndex(), key.getSection(),
- new Vector3[] { _calcVec3A, _calcVec3B, _calcVec3C });
- picked.localToWorld(_calcVec3A, _calcVec3A);
- picked.localToWorld(_calcVec3B, _calcVec3B);
- picked.localToWorld(_calcVec3C, _calcVec3C);
- final Vector3 loc = getNewOffset(oldMouse, current, camera, manager);
- final Transform transform = manager.getSpatialState().getTransform();
- transform.setTranslation(loc.addLocal(transform.getTranslation()));
-
- // apply our filters, if any, now that we've made updates.
- applyFilters(manager);
- }
- }
-
- protected Vector3 getNewOffset(final Vector2 oldMouse, final MouseState current, final Camera camera,
- final InteractManager manager) {
-
- // make plane object
- final Plane pickPlane = new Plane().setPlanePoints(_calcVec3A, _calcVec3B, _calcVec3C);
-
- // find out where we were hitting the plane before
- getPickRay(oldMouse, camera);
- if (!_calcRay.intersectsPlane(pickPlane, _calcVec3A)) {
- return _calcVec3A.zero();
- }
-
- // find out where we are hitting the plane now
- getPickRay(new Vector2(current.getX(), current.getY()), camera);
- if (!_calcRay.intersectsPlane(pickPlane, _calcVec3B)) {
- return _calcVec3A.zero();
- }
-
- // convert to target coord space
- final Node parent = manager.getSpatialTarget().getParent();
- if (parent != null) {
- parent.getWorldTransform().applyInverse(_calcVec3A);
- parent.getWorldTransform().applyInverse(_calcVec3B);
- }
-
- return _calcVec3B.subtractLocal(_calcVec3A);
- }
-}
+/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.interact.widget; + +import java.nio.FloatBuffer; +import java.util.concurrent.atomic.AtomicBoolean; + +import com.ardor3d.extension.interact.InteractManager; +import com.ardor3d.framework.Canvas; +import com.ardor3d.input.ButtonState; +import com.ardor3d.input.MouseState; +import com.ardor3d.input.logical.TwoInputStates; +import com.ardor3d.intersection.PickData; +import com.ardor3d.intersection.PrimitiveKey; +import com.ardor3d.math.ColorRGBA; +import com.ardor3d.math.Matrix3; +import com.ardor3d.math.Plane; +import com.ardor3d.math.Transform; +import com.ardor3d.math.Vector2; +import com.ardor3d.math.Vector3; +import com.ardor3d.renderer.Camera; +import com.ardor3d.renderer.Renderer; +import com.ardor3d.renderer.queue.RenderBucketType; +import com.ardor3d.renderer.state.BlendState; +import com.ardor3d.renderer.state.MaterialState; +import com.ardor3d.renderer.state.MaterialState.ColorMaterial; +import com.ardor3d.renderer.state.ShadingState; +import com.ardor3d.renderer.state.ShadingState.ShadingMode; +import com.ardor3d.renderer.state.ZBufferState; +import com.ardor3d.renderer.state.ZBufferState.TestFunction; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.scenegraph.Node; +import com.ardor3d.scenegraph.Spatial; +import com.ardor3d.scenegraph.shape.Box; +import com.ardor3d.util.geom.BufferUtils; + +public class MoveMultiPlanarWidget extends AbstractInteractWidget { + public static double MIN_SCALE = 0.000001; + + public MoveMultiPlanarWidget() { + this(0.5); + } + + public MoveMultiPlanarWidget(final double extent) { + _handle = new Node("moveHandle"); + + final BlendState blend = new BlendState(); + blend.setBlendEnabled(true); + _handle.setRenderState(blend); + + final ZBufferState zstate = new ZBufferState(); + zstate.setFunction(TestFunction.LessThanOrEqualTo); + _handle.setRenderState(zstate); + + _handle.getSceneHints().setRenderBucketType(RenderBucketType.Transparent); + _handle.updateGeometricState(0); + + createDefaultHandle(extent); + } + + protected void createDefaultHandle(final double extent) { + final Box grip = new Box("grip", Vector3.ZERO, extent, extent, extent); + grip.updateModelBound(); + _handle.attachChild(grip); + + // setup some colors, just at the corner of the primitives since we will use flat shading. + grip.setSolidColor(ColorRGBA.WHITE); + final FloatBuffer colors = grip.getMeshData().getColorBuffer(); + BufferUtils.setInBuffer(ColorRGBA.MAGENTA, colors, 0); + BufferUtils.setInBuffer(ColorRGBA.CYAN, colors, 4); + BufferUtils.setInBuffer(ColorRGBA.MAGENTA, colors, 8); + BufferUtils.setInBuffer(ColorRGBA.CYAN, colors, 12); + BufferUtils.setInBuffer(ColorRGBA.YELLOW, colors, 16); + BufferUtils.setInBuffer(ColorRGBA.YELLOW, colors, 20); + + // set flat shading + final ShadingState shade = new ShadingState(); + shade.setShadingMode(ShadingMode.Flat); + grip.setRenderState(shade); + + // setup a material state to use the colors from the vertices. + final MaterialState material = new MaterialState(); + material.setColorMaterial(ColorMaterial.Diffuse); + grip.setRenderState(material); + } + + @Override + public void targetChanged(final InteractManager manager) { + if (_dragging) { + endDrag(manager); + } + final Spatial target = manager.getSpatialTarget(); + if (target != null) { + _handle.setScale(Math.max(MoveMultiPlanarWidget.MIN_SCALE, target.getWorldBound().getRadius() + + target.getWorldTranslation().subtract(target.getWorldBound().getCenter(), _calcVec3A).length())); + } + targetDataUpdated(manager); + } + + @Override + public void targetDataUpdated(final InteractManager manager) { + final Spatial target = manager.getSpatialTarget(); + if (target == null) { + _handle.setScale(1.0); + _handle.setRotation(Matrix3.IDENTITY); + } else { + // update scale of widget using bounding radius + target.updateGeometricState(0); + + // update arrow rotations from target + if (_interactMatrix == InteractMatrix.Local) { + _handle.setRotation(target.getWorldRotation()); + } else { + _handle.setRotation(Matrix3.IDENTITY); + } + } + } + + @Override + public void render(final Renderer renderer, final InteractManager manager) { + final Spatial spat = manager.getSpatialTarget(); + if (spat == null) { + return; + } + + _handle.setTranslation(spat.getWorldTranslation()); + _handle.updateGeometricState(0); + + renderer.draw(_handle); + } + + @Override + public void processInput(final Canvas source, final TwoInputStates inputStates, final AtomicBoolean inputConsumed, + final InteractManager manager) { + // Make sure we have something to modify + if (manager.getSpatialTarget() == null) { + return; + } + + // Make sure we are dragging. + final MouseState current = inputStates.getCurrent().getMouseState(); + final MouseState previous = inputStates.getPrevious().getMouseState(); + + if (current.getButtonState(_dragButton) != ButtonState.DOWN) { + if (_dragging) { + endDrag(manager); + } + return; + } + // if we're already dragging, make sure we only act on drags that started with a positive pick. + else if (!current.getButtonsPressedSince(previous).contains(_dragButton) && !_dragging) { + return; + } + + final Camera camera = source.getCanvasRenderer().getCamera(); + final Vector2 oldMouse = new Vector2(previous.getX(), previous.getY()); + // Make sure we are dragging over the handle + if (!_dragging) { + findPick(oldMouse, camera); + final Vector3 lastPick = getLastPick(); + if (lastPick == null) { + return; + } else { + beginDrag(manager); + } + } + + // we've established that our mouse is being held down, and started over our arrow. So consume. + inputConsumed.set(true); + + // check if we've moved at all + if (current == previous || current.getDx() == 0 && current.getDy() == 0) { + return; + } + + // act on drag + final PickData pickData = _results.getPickData(0); + final Spatial picked = (Spatial) pickData.getTarget(); + if (picked instanceof Mesh && pickData.getIntersectionRecord().getNumberOfIntersections() > 0) { + final PrimitiveKey key = pickData.getIntersectionRecord().getIntersectionPrimitive(0); + ((Mesh) picked).getMeshData().getPrimitiveVertices(key.getPrimitiveIndex(), key.getSection(), + new Vector3[] { _calcVec3A, _calcVec3B, _calcVec3C }); + picked.localToWorld(_calcVec3A, _calcVec3A); + picked.localToWorld(_calcVec3B, _calcVec3B); + picked.localToWorld(_calcVec3C, _calcVec3C); + final Vector3 loc = getNewOffset(oldMouse, current, camera, manager); + final Transform transform = manager.getSpatialState().getTransform(); + transform.setTranslation(loc.addLocal(transform.getTranslation())); + + // apply our filters, if any, now that we've made updates. + applyFilters(manager); + } + } + + protected Vector3 getNewOffset(final Vector2 oldMouse, final MouseState current, final Camera camera, + final InteractManager manager) { + + // make plane object + final Plane pickPlane = new Plane().setPlanePoints(_calcVec3A, _calcVec3B, _calcVec3C); + + // find out where we were hitting the plane before + getPickRay(oldMouse, camera); + if (!_calcRay.intersectsPlane(pickPlane, _calcVec3A)) { + return _calcVec3A.zero(); + } + + // find out where we are hitting the plane now + getPickRay(new Vector2(current.getX(), current.getY()), camera); + if (!_calcRay.intersectsPlane(pickPlane, _calcVec3B)) { + return _calcVec3A.zero(); + } + + // convert to target coord space + final Node parent = manager.getSpatialTarget().getParent(); + if (parent != null) { + parent.getWorldTransform().applyInverse(_calcVec3A); + parent.getWorldTransform().applyInverse(_calcVec3B); + } + + return _calcVec3B.subtractLocal(_calcVec3A); + } +} diff --git a/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/widget/MovePlanarWidget.java b/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/widget/MovePlanarWidget.java index 7389f7f..4b551bc 100644 --- a/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/widget/MovePlanarWidget.java +++ b/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/widget/MovePlanarWidget.java @@ -1,240 +1,240 @@ -/**
- * Copyright (c) 2008-2012 Ardor Labs, Inc.
- *
- * This file is part of Ardor3D.
- *
- * Ardor3D is free software: you can redistribute it and/or modify it
- * under the terms of its license which may be found in the accompanying
- * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
- */
-
-package com.ardor3d.extension.interact.widget;
-
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import com.ardor3d.extension.interact.InteractManager;
-import com.ardor3d.framework.Canvas;
-import com.ardor3d.input.ButtonState;
-import com.ardor3d.input.MouseState;
-import com.ardor3d.input.logical.TwoInputStates;
-import com.ardor3d.math.MathUtils;
-import com.ardor3d.math.Matrix3;
-import com.ardor3d.math.Plane;
-import com.ardor3d.math.Transform;
-import com.ardor3d.math.Vector2;
-import com.ardor3d.math.Vector3;
-import com.ardor3d.math.type.ReadOnlyColorRGBA;
-import com.ardor3d.renderer.Camera;
-import com.ardor3d.renderer.Renderer;
-import com.ardor3d.renderer.queue.RenderBucketType;
-import com.ardor3d.renderer.state.BlendState;
-import com.ardor3d.renderer.state.ZBufferState;
-import com.ardor3d.renderer.state.ZBufferState.TestFunction;
-import com.ardor3d.scenegraph.Node;
-import com.ardor3d.scenegraph.Spatial;
-import com.ardor3d.scenegraph.shape.Cylinder;
-
-public class MovePlanarWidget extends AbstractInteractWidget {
- public static double MIN_SCALE = 0.000001;
-
- protected MovePlane _plane = MovePlane.XZ;
-
- public enum MovePlane {
- XY, XZ, YZ
- }
-
- public MovePlanarWidget() {
- _handle = new Node("moveHandle");
-
- final BlendState blend = new BlendState();
- blend.setBlendEnabled(true);
- _handle.setRenderState(blend);
-
- final ZBufferState zstate = new ZBufferState();
- zstate.setFunction(TestFunction.LessThanOrEqualTo);
- _handle.setRenderState(zstate);
-
- _handle.getSceneHints().setRenderBucketType(RenderBucketType.Transparent);
- _handle.updateGeometricState(0);
- }
-
- public MovePlanarWidget withDefaultHandle(final double radius, final double height, final ReadOnlyColorRGBA color) {
- final Cylinder handle = new Cylinder("handle", 2, 16, radius, height, true);
- handle.setDefaultColor(color);
- switch (_plane) {
- case XZ:
- handle.setRotation(new Matrix3().fromAngleNormalAxis(MathUtils.HALF_PI, Vector3.UNIT_X));
- break;
- case YZ:
- handle.setRotation(new Matrix3().fromAngleNormalAxis(MathUtils.HALF_PI, Vector3.UNIT_Y));
- break;
- default:
- // do nothing
- break;
- }
- handle.updateModelBound();
- withHandle(handle);
- return this;
- }
-
- public MovePlanarWidget withPlane(final MovePlane plane) {
- _plane = plane;
- return this;
- }
-
- public MovePlanarWidget withHandle(final Spatial handle) {
- _handle.attachChild(handle);
- return this;
- }
-
- @Override
- public void targetChanged(final InteractManager manager) {
- if (_dragging) {
- endDrag(manager);
- }
- final Spatial target = manager.getSpatialTarget();
- if (target != null) {
- _handle.setScale(Math.max(MovePlanarWidget.MIN_SCALE, target.getWorldBound().getRadius()
- + target.getWorldTranslation().subtract(target.getWorldBound().getCenter(), _calcVec3A).length()));
- }
- targetDataUpdated(manager);
- }
-
- @Override
- public void targetDataUpdated(final InteractManager manager) {
- final Spatial target = manager.getSpatialTarget();
- if (target == null) {
- _handle.setScale(1.0);
- _handle.setRotation(Matrix3.IDENTITY);
- } else {
- // update scale of widget using bounding radius
- target.updateGeometricState(0);
-
- // update arrow rotations from target
- if (_interactMatrix == InteractMatrix.Local) {
- _handle.setRotation(target.getWorldRotation());
- } else {
- _handle.setRotation(Matrix3.IDENTITY);
- }
- }
- }
-
- @Override
- public void render(final Renderer renderer, final InteractManager manager) {
- final Spatial spat = manager.getSpatialTarget();
- if (spat == null) {
- return;
- }
-
- _handle.setTranslation(spat.getWorldTranslation());
- _handle.updateGeometricState(0);
-
- renderer.draw(_handle);
- }
-
- @Override
- public void processInput(final Canvas source, final TwoInputStates inputStates, final AtomicBoolean inputConsumed,
- final InteractManager manager) {
- // Make sure we have something to modify
- if (manager.getSpatialTarget() == null) {
- return;
- }
-
- // Make sure we are dragging.
- final MouseState current = inputStates.getCurrent().getMouseState();
- final MouseState previous = inputStates.getPrevious().getMouseState();
-
- if (current.getButtonState(_dragButton) != ButtonState.DOWN) {
- if (_dragging) {
- endDrag(manager);
- }
- return;
- }
- // if we're already dragging, make sure we only act on drags that started with a positive pick.
- else if (!current.getButtonsPressedSince(previous).contains(_dragButton) && !_dragging) {
- return;
- }
-
- final Camera camera = source.getCanvasRenderer().getCamera();
- final Vector2 oldMouse = new Vector2(previous.getX(), previous.getY());
- // Make sure we are dragging over the handle
- if (!_dragging) {
- findPick(oldMouse, camera);
- final Vector3 lastPick = getLastPick();
- if (lastPick == null) {
- return;
- } else {
- beginDrag(manager);
- }
- }
-
- // we've established that our mouse is being held down, and started over our arrow. So consume.
- inputConsumed.set(true);
-
- // check if we've moved at all
- if (current == previous || current.getDx() == 0 && current.getDy() == 0) {
- return;
- }
-
- // act on drag
- final Spatial picked = (Spatial) _results.getPickData(0).getTarget();
- if (picked != null) {
- final Vector3 loc = getNewOffset(oldMouse, current, camera, manager);
- final Transform transform = manager.getSpatialState().getTransform();
- transform.setTranslation(loc.addLocal(transform.getTranslation()));
-
- // apply our filters, if any, now that we've made updates.
- applyFilters(manager);
- }
- }
-
- protected Vector3 getNewOffset(final Vector2 oldMouse, final MouseState current, final Camera camera,
- final InteractManager manager) {
-
- // calculate a plane
- _calcVec3A.set(_handle.getWorldTranslation());
- switch (_plane) {
- case XY:
- _calcVec3B.set(Vector3.UNIT_X);
- _calcVec3C.set(Vector3.UNIT_Y);
- break;
- case XZ:
- _calcVec3B.set(Vector3.UNIT_X);
- _calcVec3C.set(Vector3.UNIT_Z);
- break;
- case YZ:
- _calcVec3B.set(Vector3.UNIT_Y);
- _calcVec3C.set(Vector3.UNIT_Z);
- break;
- }
-
- // rotate to arrow plane
- _handle.getRotation().applyPost(_calcVec3B, _calcVec3B);
- _handle.getRotation().applyPost(_calcVec3C, _calcVec3C);
-
- // make plane object
- final Plane pickPlane = new Plane().setPlanePoints(_calcVec3A, _calcVec3B.addLocal(_calcVec3A),
- _calcVec3C.addLocal(_calcVec3A));
-
- // find out where we were hitting the plane before
- getPickRay(oldMouse, camera);
- if (!_calcRay.intersectsPlane(pickPlane, _calcVec3A)) {
- return _calcVec3A.zero();
- }
-
- // find out where we are hitting the plane now
- getPickRay(new Vector2(current.getX(), current.getY()), camera);
- if (!_calcRay.intersectsPlane(pickPlane, _calcVec3B)) {
- return _calcVec3A.zero();
- }
-
- // convert to target coord space
- final Node parent = manager.getSpatialTarget().getParent();
- if (parent != null) {
- parent.getWorldTransform().applyInverse(_calcVec3A);
- parent.getWorldTransform().applyInverse(_calcVec3B);
- }
-
- return _calcVec3B.subtractLocal(_calcVec3A);
- }
-}
+/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.interact.widget; + +import java.util.concurrent.atomic.AtomicBoolean; + +import com.ardor3d.extension.interact.InteractManager; +import com.ardor3d.framework.Canvas; +import com.ardor3d.input.ButtonState; +import com.ardor3d.input.MouseState; +import com.ardor3d.input.logical.TwoInputStates; +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Matrix3; +import com.ardor3d.math.Plane; +import com.ardor3d.math.Transform; +import com.ardor3d.math.Vector2; +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyColorRGBA; +import com.ardor3d.renderer.Camera; +import com.ardor3d.renderer.Renderer; +import com.ardor3d.renderer.queue.RenderBucketType; +import com.ardor3d.renderer.state.BlendState; +import com.ardor3d.renderer.state.ZBufferState; +import com.ardor3d.renderer.state.ZBufferState.TestFunction; +import com.ardor3d.scenegraph.Node; +import com.ardor3d.scenegraph.Spatial; +import com.ardor3d.scenegraph.shape.Cylinder; + +public class MovePlanarWidget extends AbstractInteractWidget { + public static double MIN_SCALE = 0.000001; + + protected MovePlane _plane = MovePlane.XZ; + + public enum MovePlane { + XY, XZ, YZ + } + + public MovePlanarWidget() { + _handle = new Node("moveHandle"); + + final BlendState blend = new BlendState(); + blend.setBlendEnabled(true); + _handle.setRenderState(blend); + + final ZBufferState zstate = new ZBufferState(); + zstate.setFunction(TestFunction.LessThanOrEqualTo); + _handle.setRenderState(zstate); + + _handle.getSceneHints().setRenderBucketType(RenderBucketType.Transparent); + _handle.updateGeometricState(0); + } + + public MovePlanarWidget withDefaultHandle(final double radius, final double height, final ReadOnlyColorRGBA color) { + final Cylinder handle = new Cylinder("handle", 2, 16, radius, height, true); + handle.setDefaultColor(color); + switch (_plane) { + case XZ: + handle.setRotation(new Matrix3().fromAngleNormalAxis(MathUtils.HALF_PI, Vector3.UNIT_X)); + break; + case YZ: + handle.setRotation(new Matrix3().fromAngleNormalAxis(MathUtils.HALF_PI, Vector3.UNIT_Y)); + break; + default: + // do nothing + break; + } + handle.updateModelBound(); + withHandle(handle); + return this; + } + + public MovePlanarWidget withPlane(final MovePlane plane) { + _plane = plane; + return this; + } + + public MovePlanarWidget withHandle(final Spatial handle) { + _handle.attachChild(handle); + return this; + } + + @Override + public void targetChanged(final InteractManager manager) { + if (_dragging) { + endDrag(manager); + } + final Spatial target = manager.getSpatialTarget(); + if (target != null) { + _handle.setScale(Math.max(MovePlanarWidget.MIN_SCALE, target.getWorldBound().getRadius() + + target.getWorldTranslation().subtract(target.getWorldBound().getCenter(), _calcVec3A).length())); + } + targetDataUpdated(manager); + } + + @Override + public void targetDataUpdated(final InteractManager manager) { + final Spatial target = manager.getSpatialTarget(); + if (target == null) { + _handle.setScale(1.0); + _handle.setRotation(Matrix3.IDENTITY); + } else { + // update scale of widget using bounding radius + target.updateGeometricState(0); + + // update arrow rotations from target + if (_interactMatrix == InteractMatrix.Local) { + _handle.setRotation(target.getWorldRotation()); + } else { + _handle.setRotation(Matrix3.IDENTITY); + } + } + } + + @Override + public void render(final Renderer renderer, final InteractManager manager) { + final Spatial spat = manager.getSpatialTarget(); + if (spat == null) { + return; + } + + _handle.setTranslation(spat.getWorldTranslation()); + _handle.updateGeometricState(0); + + renderer.draw(_handle); + } + + @Override + public void processInput(final Canvas source, final TwoInputStates inputStates, final AtomicBoolean inputConsumed, + final InteractManager manager) { + // Make sure we have something to modify + if (manager.getSpatialTarget() == null) { + return; + } + + // Make sure we are dragging. + final MouseState current = inputStates.getCurrent().getMouseState(); + final MouseState previous = inputStates.getPrevious().getMouseState(); + + if (current.getButtonState(_dragButton) != ButtonState.DOWN) { + if (_dragging) { + endDrag(manager); + } + return; + } + // if we're already dragging, make sure we only act on drags that started with a positive pick. + else if (!current.getButtonsPressedSince(previous).contains(_dragButton) && !_dragging) { + return; + } + + final Camera camera = source.getCanvasRenderer().getCamera(); + final Vector2 oldMouse = new Vector2(previous.getX(), previous.getY()); + // Make sure we are dragging over the handle + if (!_dragging) { + findPick(oldMouse, camera); + final Vector3 lastPick = getLastPick(); + if (lastPick == null) { + return; + } else { + beginDrag(manager); + } + } + + // we've established that our mouse is being held down, and started over our arrow. So consume. + inputConsumed.set(true); + + // check if we've moved at all + if (current == previous || current.getDx() == 0 && current.getDy() == 0) { + return; + } + + // act on drag + final Spatial picked = (Spatial) _results.getPickData(0).getTarget(); + if (picked != null) { + final Vector3 loc = getNewOffset(oldMouse, current, camera, manager); + final Transform transform = manager.getSpatialState().getTransform(); + transform.setTranslation(loc.addLocal(transform.getTranslation())); + + // apply our filters, if any, now that we've made updates. + applyFilters(manager); + } + } + + protected Vector3 getNewOffset(final Vector2 oldMouse, final MouseState current, final Camera camera, + final InteractManager manager) { + + // calculate a plane + _calcVec3A.set(_handle.getWorldTranslation()); + switch (_plane) { + case XY: + _calcVec3B.set(Vector3.UNIT_X); + _calcVec3C.set(Vector3.UNIT_Y); + break; + case XZ: + _calcVec3B.set(Vector3.UNIT_X); + _calcVec3C.set(Vector3.UNIT_Z); + break; + case YZ: + _calcVec3B.set(Vector3.UNIT_Y); + _calcVec3C.set(Vector3.UNIT_Z); + break; + } + + // rotate to arrow plane + _handle.getRotation().applyPost(_calcVec3B, _calcVec3B); + _handle.getRotation().applyPost(_calcVec3C, _calcVec3C); + + // make plane object + final Plane pickPlane = new Plane().setPlanePoints(_calcVec3A, _calcVec3B.addLocal(_calcVec3A), + _calcVec3C.addLocal(_calcVec3A)); + + // find out where we were hitting the plane before + getPickRay(oldMouse, camera); + if (!_calcRay.intersectsPlane(pickPlane, _calcVec3A)) { + return _calcVec3A.zero(); + } + + // find out where we are hitting the plane now + getPickRay(new Vector2(current.getX(), current.getY()), camera); + if (!_calcRay.intersectsPlane(pickPlane, _calcVec3B)) { + return _calcVec3A.zero(); + } + + // convert to target coord space + final Node parent = manager.getSpatialTarget().getParent(); + if (parent != null) { + parent.getWorldTransform().applyInverse(_calcVec3A); + parent.getWorldTransform().applyInverse(_calcVec3B); + } + + return _calcVec3B.subtractLocal(_calcVec3A); + } +} diff --git a/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/widget/MoveWidget.java b/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/widget/MoveWidget.java index dfdb2b1..5a6cda9 100644 --- a/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/widget/MoveWidget.java +++ b/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/widget/MoveWidget.java @@ -1,301 +1,301 @@ -/**
- * Copyright (c) 2008-2012 Ardor Labs, Inc.
- *
- * This file is part of Ardor3D.
- *
- * Ardor3D is free software: you can redistribute it and/or modify it
- * under the terms of its license which may be found in the accompanying
- * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
- */
-
-package com.ardor3d.extension.interact.widget;
-
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import com.ardor3d.extension.interact.InteractManager;
-import com.ardor3d.framework.Canvas;
-import com.ardor3d.input.ButtonState;
-import com.ardor3d.input.MouseState;
-import com.ardor3d.input.logical.TwoInputStates;
-import com.ardor3d.math.ColorRGBA;
-import com.ardor3d.math.Line3;
-import com.ardor3d.math.MathUtils;
-import com.ardor3d.math.Matrix3;
-import com.ardor3d.math.Plane;
-import com.ardor3d.math.Quaternion;
-import com.ardor3d.math.Transform;
-import com.ardor3d.math.Vector2;
-import com.ardor3d.math.Vector3;
-import com.ardor3d.math.type.ReadOnlyColorRGBA;
-import com.ardor3d.renderer.Camera;
-import com.ardor3d.renderer.Renderer;
-import com.ardor3d.renderer.queue.RenderBucketType;
-import com.ardor3d.renderer.state.BlendState;
-import com.ardor3d.renderer.state.ZBufferState;
-import com.ardor3d.renderer.state.ZBufferState.TestFunction;
-import com.ardor3d.scenegraph.Node;
-import com.ardor3d.scenegraph.Spatial;
-
-public class MoveWidget extends AbstractInteractWidget {
- public static double MIN_SCALE = 0.000001;
-
- protected InteractArrow _lastArrow = null;
-
- protected InteractArrow _xArrow = null;
- protected InteractArrow _yArrow = null;
- protected InteractArrow _zArrow = null;
-
- protected ColorRGBA _xColor = new ColorRGBA(1, 0, 0, .65f);
- protected ColorRGBA _yColor = new ColorRGBA(0, 1, 0, .65f);
- protected ColorRGBA _zColor = new ColorRGBA(0, 0, 1, .65f);
-
- protected InteractMatrix _interactMatrix = InteractMatrix.World;
-
- public MoveWidget() {
- _handle = new Node("moveHandle");
-
- final BlendState blend = new BlendState();
- blend.setBlendEnabled(true);
- _handle.setRenderState(blend);
-
- final ZBufferState zstate = new ZBufferState();
- zstate.setFunction(TestFunction.LessThanOrEqualTo);
- _handle.setRenderState(zstate);
-
- _handle.getSceneHints().setRenderBucketType(RenderBucketType.Transparent);
- _handle.updateGeometricState(0);
- }
-
- public MoveWidget withXAxis() {
- return withXAxis(_xColor);
- }
-
- public MoveWidget withXAxis(final ReadOnlyColorRGBA color) {
- return withXAxis(color, 1.0, 0.15, 0, 0);
- }
-
- public MoveWidget withXAxis(final ReadOnlyColorRGBA color, final double scale, final double width,
- final double lengthGap, final double tipGap) {
- if (_xArrow != null) {
- _xArrow.removeFromParent();
- }
- _xColor.set(color);
- _xArrow = new InteractArrow("xMoveArrow", scale, width, lengthGap, tipGap);
- _xArrow.setDefaultColor(color);
- final Quaternion rotate = new Quaternion().fromAngleAxis(MathUtils.HALF_PI, Vector3.UNIT_Y);
- _xArrow.setRotation(rotate);
- _handle.attachChild(_xArrow);
- return this;
- }
-
- public MoveWidget withYAxis() {
- return withYAxis(_yColor);
- }
-
- public MoveWidget withYAxis(final ReadOnlyColorRGBA color) {
- return withYAxis(color, 1.0, 0.15, 0, 0);
- }
-
- public MoveWidget withYAxis(final ReadOnlyColorRGBA color, final double scale, final double width,
- final double lengthGap, final double tipGap) {
- if (_yArrow != null) {
- _yArrow.removeFromParent();
- }
- _yColor.set(color);
- _yArrow = new InteractArrow("yMoveArrow", scale, width, lengthGap, tipGap);
- _yArrow.setDefaultColor(color);
- final Quaternion rotate = new Quaternion().fromAngleAxis(MathUtils.HALF_PI, Vector3.NEG_UNIT_X);
- _yArrow.setRotation(rotate);
- _handle.attachChild(_yArrow);
- return this;
- }
-
- public MoveWidget withZAxis() {
- return withZAxis(new ColorRGBA(0, 0, 1, .65f));
- }
-
- public MoveWidget withZAxis(final ReadOnlyColorRGBA color) {
- return withZAxis(color, 1.0, 0.15, 0, 0);
- }
-
- public MoveWidget withZAxis(final ReadOnlyColorRGBA color, final double scale, final double width,
- final double lengthGap, final double tipGap) {
- if (_zArrow != null) {
- _zArrow.removeFromParent();
- }
- _zColor.set(color);
- _zArrow = new InteractArrow("zMoveArrow", scale, width, lengthGap, tipGap);
- _zArrow.setDefaultColor(color);
- _handle.attachChild(_zArrow);
- return this;
- }
-
- @Override
- public void targetChanged(final InteractManager manager) {
- if (_dragging) {
- endDrag(manager);
- }
- final Spatial target = manager.getSpatialTarget();
- if (target != null) {
- _handle.setScale(Math.max(MoveWidget.MIN_SCALE, target.getWorldBound().getRadius()
- + target.getWorldTranslation().subtract(target.getWorldBound().getCenter(), _calcVec3A).length()));
- }
- targetDataUpdated(manager);
- }
-
- @Override
- public void targetDataUpdated(final InteractManager manager) {
- final Spatial target = manager.getSpatialTarget();
- if (target == null) {
- _handle.setScale(1.0);
- _handle.setRotation(Matrix3.IDENTITY);
- } else {
- // update scale of widget using bounding radius
- target.updateGeometricState(0);
-
- // update arrow rotations from target
- if (_interactMatrix == InteractMatrix.Local) {
- _handle.setRotation(target.getWorldRotation());
- } else {
- _handle.setRotation(Matrix3.IDENTITY);
- }
- }
- }
-
- @Override
- public void render(final Renderer renderer, final InteractManager manager) {
- final Spatial spat = manager.getSpatialTarget();
- if (spat == null) {
- return;
- }
-
- _handle.setTranslation(spat.getWorldTranslation());
- _handle.updateGeometricState(0);
-
- renderer.draw(_handle);
- }
-
- @Override
- public void processInput(final Canvas source, final TwoInputStates inputStates, final AtomicBoolean inputConsumed,
- final InteractManager manager) {
- // Make sure we have something to modify
- if (manager.getSpatialTarget() == null) {
- return;
- }
-
- // Make sure we are dragging.
- final MouseState current = inputStates.getCurrent().getMouseState();
- final MouseState previous = inputStates.getPrevious().getMouseState();
-
- if (current.getButtonState(_dragButton) != ButtonState.DOWN) {
- if (_dragging) {
- endDrag(manager);
- }
- return;
- }
- // if we're already dragging, make sure we only act on drags that started with a positive pick.
- else if (!current.getButtonsPressedSince(previous).contains(_dragButton) && !_dragging) {
- return;
- }
-
- final Camera camera = source.getCanvasRenderer().getCamera();
- final Vector2 oldMouse = new Vector2(previous.getX(), previous.getY());
- // Make sure we are dragging over the handle
- if (!_dragging) {
- findPick(oldMouse, camera);
- final Vector3 lastPick = getLastPick();
- if (lastPick == null) {
- return;
- } else {
- beginDrag(manager);
- }
- }
-
- // we've established that our mouse is being held down, and started over our arrow. So consume.
- inputConsumed.set(true);
-
- // check if we've moved at all
- if (current == previous || current.getDx() == 0 && current.getDy() == 0) {
- return;
- }
-
- // act on drag
- final Spatial picked = (Spatial) _results.getPickData(0).getTarget();
- if (picked != null && picked.getParent() instanceof InteractArrow) {
- final InteractArrow arrow = (InteractArrow) picked.getParent();
- _lastArrow = arrow;
- final Vector3 loc = getNewOffset(arrow, oldMouse, current, camera, manager);
- final Transform transform = manager.getSpatialState().getTransform();
- transform.setTranslation(loc.addLocal(transform.getTranslation()));
-
- // apply our filters, if any, now that we've made updates.
- applyFilters(manager);
- }
- }
-
- protected Vector3 getNewOffset(final InteractArrow arrow, final Vector2 oldMouse, final MouseState current,
- final Camera camera, final InteractManager manager) {
-
- // calculate a plane running through the Arrow and facing the camera.
- _calcVec3A.set(_handle.getWorldTranslation());
- _calcVec3B.set(_calcVec3A).addLocal(camera.getLeft());
- _calcVec3C.set( //
- arrow == _xArrow ? Vector3.UNIT_X : //
- arrow == _yArrow ? Vector3.UNIT_Y : //
- Vector3.UNIT_Z);
-
- // rotate to arrow plane
- _handle.getRotation().applyPost(_calcVec3C, _calcVec3C);
- final Line3 arrowLine = new Line3(_calcVec3A, _calcVec3C.normalize(_calcVec3D));
-
- // make plane object
- final Plane pickPlane = new Plane().setPlanePoints(_calcVec3A, _calcVec3B, _calcVec3C.addLocal(_calcVec3A));
-
- // find out where we were hitting the plane before
- getPickRay(oldMouse, camera);
- if (!_calcRay.intersectsPlane(pickPlane, _calcVec3A)) {
- return _calcVec3A.zero();
- }
-
- // find out where we are hitting the plane now
- getPickRay(new Vector2(current.getX(), current.getY()), camera);
- if (!_calcRay.intersectsPlane(pickPlane, _calcVec3B)) {
- return _calcVec3A.zero();
- }
-
- // Cast us to the line along our arrow
- arrowLine.distanceSquared(_calcVec3A, _calcVec3C);
- arrowLine.distanceSquared(_calcVec3B, _calcVec3D);
-
- // convert to target coord space
- final Node parent = manager.getSpatialTarget().getParent();
- if (parent != null) {
- parent.getWorldTransform().applyInverse(_calcVec3C);
- parent.getWorldTransform().applyInverse(_calcVec3D);
- }
-
- return _calcVec3D.subtractLocal(_calcVec3C);
- }
-
- @Override
- public void setInteractMatrix(final InteractMatrix matrix) {
- _interactMatrix = matrix;
- }
-
- @Override
- public InteractMatrix getInteractMatrix() {
- return _interactMatrix;
- }
-
- public InteractArrow getXArrow() {
- return _xArrow;
- }
-
- public InteractArrow getYRing() {
- return _yArrow;
- }
-
- public InteractArrow getZRing() {
- return _zArrow;
- }
-}
+/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.interact.widget; + +import java.util.concurrent.atomic.AtomicBoolean; + +import com.ardor3d.extension.interact.InteractManager; +import com.ardor3d.framework.Canvas; +import com.ardor3d.input.ButtonState; +import com.ardor3d.input.MouseState; +import com.ardor3d.input.logical.TwoInputStates; +import com.ardor3d.math.ColorRGBA; +import com.ardor3d.math.Line3; +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Matrix3; +import com.ardor3d.math.Plane; +import com.ardor3d.math.Quaternion; +import com.ardor3d.math.Transform; +import com.ardor3d.math.Vector2; +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyColorRGBA; +import com.ardor3d.renderer.Camera; +import com.ardor3d.renderer.Renderer; +import com.ardor3d.renderer.queue.RenderBucketType; +import com.ardor3d.renderer.state.BlendState; +import com.ardor3d.renderer.state.ZBufferState; +import com.ardor3d.renderer.state.ZBufferState.TestFunction; +import com.ardor3d.scenegraph.Node; +import com.ardor3d.scenegraph.Spatial; + +public class MoveWidget extends AbstractInteractWidget { + public static double MIN_SCALE = 0.000001; + + protected InteractArrow _lastArrow = null; + + protected InteractArrow _xArrow = null; + protected InteractArrow _yArrow = null; + protected InteractArrow _zArrow = null; + + protected ColorRGBA _xColor = new ColorRGBA(1, 0, 0, .65f); + protected ColorRGBA _yColor = new ColorRGBA(0, 1, 0, .65f); + protected ColorRGBA _zColor = new ColorRGBA(0, 0, 1, .65f); + + protected InteractMatrix _interactMatrix = InteractMatrix.World; + + public MoveWidget() { + _handle = new Node("moveHandle"); + + final BlendState blend = new BlendState(); + blend.setBlendEnabled(true); + _handle.setRenderState(blend); + + final ZBufferState zstate = new ZBufferState(); + zstate.setFunction(TestFunction.LessThanOrEqualTo); + _handle.setRenderState(zstate); + + _handle.getSceneHints().setRenderBucketType(RenderBucketType.Transparent); + _handle.updateGeometricState(0); + } + + public MoveWidget withXAxis() { + return withXAxis(_xColor); + } + + public MoveWidget withXAxis(final ReadOnlyColorRGBA color) { + return withXAxis(color, 1.0, 0.15, 0, 0); + } + + public MoveWidget withXAxis(final ReadOnlyColorRGBA color, final double scale, final double width, + final double lengthGap, final double tipGap) { + if (_xArrow != null) { + _xArrow.removeFromParent(); + } + _xColor.set(color); + _xArrow = new InteractArrow("xMoveArrow", scale, width, lengthGap, tipGap); + _xArrow.setDefaultColor(color); + final Quaternion rotate = new Quaternion().fromAngleAxis(MathUtils.HALF_PI, Vector3.UNIT_Y); + _xArrow.setRotation(rotate); + _handle.attachChild(_xArrow); + return this; + } + + public MoveWidget withYAxis() { + return withYAxis(_yColor); + } + + public MoveWidget withYAxis(final ReadOnlyColorRGBA color) { + return withYAxis(color, 1.0, 0.15, 0, 0); + } + + public MoveWidget withYAxis(final ReadOnlyColorRGBA color, final double scale, final double width, + final double lengthGap, final double tipGap) { + if (_yArrow != null) { + _yArrow.removeFromParent(); + } + _yColor.set(color); + _yArrow = new InteractArrow("yMoveArrow", scale, width, lengthGap, tipGap); + _yArrow.setDefaultColor(color); + final Quaternion rotate = new Quaternion().fromAngleAxis(MathUtils.HALF_PI, Vector3.NEG_UNIT_X); + _yArrow.setRotation(rotate); + _handle.attachChild(_yArrow); + return this; + } + + public MoveWidget withZAxis() { + return withZAxis(new ColorRGBA(0, 0, 1, .65f)); + } + + public MoveWidget withZAxis(final ReadOnlyColorRGBA color) { + return withZAxis(color, 1.0, 0.15, 0, 0); + } + + public MoveWidget withZAxis(final ReadOnlyColorRGBA color, final double scale, final double width, + final double lengthGap, final double tipGap) { + if (_zArrow != null) { + _zArrow.removeFromParent(); + } + _zColor.set(color); + _zArrow = new InteractArrow("zMoveArrow", scale, width, lengthGap, tipGap); + _zArrow.setDefaultColor(color); + _handle.attachChild(_zArrow); + return this; + } + + @Override + public void targetChanged(final InteractManager manager) { + if (_dragging) { + endDrag(manager); + } + final Spatial target = manager.getSpatialTarget(); + if (target != null) { + _handle.setScale(Math.max(MoveWidget.MIN_SCALE, target.getWorldBound().getRadius() + + target.getWorldTranslation().subtract(target.getWorldBound().getCenter(), _calcVec3A).length())); + } + targetDataUpdated(manager); + } + + @Override + public void targetDataUpdated(final InteractManager manager) { + final Spatial target = manager.getSpatialTarget(); + if (target == null) { + _handle.setScale(1.0); + _handle.setRotation(Matrix3.IDENTITY); + } else { + // update scale of widget using bounding radius + target.updateGeometricState(0); + + // update arrow rotations from target + if (_interactMatrix == InteractMatrix.Local) { + _handle.setRotation(target.getWorldRotation()); + } else { + _handle.setRotation(Matrix3.IDENTITY); + } + } + } + + @Override + public void render(final Renderer renderer, final InteractManager manager) { + final Spatial spat = manager.getSpatialTarget(); + if (spat == null) { + return; + } + + _handle.setTranslation(spat.getWorldTranslation()); + _handle.updateGeometricState(0); + + renderer.draw(_handle); + } + + @Override + public void processInput(final Canvas source, final TwoInputStates inputStates, final AtomicBoolean inputConsumed, + final InteractManager manager) { + // Make sure we have something to modify + if (manager.getSpatialTarget() == null) { + return; + } + + // Make sure we are dragging. + final MouseState current = inputStates.getCurrent().getMouseState(); + final MouseState previous = inputStates.getPrevious().getMouseState(); + + if (current.getButtonState(_dragButton) != ButtonState.DOWN) { + if (_dragging) { + endDrag(manager); + } + return; + } + // if we're already dragging, make sure we only act on drags that started with a positive pick. + else if (!current.getButtonsPressedSince(previous).contains(_dragButton) && !_dragging) { + return; + } + + final Camera camera = source.getCanvasRenderer().getCamera(); + final Vector2 oldMouse = new Vector2(previous.getX(), previous.getY()); + // Make sure we are dragging over the handle + if (!_dragging) { + findPick(oldMouse, camera); + final Vector3 lastPick = getLastPick(); + if (lastPick == null) { + return; + } else { + beginDrag(manager); + } + } + + // we've established that our mouse is being held down, and started over our arrow. So consume. + inputConsumed.set(true); + + // check if we've moved at all + if (current == previous || current.getDx() == 0 && current.getDy() == 0) { + return; + } + + // act on drag + final Spatial picked = (Spatial) _results.getPickData(0).getTarget(); + if (picked != null && picked.getParent() instanceof InteractArrow) { + final InteractArrow arrow = (InteractArrow) picked.getParent(); + _lastArrow = arrow; + final Vector3 loc = getNewOffset(arrow, oldMouse, current, camera, manager); + final Transform transform = manager.getSpatialState().getTransform(); + transform.setTranslation(loc.addLocal(transform.getTranslation())); + + // apply our filters, if any, now that we've made updates. + applyFilters(manager); + } + } + + protected Vector3 getNewOffset(final InteractArrow arrow, final Vector2 oldMouse, final MouseState current, + final Camera camera, final InteractManager manager) { + + // calculate a plane running through the Arrow and facing the camera. + _calcVec3A.set(_handle.getWorldTranslation()); + _calcVec3B.set(_calcVec3A).addLocal(camera.getLeft()); + _calcVec3C.set( // + arrow == _xArrow ? Vector3.UNIT_X : // + arrow == _yArrow ? Vector3.UNIT_Y : // + Vector3.UNIT_Z); + + // rotate to arrow plane + _handle.getRotation().applyPost(_calcVec3C, _calcVec3C); + final Line3 arrowLine = new Line3(_calcVec3A, _calcVec3C.normalize(_calcVec3D)); + + // make plane object + final Plane pickPlane = new Plane().setPlanePoints(_calcVec3A, _calcVec3B, _calcVec3C.addLocal(_calcVec3A)); + + // find out where we were hitting the plane before + getPickRay(oldMouse, camera); + if (!_calcRay.intersectsPlane(pickPlane, _calcVec3A)) { + return _calcVec3A.zero(); + } + + // find out where we are hitting the plane now + getPickRay(new Vector2(current.getX(), current.getY()), camera); + if (!_calcRay.intersectsPlane(pickPlane, _calcVec3B)) { + return _calcVec3A.zero(); + } + + // Cast us to the line along our arrow + arrowLine.distanceSquared(_calcVec3A, _calcVec3C); + arrowLine.distanceSquared(_calcVec3B, _calcVec3D); + + // convert to target coord space + final Node parent = manager.getSpatialTarget().getParent(); + if (parent != null) { + parent.getWorldTransform().applyInverse(_calcVec3C); + parent.getWorldTransform().applyInverse(_calcVec3D); + } + + return _calcVec3D.subtractLocal(_calcVec3C); + } + + @Override + public void setInteractMatrix(final InteractMatrix matrix) { + _interactMatrix = matrix; + } + + @Override + public InteractMatrix getInteractMatrix() { + return _interactMatrix; + } + + public InteractArrow getXArrow() { + return _xArrow; + } + + public InteractArrow getYRing() { + return _yArrow; + } + + public InteractArrow getZRing() { + return _zArrow; + } +} diff --git a/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/widget/RotateWidget.java b/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/widget/RotateWidget.java index 50c3a1a..9040208 100644 --- a/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/widget/RotateWidget.java +++ b/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/widget/RotateWidget.java @@ -1,348 +1,348 @@ -/**
- * Copyright (c) 2008-2012 Ardor Labs, Inc.
- *
- * This file is part of Ardor3D.
- *
- * Ardor3D is free software: you can redistribute it and/or modify it
- * under the terms of its license which may be found in the accompanying
- * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
- */
-
-package com.ardor3d.extension.interact.widget;
-
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import com.ardor3d.extension.interact.InteractManager;
-import com.ardor3d.framework.Canvas;
-import com.ardor3d.image.Texture2D;
-import com.ardor3d.input.ButtonState;
-import com.ardor3d.input.MouseState;
-import com.ardor3d.input.logical.TwoInputStates;
-import com.ardor3d.math.ColorRGBA;
-import com.ardor3d.math.MathUtils;
-import com.ardor3d.math.Matrix3;
-import com.ardor3d.math.Plane;
-import com.ardor3d.math.Quaternion;
-import com.ardor3d.math.Transform;
-import com.ardor3d.math.Vector2;
-import com.ardor3d.math.Vector3;
-import com.ardor3d.math.type.ReadOnlyColorRGBA;
-import com.ardor3d.math.type.ReadOnlyMatrix3;
-import com.ardor3d.math.type.ReadOnlyQuaternion;
-import com.ardor3d.renderer.Camera;
-import com.ardor3d.renderer.Renderer;
-import com.ardor3d.renderer.queue.RenderBucketType;
-import com.ardor3d.renderer.state.BlendState;
-import com.ardor3d.renderer.state.ZBufferState;
-import com.ardor3d.renderer.state.ZBufferState.TestFunction;
-import com.ardor3d.scenegraph.Node;
-import com.ardor3d.scenegraph.Spatial;
-
-public class RotateWidget extends AbstractInteractWidget {
- public static double MIN_SCALE = 0.000001;
-
- protected Matrix3 _calcMat3 = new Matrix3();
- protected Matrix3 _rotateStore = new Matrix3();
-
- protected InteractRing _lastRing = null;
-
- protected InteractRing _xRing = null;
- protected InteractRing _yRing = null;
- protected InteractRing _zRing = null;
-
- public RotateWidget() {
- _handle = new Node("rotationHandle");
-
- final BlendState blend = new BlendState();
- blend.setBlendEnabled(true);
- _handle.setRenderState(blend);
-
- final ZBufferState zstate = new ZBufferState();
- zstate.setFunction(TestFunction.LessThanOrEqualTo);
- _handle.setRenderState(zstate);
-
- _handle.getSceneHints().setRenderBucketType(RenderBucketType.Transparent);
- _handle.updateGeometricState(0);
- }
-
- /**
- * Call this after creating the rings you want to use.
- *
- * @param texture
- * @return
- */
- public void setTexture(final Texture2D texture) {
- if (_xRing != null) {
- _xRing.setTexture(texture);
- }
- if (_yRing != null) {
- _yRing.setTexture(texture);
- }
- if (_zRing != null) {
- _zRing.setTexture(texture);
- }
- }
-
- public RotateWidget withXAxis() {
- return withXAxis(new ColorRGBA(1, 0, 0, .65f));
- }
-
- public RotateWidget withXAxis(final ReadOnlyColorRGBA color) {
- return withXAxis(color, 1.0f, 0.15f);
- }
-
- public RotateWidget withXAxis(final ReadOnlyColorRGBA color, final float scale, final float width) {
- if (_xRing != null) {
- _xRing.removeFromParent();
- }
- _xRing = new InteractRing("xRotRing", 4, 32, scale, width);
- _xRing.setDefaultColor(color);
- final Quaternion rotate = new Quaternion().fromAngleAxis(MathUtils.HALF_PI, Vector3.UNIT_Y);
- _xRing.getMeshData().rotatePoints(rotate);
- _xRing.getMeshData().rotateNormals(rotate);
- _handle.attachChild(_xRing);
- return this;
- }
-
- public RotateWidget withYAxis() {
- return withYAxis(new ColorRGBA(0, 1, 0, .65f));
- }
-
- public RotateWidget withYAxis(final ReadOnlyColorRGBA color) {
- return withYAxis(color, 1.0f, 0.15f);
- }
-
- public RotateWidget withYAxis(final ReadOnlyColorRGBA color, final float scale, final float width) {
- if (_yRing != null) {
- _yRing.removeFromParent();
- }
- _yRing = new InteractRing("yRotRing", 4, 32, scale, width);
- _yRing.setDefaultColor(color);
- final Quaternion rotate = new Quaternion().fromAngleAxis(MathUtils.HALF_PI, Vector3.NEG_UNIT_X);
- _yRing.getMeshData().rotatePoints(rotate);
- _yRing.getMeshData().rotateNormals(rotate);
- _handle.attachChild(_yRing);
- return this;
- }
-
- public RotateWidget withZAxis() {
- return withZAxis(new ColorRGBA(0, 0, 1, .65f));
- }
-
- public RotateWidget withZAxis(final ReadOnlyColorRGBA color) {
- return withZAxis(color, 1.0f, 0.15f);
- }
-
- public RotateWidget withZAxis(final ReadOnlyColorRGBA color, final float scale, final float width) {
- if (_zRing != null) {
- _zRing.removeFromParent();
- }
- _zRing = new InteractRing("zRotRing", 4, 32, scale, width);
- _zRing.setDefaultColor(color);
- _handle.attachChild(_zRing);
- return this;
- }
-
- @Override
- public void targetChanged(final InteractManager manager) {
- if (_dragging) {
- endDrag(manager);
- }
- final Spatial target = manager.getSpatialTarget();
- if (target != null) {
- _handle.setScale(Math.max(RotateWidget.MIN_SCALE, target.getWorldBound().getRadius()
- + target.getWorldTranslation().subtract(target.getWorldBound().getCenter(), _calcVec3A).length()));
- }
- targetDataUpdated(manager);
- }
-
- protected void setRingRotations(final ReadOnlyMatrix3 rot) {
- if (_xRing != null) {
- _xRing.setRotation(rot);
- }
- if (_yRing != null) {
- _yRing.setRotation(rot);
- }
- if (_zRing != null) {
- _zRing.setRotation(rot);
- }
- }
-
- @Override
- public void targetDataUpdated(final InteractManager manager) {
- final Spatial target = manager.getSpatialTarget();
- if (target == null) {
- _handle.setScale(1.0);
- setRingRotations(Matrix3.IDENTITY);
- } else {
- // update scale of widget using bounding radius
- target.updateGeometricState(0);
-
- // update ring rotations from target
- if (_interactMatrix == InteractMatrix.Local) {
- setRingRotations(target.getWorldRotation());
- } else {
- setRingRotations(Matrix3.IDENTITY);
- if (_lastRing != null) {
- _lastRing.setRotation(_rotateStore);
- }
- }
- }
- }
-
- @Override
- public void render(final Renderer renderer, final InteractManager manager) {
- final Spatial spat = manager.getSpatialTarget();
- if (spat == null) {
- return;
- }
-
- _handle.setTranslation(spat.getWorldTranslation());
- _handle.updateGeometricState(0);
-
- renderer.draw(_handle);
- }
-
- @Override
- public void processInput(final Canvas source, final TwoInputStates inputStates, final AtomicBoolean inputConsumed,
- final InteractManager manager) {
- // Make sure we have something to modify
- if (manager.getSpatialTarget() == null) {
- return;
- }
-
- // Make sure we are dragging.
- final MouseState current = inputStates.getCurrent().getMouseState();
- final MouseState previous = inputStates.getPrevious().getMouseState();
-
- if (current.getButtonsReleasedSince(previous).contains(_dragButton)) {
- _rotateStore.setIdentity();
- if (_interactMatrix != InteractMatrix.Local) {
- setRingRotations(Matrix3.IDENTITY);
- }
- }
-
- if (current.getButtonState(_dragButton) != ButtonState.DOWN) {
- if (_dragging) {
- endDrag(manager);
- }
- return;
- }
- // if we're already dragging, make sure we only act on drags that started with a positive pick.
- else if (!current.getButtonsPressedSince(previous).contains(_dragButton) && !_dragging) {
- return;
- }
-
- final Camera camera = source.getCanvasRenderer().getCamera();
- final Vector2 oldMouse = new Vector2(previous.getX(), previous.getY());
- // Make sure we are dragging over the handle
- if (!_dragging) {
- findPick(oldMouse, camera);
- final Vector3 lastPick = getLastPick();
- if (lastPick == null) {
- return;
- } else {
- beginDrag(manager);
- }
- }
-
- // we've established that our mouse is being held down, and started over our arrow. So consume.
- inputConsumed.set(true);
-
- // check if we've moved at all
- if (current == previous || current.getDx() == 0 && current.getDy() == 0) {
- return;
- }
-
- // act on drag
- final Spatial picked = (Spatial) _results.getPickData(0).getTarget();
- if (picked instanceof InteractRing) {
- final InteractRing ring = (InteractRing) picked;
- _lastRing = ring;
- final ReadOnlyQuaternion rot = getNewAxisRotation(ring, oldMouse, current, camera, manager);
- final Transform transform = manager.getSpatialState().getTransform();
- rot.toRotationMatrix(_calcMat3).multiply(transform.getMatrix(), _calcMat3);
- transform.setRotation(_calcMat3);
-
- // apply our filters, if any, now that we've made updates.
- applyFilters(manager);
- }
- }
-
- protected ReadOnlyQuaternion getNewAxisRotation(final InteractRing ring, final Vector2 oldMouse,
- final MouseState current, final Camera camera, final InteractManager manager) {
- // calculate a plane running through the ring we picked
- _calcVec3A.set(_handle.getWorldTranslation());
- if (ring == _zRing || ring == _yRing) {
- _calcVec3B.set(Vector3.UNIT_X);
- } else {
- _calcVec3B.set(Vector3.UNIT_Z);
- }
-
- if (ring == _zRing || ring == _xRing) {
- _calcVec3C.set(Vector3.UNIT_Y);
- } else {
- _calcVec3C.set(Vector3.UNIT_Z);
- }
-
- // rotate to ring plane
- ring.getRotation().applyPost(_calcVec3B, _calcVec3B);
- ring.getRotation().applyPost(_calcVec3C, _calcVec3C);
-
- // make plane object
- final Plane pickPlane = new Plane().setPlanePoints(_calcVec3A, _calcVec3B.addLocal(_calcVec3A),
- _calcVec3C.addLocal(_calcVec3A));
-
- // find out where we were hitting the plane before
- getPickRay(oldMouse, camera);
- if (!_calcRay.intersectsPlane(pickPlane, _calcVec3A)) {
- return Quaternion.IDENTITY;
- }
-
- // find out where we are hitting the plane now
- getPickRay(new Vector2(current.getX(), current.getY()), camera);
- if (!_calcRay.intersectsPlane(pickPlane, _calcVec3B)) {
- return Quaternion.IDENTITY;
- }
-
- // convert to vectors
- _calcVec3A.subtractLocal(_handle.getWorldTranslation());
- _calcVec3B.subtractLocal(_handle.getWorldTranslation());
-
- // apply to our interact matrix if used
- if (_interactMatrix == InteractMatrix.World) {
- _rotateStore.multiplyLocal(new Quaternion().fromVectorToVector(_calcVec3A, _calcVec3B).toRotationMatrix(
- _calcMat3));
- }
-
- // convert to target coord space
- final Node parent = manager.getSpatialTarget().getParent();
- if (parent != null) {
- parent.getWorldTransform().applyInverseVector(_calcVec3A);
- parent.getWorldTransform().applyInverseVector(_calcVec3B);
- }
-
- // return a rotation to take us to the new rotation
- return new Quaternion().fromVectorToVector(_calcVec3A, _calcVec3B);
- }
-
- @Override
- public void setInteractMatrix(final InteractMatrix matrix) {
- if (_interactMatrix != matrix) {
- _lastRing = null;
- _interactMatrix = matrix;
- }
- }
-
- public InteractRing getXRing() {
- return _xRing;
- }
-
- public InteractRing getYRing() {
- return _yRing;
- }
-
- public InteractRing getZRing() {
- return _zRing;
- }
-}
+/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.interact.widget; + +import java.util.concurrent.atomic.AtomicBoolean; + +import com.ardor3d.extension.interact.InteractManager; +import com.ardor3d.framework.Canvas; +import com.ardor3d.image.Texture2D; +import com.ardor3d.input.ButtonState; +import com.ardor3d.input.MouseState; +import com.ardor3d.input.logical.TwoInputStates; +import com.ardor3d.math.ColorRGBA; +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Matrix3; +import com.ardor3d.math.Plane; +import com.ardor3d.math.Quaternion; +import com.ardor3d.math.Transform; +import com.ardor3d.math.Vector2; +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyColorRGBA; +import com.ardor3d.math.type.ReadOnlyMatrix3; +import com.ardor3d.math.type.ReadOnlyQuaternion; +import com.ardor3d.renderer.Camera; +import com.ardor3d.renderer.Renderer; +import com.ardor3d.renderer.queue.RenderBucketType; +import com.ardor3d.renderer.state.BlendState; +import com.ardor3d.renderer.state.ZBufferState; +import com.ardor3d.renderer.state.ZBufferState.TestFunction; +import com.ardor3d.scenegraph.Node; +import com.ardor3d.scenegraph.Spatial; + +public class RotateWidget extends AbstractInteractWidget { + public static double MIN_SCALE = 0.000001; + + protected Matrix3 _calcMat3 = new Matrix3(); + protected Matrix3 _rotateStore = new Matrix3(); + + protected InteractRing _lastRing = null; + + protected InteractRing _xRing = null; + protected InteractRing _yRing = null; + protected InteractRing _zRing = null; + + public RotateWidget() { + _handle = new Node("rotationHandle"); + + final BlendState blend = new BlendState(); + blend.setBlendEnabled(true); + _handle.setRenderState(blend); + + final ZBufferState zstate = new ZBufferState(); + zstate.setFunction(TestFunction.LessThanOrEqualTo); + _handle.setRenderState(zstate); + + _handle.getSceneHints().setRenderBucketType(RenderBucketType.Transparent); + _handle.updateGeometricState(0); + } + + /** + * Call this after creating the rings you want to use. + * + * @param texture + * @return + */ + public void setTexture(final Texture2D texture) { + if (_xRing != null) { + _xRing.setTexture(texture); + } + if (_yRing != null) { + _yRing.setTexture(texture); + } + if (_zRing != null) { + _zRing.setTexture(texture); + } + } + + public RotateWidget withXAxis() { + return withXAxis(new ColorRGBA(1, 0, 0, .65f)); + } + + public RotateWidget withXAxis(final ReadOnlyColorRGBA color) { + return withXAxis(color, 1.0f, 0.15f); + } + + public RotateWidget withXAxis(final ReadOnlyColorRGBA color, final float scale, final float width) { + if (_xRing != null) { + _xRing.removeFromParent(); + } + _xRing = new InteractRing("xRotRing", 4, 32, scale, width); + _xRing.setDefaultColor(color); + final Quaternion rotate = new Quaternion().fromAngleAxis(MathUtils.HALF_PI, Vector3.UNIT_Y); + _xRing.getMeshData().rotatePoints(rotate); + _xRing.getMeshData().rotateNormals(rotate); + _handle.attachChild(_xRing); + return this; + } + + public RotateWidget withYAxis() { + return withYAxis(new ColorRGBA(0, 1, 0, .65f)); + } + + public RotateWidget withYAxis(final ReadOnlyColorRGBA color) { + return withYAxis(color, 1.0f, 0.15f); + } + + public RotateWidget withYAxis(final ReadOnlyColorRGBA color, final float scale, final float width) { + if (_yRing != null) { + _yRing.removeFromParent(); + } + _yRing = new InteractRing("yRotRing", 4, 32, scale, width); + _yRing.setDefaultColor(color); + final Quaternion rotate = new Quaternion().fromAngleAxis(MathUtils.HALF_PI, Vector3.NEG_UNIT_X); + _yRing.getMeshData().rotatePoints(rotate); + _yRing.getMeshData().rotateNormals(rotate); + _handle.attachChild(_yRing); + return this; + } + + public RotateWidget withZAxis() { + return withZAxis(new ColorRGBA(0, 0, 1, .65f)); + } + + public RotateWidget withZAxis(final ReadOnlyColorRGBA color) { + return withZAxis(color, 1.0f, 0.15f); + } + + public RotateWidget withZAxis(final ReadOnlyColorRGBA color, final float scale, final float width) { + if (_zRing != null) { + _zRing.removeFromParent(); + } + _zRing = new InteractRing("zRotRing", 4, 32, scale, width); + _zRing.setDefaultColor(color); + _handle.attachChild(_zRing); + return this; + } + + @Override + public void targetChanged(final InteractManager manager) { + if (_dragging) { + endDrag(manager); + } + final Spatial target = manager.getSpatialTarget(); + if (target != null) { + _handle.setScale(Math.max(RotateWidget.MIN_SCALE, target.getWorldBound().getRadius() + + target.getWorldTranslation().subtract(target.getWorldBound().getCenter(), _calcVec3A).length())); + } + targetDataUpdated(manager); + } + + protected void setRingRotations(final ReadOnlyMatrix3 rot) { + if (_xRing != null) { + _xRing.setRotation(rot); + } + if (_yRing != null) { + _yRing.setRotation(rot); + } + if (_zRing != null) { + _zRing.setRotation(rot); + } + } + + @Override + public void targetDataUpdated(final InteractManager manager) { + final Spatial target = manager.getSpatialTarget(); + if (target == null) { + _handle.setScale(1.0); + setRingRotations(Matrix3.IDENTITY); + } else { + // update scale of widget using bounding radius + target.updateGeometricState(0); + + // update ring rotations from target + if (_interactMatrix == InteractMatrix.Local) { + setRingRotations(target.getWorldRotation()); + } else { + setRingRotations(Matrix3.IDENTITY); + if (_lastRing != null) { + _lastRing.setRotation(_rotateStore); + } + } + } + } + + @Override + public void render(final Renderer renderer, final InteractManager manager) { + final Spatial spat = manager.getSpatialTarget(); + if (spat == null) { + return; + } + + _handle.setTranslation(spat.getWorldTranslation()); + _handle.updateGeometricState(0); + + renderer.draw(_handle); + } + + @Override + public void processInput(final Canvas source, final TwoInputStates inputStates, final AtomicBoolean inputConsumed, + final InteractManager manager) { + // Make sure we have something to modify + if (manager.getSpatialTarget() == null) { + return; + } + + // Make sure we are dragging. + final MouseState current = inputStates.getCurrent().getMouseState(); + final MouseState previous = inputStates.getPrevious().getMouseState(); + + if (current.getButtonsReleasedSince(previous).contains(_dragButton)) { + _rotateStore.setIdentity(); + if (_interactMatrix != InteractMatrix.Local) { + setRingRotations(Matrix3.IDENTITY); + } + } + + if (current.getButtonState(_dragButton) != ButtonState.DOWN) { + if (_dragging) { + endDrag(manager); + } + return; + } + // if we're already dragging, make sure we only act on drags that started with a positive pick. + else if (!current.getButtonsPressedSince(previous).contains(_dragButton) && !_dragging) { + return; + } + + final Camera camera = source.getCanvasRenderer().getCamera(); + final Vector2 oldMouse = new Vector2(previous.getX(), previous.getY()); + // Make sure we are dragging over the handle + if (!_dragging) { + findPick(oldMouse, camera); + final Vector3 lastPick = getLastPick(); + if (lastPick == null) { + return; + } else { + beginDrag(manager); + } + } + + // we've established that our mouse is being held down, and started over our arrow. So consume. + inputConsumed.set(true); + + // check if we've moved at all + if (current == previous || current.getDx() == 0 && current.getDy() == 0) { + return; + } + + // act on drag + final Spatial picked = (Spatial) _results.getPickData(0).getTarget(); + if (picked instanceof InteractRing) { + final InteractRing ring = (InteractRing) picked; + _lastRing = ring; + final ReadOnlyQuaternion rot = getNewAxisRotation(ring, oldMouse, current, camera, manager); + final Transform transform = manager.getSpatialState().getTransform(); + rot.toRotationMatrix(_calcMat3).multiply(transform.getMatrix(), _calcMat3); + transform.setRotation(_calcMat3); + + // apply our filters, if any, now that we've made updates. + applyFilters(manager); + } + } + + protected ReadOnlyQuaternion getNewAxisRotation(final InteractRing ring, final Vector2 oldMouse, + final MouseState current, final Camera camera, final InteractManager manager) { + // calculate a plane running through the ring we picked + _calcVec3A.set(_handle.getWorldTranslation()); + if (ring == _zRing || ring == _yRing) { + _calcVec3B.set(Vector3.UNIT_X); + } else { + _calcVec3B.set(Vector3.UNIT_Z); + } + + if (ring == _zRing || ring == _xRing) { + _calcVec3C.set(Vector3.UNIT_Y); + } else { + _calcVec3C.set(Vector3.UNIT_Z); + } + + // rotate to ring plane + ring.getRotation().applyPost(_calcVec3B, _calcVec3B); + ring.getRotation().applyPost(_calcVec3C, _calcVec3C); + + // make plane object + final Plane pickPlane = new Plane().setPlanePoints(_calcVec3A, _calcVec3B.addLocal(_calcVec3A), + _calcVec3C.addLocal(_calcVec3A)); + + // find out where we were hitting the plane before + getPickRay(oldMouse, camera); + if (!_calcRay.intersectsPlane(pickPlane, _calcVec3A)) { + return Quaternion.IDENTITY; + } + + // find out where we are hitting the plane now + getPickRay(new Vector2(current.getX(), current.getY()), camera); + if (!_calcRay.intersectsPlane(pickPlane, _calcVec3B)) { + return Quaternion.IDENTITY; + } + + // convert to vectors + _calcVec3A.subtractLocal(_handle.getWorldTranslation()); + _calcVec3B.subtractLocal(_handle.getWorldTranslation()); + + // apply to our interact matrix if used + if (_interactMatrix == InteractMatrix.World) { + _rotateStore.multiplyLocal(new Quaternion().fromVectorToVector(_calcVec3A, _calcVec3B).toRotationMatrix( + _calcMat3)); + } + + // convert to target coord space + final Node parent = manager.getSpatialTarget().getParent(); + if (parent != null) { + parent.getWorldTransform().applyInverseVector(_calcVec3A); + parent.getWorldTransform().applyInverseVector(_calcVec3B); + } + + // return a rotation to take us to the new rotation + return new Quaternion().fromVectorToVector(_calcVec3A, _calcVec3B); + } + + @Override + public void setInteractMatrix(final InteractMatrix matrix) { + if (_interactMatrix != matrix) { + _lastRing = null; + _interactMatrix = matrix; + } + } + + public InteractRing getXRing() { + return _xRing; + } + + public InteractRing getYRing() { + return _yRing; + } + + public InteractRing getZRing() { + return _zRing; + } +} diff --git a/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/widget/SimpleScaleWidget.java b/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/widget/SimpleScaleWidget.java index e03dda1..617c603 100644 --- a/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/widget/SimpleScaleWidget.java +++ b/ardor3d-extras/src/main/java/com/ardor3d/extension/interact/widget/SimpleScaleWidget.java @@ -1,184 +1,184 @@ -/**
- * Copyright (c) 2008-2012 Ardor Labs, Inc.
- *
- * This file is part of Ardor3D.
- *
- * Ardor3D is free software: you can redistribute it and/or modify it
- * under the terms of its license which may be found in the accompanying
- * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
- */
-
-package com.ardor3d.extension.interact.widget;
-
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import com.ardor3d.extension.interact.InteractManager;
-import com.ardor3d.framework.Canvas;
-import com.ardor3d.input.ButtonState;
-import com.ardor3d.input.MouseState;
-import com.ardor3d.input.logical.TwoInputStates;
-import com.ardor3d.math.ColorRGBA;
-import com.ardor3d.math.Plane;
-import com.ardor3d.math.Quaternion;
-import com.ardor3d.math.Vector2;
-import com.ardor3d.math.Vector3;
-import com.ardor3d.math.type.ReadOnlyColorRGBA;
-import com.ardor3d.math.type.ReadOnlyVector3;
-import com.ardor3d.renderer.Camera;
-import com.ardor3d.renderer.Renderer;
-import com.ardor3d.renderer.queue.RenderBucketType;
-import com.ardor3d.renderer.state.BlendState;
-import com.ardor3d.renderer.state.ZBufferState;
-import com.ardor3d.renderer.state.ZBufferState.TestFunction;
-import com.ardor3d.scenegraph.Spatial;
-import com.ardor3d.scenegraph.shape.Arrow;
-
-public class SimpleScaleWidget extends AbstractInteractWidget {
- public static double MIN_SCALE = 0.000001;
-
- protected ReadOnlyVector3 _arrowDirection;
-
- public SimpleScaleWidget() {}
-
- public SimpleScaleWidget withArrow(final ReadOnlyVector3 arrowDirection) {
- return withArrow(arrowDirection, new ColorRGBA(1.0f, 0.0f, 0.0f, 0.4f), 0, 0);
- }
-
- public SimpleScaleWidget withArrow(final ReadOnlyVector3 arrowDirection, final ReadOnlyColorRGBA color) {
- return withArrow(arrowDirection, color, 0, 0);
- }
-
- public SimpleScaleWidget withArrow(final ReadOnlyVector3 arrowDirection, final ReadOnlyColorRGBA color,
- final double lengthGap, final double tipGap) {
- _arrowDirection = new Vector3(arrowDirection);
- _handle = new InteractArrow("scaleHandle", 1.0, 0.125, lengthGap, tipGap);
- if (!_arrowDirection.equals(Vector3.UNIT_Z)) {
- _handle.setRotation(new Quaternion().fromVectorToVector(Vector3.UNIT_Z, _arrowDirection));
- }
-
- final BlendState blend = new BlendState();
- blend.setBlendEnabled(true);
- _handle.setRenderState(blend);
-
- ((Arrow) _handle).setDefaultColor(color);
-
- final ZBufferState zstate = new ZBufferState();
- zstate.setWritable(false);
- zstate.setFunction(TestFunction.Always);
- _handle.setRenderState(zstate);
-
- _handle.getSceneHints().setRenderBucketType(RenderBucketType.PostBucket);
- _handle.updateGeometricState(0);
- return this;
- }
-
- @Override
- public void targetChanged(final InteractManager manager) {
- if (_dragging) {
- endDrag(manager);
- }
- targetDataUpdated(manager);
- }
-
- @Override
- public void targetDataUpdated(final InteractManager manager) {
- final Spatial target = manager.getSpatialTarget();
- if (target == null) {
- _handle.setScale(1.0);
- } else {
- _handle.setScale(Math.max(SimpleScaleWidget.MIN_SCALE, target.getWorldBound().getRadius()));
- }
- }
-
- @Override
- public void render(final Renderer renderer, final InteractManager manager) {
- final Spatial spat = manager.getSpatialTarget();
- if (spat == null) {
- return;
- }
-
- _handle.setTranslation(spat.getWorldTranslation());
- _handle.updateGeometricState(0);
-
- renderer.draw(_handle);
- }
-
- @Override
- public void processInput(final Canvas source, final TwoInputStates inputStates, final AtomicBoolean inputConsumed,
- final InteractManager manager) {
- // Make sure we have something to modify
- if (manager.getSpatialTarget() == null) {
- return;
- }
-
- // Make sure we are dragging.
- final MouseState current = inputStates.getCurrent().getMouseState();
- final MouseState previous = inputStates.getPrevious().getMouseState();
- if (current.getButtonState(_dragButton) != ButtonState.DOWN) {
- if (_dragging) {
- endDrag(manager);
- }
- return;
- }
- // if we're already dragging, make sure we only act on drags that started with a positive pick.
- else if (!current.getButtonsPressedSince(previous).contains(_dragButton) && !_dragging) {
- return;
- }
-
- final Camera camera = source.getCanvasRenderer().getCamera();
- final Vector2 oldMouse = new Vector2(previous.getX(), previous.getY());
- // Make sure we are dragging over the arrow
- if (!_dragging) {
- findPick(oldMouse, camera);
- final Vector3 lastPick = getLastPick();
- if (lastPick == null) {
- return;
- } else {
- beginDrag(manager);
- }
- }
-
- // we've established that our mouse is being held down, and started over our arrow. So consume.
- inputConsumed.set(true);
-
- // check if we've moved at all
- if (current == previous || current.getDx() == 0 && current.getDy() == 0) {
- return;
- }
-
- // act on drag
- final double scale = getNewScale(oldMouse, current, camera, manager);
-
- // Set new scale on spatial state
- manager.getSpatialState().getTransform().setScale(scale);
-
- // apply our filters, if any, now that we've made updates.
- applyFilters(manager);
- }
-
- protected double getNewScale(final Vector2 oldMouse, final MouseState current, final Camera camera,
- final InteractManager manager) {
- // calculate a plane running through the Arrow and facing the camera.
- _calcVec3A.set(_handle.getWorldTranslation());
- _calcVec3B.set(_calcVec3A).addLocal(camera.getLeft());
- _calcVec3C.set(_calcVec3A).addLocal(_arrowDirection);
- final Plane pickPlane = new Plane().setPlanePoints(_calcVec3A, _calcVec3B, _calcVec3C);
-
- // find out where we were hitting the plane before
- getPickRay(oldMouse, camera);
- _calcRay.intersectsPlane(pickPlane, _calcVec3A);
- final double oldHeight = _calcVec3A.getY();
-
- // find out where we are hitting the plane now
- getPickRay(new Vector2(current.getX(), current.getY()), camera);
- _calcRay.intersectsPlane(pickPlane, _calcVec3A);
- final double newHeight = _calcVec3A.getY();
-
- // Use distance between points against arrow length to determine how big we need to grow our bounding radius
- final double delta = newHeight - oldHeight;
-
- final double oldRadius = manager.getSpatialTarget().getWorldBound().getRadius();
-
- return manager.getSpatialTarget().getScale().getY() * (1.0 + delta / oldRadius);
- }
-}
+/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.interact.widget; + +import java.util.concurrent.atomic.AtomicBoolean; + +import com.ardor3d.extension.interact.InteractManager; +import com.ardor3d.framework.Canvas; +import com.ardor3d.input.ButtonState; +import com.ardor3d.input.MouseState; +import com.ardor3d.input.logical.TwoInputStates; +import com.ardor3d.math.ColorRGBA; +import com.ardor3d.math.Plane; +import com.ardor3d.math.Quaternion; +import com.ardor3d.math.Vector2; +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyColorRGBA; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardor3d.renderer.Camera; +import com.ardor3d.renderer.Renderer; +import com.ardor3d.renderer.queue.RenderBucketType; +import com.ardor3d.renderer.state.BlendState; +import com.ardor3d.renderer.state.ZBufferState; +import com.ardor3d.renderer.state.ZBufferState.TestFunction; +import com.ardor3d.scenegraph.Spatial; +import com.ardor3d.scenegraph.shape.Arrow; + +public class SimpleScaleWidget extends AbstractInteractWidget { + public static double MIN_SCALE = 0.000001; + + protected ReadOnlyVector3 _arrowDirection; + + public SimpleScaleWidget() {} + + public SimpleScaleWidget withArrow(final ReadOnlyVector3 arrowDirection) { + return withArrow(arrowDirection, new ColorRGBA(1.0f, 0.0f, 0.0f, 0.4f), 0, 0); + } + + public SimpleScaleWidget withArrow(final ReadOnlyVector3 arrowDirection, final ReadOnlyColorRGBA color) { + return withArrow(arrowDirection, color, 0, 0); + } + + public SimpleScaleWidget withArrow(final ReadOnlyVector3 arrowDirection, final ReadOnlyColorRGBA color, + final double lengthGap, final double tipGap) { + _arrowDirection = new Vector3(arrowDirection); + _handle = new InteractArrow("scaleHandle", 1.0, 0.125, lengthGap, tipGap); + if (!_arrowDirection.equals(Vector3.UNIT_Z)) { + _handle.setRotation(new Quaternion().fromVectorToVector(Vector3.UNIT_Z, _arrowDirection)); + } + + final BlendState blend = new BlendState(); + blend.setBlendEnabled(true); + _handle.setRenderState(blend); + + ((Arrow) _handle).setDefaultColor(color); + + final ZBufferState zstate = new ZBufferState(); + zstate.setWritable(false); + zstate.setFunction(TestFunction.Always); + _handle.setRenderState(zstate); + + _handle.getSceneHints().setRenderBucketType(RenderBucketType.PostBucket); + _handle.updateGeometricState(0); + return this; + } + + @Override + public void targetChanged(final InteractManager manager) { + if (_dragging) { + endDrag(manager); + } + targetDataUpdated(manager); + } + + @Override + public void targetDataUpdated(final InteractManager manager) { + final Spatial target = manager.getSpatialTarget(); + if (target == null) { + _handle.setScale(1.0); + } else { + _handle.setScale(Math.max(SimpleScaleWidget.MIN_SCALE, target.getWorldBound().getRadius())); + } + } + + @Override + public void render(final Renderer renderer, final InteractManager manager) { + final Spatial spat = manager.getSpatialTarget(); + if (spat == null) { + return; + } + + _handle.setTranslation(spat.getWorldTranslation()); + _handle.updateGeometricState(0); + + renderer.draw(_handle); + } + + @Override + public void processInput(final Canvas source, final TwoInputStates inputStates, final AtomicBoolean inputConsumed, + final InteractManager manager) { + // Make sure we have something to modify + if (manager.getSpatialTarget() == null) { + return; + } + + // Make sure we are dragging. + final MouseState current = inputStates.getCurrent().getMouseState(); + final MouseState previous = inputStates.getPrevious().getMouseState(); + if (current.getButtonState(_dragButton) != ButtonState.DOWN) { + if (_dragging) { + endDrag(manager); + } + return; + } + // if we're already dragging, make sure we only act on drags that started with a positive pick. + else if (!current.getButtonsPressedSince(previous).contains(_dragButton) && !_dragging) { + return; + } + + final Camera camera = source.getCanvasRenderer().getCamera(); + final Vector2 oldMouse = new Vector2(previous.getX(), previous.getY()); + // Make sure we are dragging over the arrow + if (!_dragging) { + findPick(oldMouse, camera); + final Vector3 lastPick = getLastPick(); + if (lastPick == null) { + return; + } else { + beginDrag(manager); + } + } + + // we've established that our mouse is being held down, and started over our arrow. So consume. + inputConsumed.set(true); + + // check if we've moved at all + if (current == previous || current.getDx() == 0 && current.getDy() == 0) { + return; + } + + // act on drag + final double scale = getNewScale(oldMouse, current, camera, manager); + + // Set new scale on spatial state + manager.getSpatialState().getTransform().setScale(scale); + + // apply our filters, if any, now that we've made updates. + applyFilters(manager); + } + + protected double getNewScale(final Vector2 oldMouse, final MouseState current, final Camera camera, + final InteractManager manager) { + // calculate a plane running through the Arrow and facing the camera. + _calcVec3A.set(_handle.getWorldTranslation()); + _calcVec3B.set(_calcVec3A).addLocal(camera.getLeft()); + _calcVec3C.set(_calcVec3A).addLocal(_arrowDirection); + final Plane pickPlane = new Plane().setPlanePoints(_calcVec3A, _calcVec3B, _calcVec3C); + + // find out where we were hitting the plane before + getPickRay(oldMouse, camera); + _calcRay.intersectsPlane(pickPlane, _calcVec3A); + final double oldHeight = _calcVec3A.getY(); + + // find out where we are hitting the plane now + getPickRay(new Vector2(current.getX(), current.getY()), camera); + _calcRay.intersectsPlane(pickPlane, _calcVec3A); + final double newHeight = _calcVec3A.getY(); + + // Use distance between points against arrow length to determine how big we need to grow our bounding radius + final double delta = newHeight - oldHeight; + + final double oldRadius = manager.getSpatialTarget().getWorldBound().getRadius(); + + return manager.getSpatialTarget().getScale().getY() * (1.0 + delta / oldRadius); + } +} diff --git a/ardor3d-extras/src/main/java/com/ardor3d/extension/model/md2/Md2DataStore.java b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/md2/Md2DataStore.java index 52af183..a2c12b1 100644 --- a/ardor3d-extras/src/main/java/com/ardor3d/extension/model/md2/Md2DataStore.java +++ b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/md2/Md2DataStore.java @@ -1,52 +1,52 @@ -/**
- * Copyright (c) 2008-2012 Ardor Labs, Inc.
- *
- * This file is part of Ardor3D.
- *
- * Ardor3D is free software: you can redistribute it and/or modify it
- * under the terms of its license which may be found in the accompanying
- * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
- */
-
-package com.ardor3d.extension.model.md2;
-
-import java.util.List;
-
-import com.ardor3d.extension.model.util.KeyframeController;
-import com.ardor3d.scenegraph.Mesh;
-import com.google.common.collect.Lists;
-
-public class Md2DataStore {
-
- private final Mesh _mainMesh;
- private final KeyframeController<Mesh> _controller;
-
- private final List<String> _frameNames = Lists.newArrayList();
-
- private final List<String> _skinNames = Lists.newArrayList();
-
- public Md2DataStore(final Mesh mainMesh, final KeyframeController<Mesh> controller) {
- _mainMesh = mainMesh;
- _controller = controller;
- }
-
- public Mesh getScene() {
- return _mainMesh;
- }
-
- public KeyframeController<Mesh> getController() {
- return _controller;
- }
-
- public List<String> getFrameNames() {
- return _frameNames;
- }
-
- public int getFrameIndex(final String frameName) {
- return _frameNames.indexOf(frameName);
- }
-
- public List<String> getSkinNames() {
- return _skinNames;
- }
-}
+/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.model.md2; + +import java.util.List; + +import com.ardor3d.extension.model.util.KeyframeController; +import com.ardor3d.scenegraph.Mesh; +import com.google.common.collect.Lists; + +public class Md2DataStore { + + private final Mesh _mainMesh; + private final KeyframeController<Mesh> _controller; + + private final List<String> _frameNames = Lists.newArrayList(); + + private final List<String> _skinNames = Lists.newArrayList(); + + public Md2DataStore(final Mesh mainMesh, final KeyframeController<Mesh> controller) { + _mainMesh = mainMesh; + _controller = controller; + } + + public Mesh getScene() { + return _mainMesh; + } + + public KeyframeController<Mesh> getController() { + return _controller; + } + + public List<String> getFrameNames() { + return _frameNames; + } + + public int getFrameIndex(final String frameName) { + return _frameNames.indexOf(frameName); + } + + public List<String> getSkinNames() { + return _skinNames; + } +} diff --git a/ardor3d-extras/src/main/java/com/ardor3d/extension/model/md2/Md2Frame.java b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/md2/Md2Frame.java index f6e75d2..8c6b8c1 100644 --- a/ardor3d-extras/src/main/java/com/ardor3d/extension/model/md2/Md2Frame.java +++ b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/md2/Md2Frame.java @@ -1,36 +1,36 @@ -/**
- * Copyright (c) 2008-2012 Ardor Labs, Inc.
- *
- * This file is part of Ardor3D.
- *
- * Ardor3D is free software: you can redistribute it and/or modify it
- * under the terms of its license which may be found in the accompanying
- * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
- */
-
-package com.ardor3d.extension.model.md2;
-
-import com.ardor3d.math.Vector3;
-import com.ardor3d.math.type.ReadOnlyVector3;
-
-final class Md2Frame {
-
- /** frame name */
- String name; // char [16]
-
- /** scale factor */
- final Vector3 scale = new Vector3(1, 1, 1);
-
- /** translation vector */
- final Vector3 translate = new Vector3();
-
- /** vertex data */
- byte[] vertData;
-
- Md2Frame(final byte[] vertData, final String name, final ReadOnlyVector3 scale, final ReadOnlyVector3 translate) {
- this.vertData = vertData;
- this.scale.set(scale);
- this.translate.set(translate);
- this.name = name;
- }
-}
+/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.model.md2; + +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyVector3; + +final class Md2Frame { + + /** frame name */ + String name; // char [16] + + /** scale factor */ + final Vector3 scale = new Vector3(1, 1, 1); + + /** translation vector */ + final Vector3 translate = new Vector3(); + + /** vertex data */ + byte[] vertData; + + Md2Frame(final byte[] vertData, final String name, final ReadOnlyVector3 scale, final ReadOnlyVector3 translate) { + this.vertData = vertData; + this.scale.set(scale); + this.translate.set(translate); + this.name = name; + } +} diff --git a/ardor3d-extras/src/main/java/com/ardor3d/extension/model/md2/Md2Header.java b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/md2/Md2Header.java index 992c018..2dc7bf7 100644 --- a/ardor3d-extras/src/main/java/com/ardor3d/extension/model/md2/Md2Header.java +++ b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/md2/Md2Header.java @@ -1,78 +1,78 @@ -/**
- * Copyright (c) 2008-2012 Ardor Labs, Inc.
- *
- * This file is part of Ardor3D.
- *
- * Ardor3D is free software: you can redistribute it and/or modify it
- * under the terms of its license which may be found in the accompanying
- * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
- */
-
-package com.ardor3d.extension.model.md2;
-
-/**
- * Header of MD2: see also http://tfc.duke.free.fr/coding/md2-specs-en.html
- */
-final class Md2Header {
- /** identifier of the file: magic number: "IDP2" */
- final int magic;
- /** version number of the file (must be 8) */
- final int version;
-
- /** texture width in pixels */
- final int skinWidth;
- /** texture height in pixels */
- final int skinHeight;
-
- /** size in bytes of a frame */
- final int frameSize;
-
- /** number of textures associated with the model */
- final int numSkins;
- /** number of vertices per frame */
- final int numVertices;
- /** number of texture coordinates */
- final int numTexCoords;
- /** number of triangles */
- final int numTriangles;
- /** number of gl commands */
- final int numGlCommands;
- /** number of animation frames */
- final int numFrames;
-
- /** offset in the file for the texture data */
- final int offsetSkins;
- /** offset in the file for the texture coords */
- final int offsetTexCoords;
- /** offset in the file for the face data */
- final int offsetTriangles;
- /** offset in the file for the frames data */
- final int offsetFrames;
- /** offset in the file for the gl commands data */
- final int offsetGlCommands;
- /** offset of EOF */
- final int offsetEnd;
-
- Md2Header(final int magic, final int version, final int skinWidth, final int skinHeight, final int frameSize,
- final int numSkins, final int numVertices, final int numTexCoords, final int numTriangles,
- final int numGlCommands, final int numFrames, final int offsetSkins, final int offsetTexCoords,
- final int offsetTriangles, final int offsetFrames, final int offsetGlCommands, final int offsetEnd) {
- this.magic = magic;
- this.version = version;
- this.skinWidth = skinWidth;
- this.skinHeight = skinHeight;
- this.frameSize = frameSize;
- this.numSkins = numSkins;
- this.numVertices = numVertices;
- this.numTexCoords = numTexCoords;
- this.numTriangles = numTriangles;
- this.numGlCommands = numGlCommands;
- this.numFrames = numFrames;
- this.offsetSkins = offsetSkins;
- this.offsetTexCoords = offsetTexCoords;
- this.offsetTriangles = offsetTriangles;
- this.offsetFrames = offsetFrames;
- this.offsetGlCommands = offsetGlCommands;
- this.offsetEnd = offsetEnd;
- }
-}
+/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.model.md2; + +/** + * Header of MD2: see also http://tfc.duke.free.fr/coding/md2-specs-en.html + */ +final class Md2Header { + /** identifier of the file: magic number: "IDP2" */ + final int magic; + /** version number of the file (must be 8) */ + final int version; + + /** texture width in pixels */ + final int skinWidth; + /** texture height in pixels */ + final int skinHeight; + + /** size in bytes of a frame */ + final int frameSize; + + /** number of textures associated with the model */ + final int numSkins; + /** number of vertices per frame */ + final int numVertices; + /** number of texture coordinates */ + final int numTexCoords; + /** number of triangles */ + final int numTriangles; + /** number of gl commands */ + final int numGlCommands; + /** number of animation frames */ + final int numFrames; + + /** offset in the file for the texture data */ + final int offsetSkins; + /** offset in the file for the texture coords */ + final int offsetTexCoords; + /** offset in the file for the face data */ + final int offsetTriangles; + /** offset in the file for the frames data */ + final int offsetFrames; + /** offset in the file for the gl commands data */ + final int offsetGlCommands; + /** offset of EOF */ + final int offsetEnd; + + Md2Header(final int magic, final int version, final int skinWidth, final int skinHeight, final int frameSize, + final int numSkins, final int numVertices, final int numTexCoords, final int numTriangles, + final int numGlCommands, final int numFrames, final int offsetSkins, final int offsetTexCoords, + final int offsetTriangles, final int offsetFrames, final int offsetGlCommands, final int offsetEnd) { + this.magic = magic; + this.version = version; + this.skinWidth = skinWidth; + this.skinHeight = skinHeight; + this.frameSize = frameSize; + this.numSkins = numSkins; + this.numVertices = numVertices; + this.numTexCoords = numTexCoords; + this.numTriangles = numTriangles; + this.numGlCommands = numGlCommands; + this.numFrames = numFrames; + this.offsetSkins = offsetSkins; + this.offsetTexCoords = offsetTexCoords; + this.offsetTriangles = offsetTriangles; + this.offsetFrames = offsetFrames; + this.offsetGlCommands = offsetGlCommands; + this.offsetEnd = offsetEnd; + } +} diff --git a/ardor3d-extras/src/main/java/com/ardor3d/extension/model/md2/Md2Importer.java b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/md2/Md2Importer.java index 6fbc793..6270b66 100644 --- a/ardor3d-extras/src/main/java/com/ardor3d/extension/model/md2/Md2Importer.java +++ b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/md2/Md2Importer.java @@ -1,420 +1,420 @@ -/**
- * Copyright (c) 2008-2012 Ardor Labs, Inc.
- *
- * This file is part of Ardor3D.
- *
- * Ardor3D is free software: you can redistribute it and/or modify it
- * under the terms of its license which may be found in the accompanying
- * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
- */
-
-package com.ardor3d.extension.model.md2;
-
-import java.io.InputStream;
-import java.util.List;
-
-import com.ardor3d.bounding.BoundingBox;
-import com.ardor3d.extension.model.util.KeyframeController;
-import com.ardor3d.image.Texture;
-import com.ardor3d.image.TextureStoreFormat;
-import com.ardor3d.image.Texture.MinificationFilter;
-import com.ardor3d.math.Vector3;
-import com.ardor3d.renderer.IndexMode;
-import com.ardor3d.renderer.state.TextureState;
-import com.ardor3d.scenegraph.FloatBufferData;
-import com.ardor3d.scenegraph.Mesh;
-import com.ardor3d.scenegraph.MeshData;
-import com.ardor3d.util.Ardor3dException;
-import com.ardor3d.util.LittleEndianRandomAccessDataInput;
-import com.ardor3d.util.TextureManager;
-import com.ardor3d.util.resource.ResourceLocator;
-import com.ardor3d.util.resource.ResourceLocatorTool;
-import com.ardor3d.util.resource.ResourceSource;
-import com.google.common.collect.Lists;
-
-public class Md2Importer {
-
- private final Vector3 calcVert = new Vector3();
-
- private boolean _loadTextures = true;
- private ResourceLocator _textureLocator;
- private ResourceLocator _modelLocator;
-
- // texture defaults
- private MinificationFilter _minificationFilter = MinificationFilter.Trilinear;
- private boolean _useCompression = true;
- private boolean _flipTextureVertically = false;
-
- public boolean isLoadTextures() {
- return _loadTextures;
- }
-
- public Md2Importer setLoadTextures(final boolean loadTextures) {
- _loadTextures = loadTextures;
- return this;
- }
-
- public Md2Importer setTextureLocator(final ResourceLocator locator) {
- _textureLocator = locator;
- return this;
- }
-
- public Md2Importer setModelLocator(final ResourceLocator locator) {
- _modelLocator = locator;
- return this;
- }
-
- public Md2Importer setFlipTextureVertically(final boolean flipTextureVertically) {
- _flipTextureVertically = flipTextureVertically;
- return this;
- }
-
- public boolean isFlipTextureVertically() {
- return _flipTextureVertically;
- }
-
- public Md2Importer setUseCompression(final boolean useCompression) {
- _useCompression = useCompression;
- return this;
- }
-
- public boolean isUseCompression() {
- return _useCompression;
- }
-
- public Md2Importer setMinificationFilter(final MinificationFilter minificationFilter) {
- _minificationFilter = minificationFilter;
- return this;
- }
-
- public MinificationFilter getMinificationFilter() {
- return _minificationFilter;
- }
-
- /**
- * Reads an MD2 file from the given resource
- *
- * @param resource
- * a resource pointing to the model we wish to load.
- * @return an Md2DataStore data object containing the scene and other useful elements.
- */
- public Md2DataStore load(final ResourceSource resource) {
- if (resource == null) {
- throw new NullPointerException("Unable to load null resource");
- }
-
- try {
- final InputStream md2Stream = resource.openStream();
- if (md2Stream == null) {
- throw new NullPointerException("Unable to load null streams");
- }
- // final Md2DataStore store = new Md2DataStore();
- final LittleEndianRandomAccessDataInput bis = new LittleEndianRandomAccessDataInput(md2Stream);
-
- // parse the header
- final Md2Header header = new Md2Header(bis.readInt(), bis.readInt(), bis.readInt(), bis.readInt(), bis
- .readInt(), bis.readInt(), bis.readInt(), bis.readInt(), bis.readInt(), bis.readInt(), bis
- .readInt(), bis.readInt(), bis.readInt(), bis.readInt(), bis.readInt(), bis.readInt(), bis
- .readInt());
-
- // Check magic word and version
- if (header.magic != ('2' << 24) + ('P' << 16) + ('D' << 8) + 'I') {
- throw new Ardor3dException("Not an MD2 file.");
- }
- if (header.version != 8) {
- throw new Ardor3dException("Invalid file version (Version not 8)!");
- }
-
- // Parse out texture names
- final String[] texNames = new String[header.numSkins];
- bis.seek(header.offsetSkins);
- for (int i = 0; i < header.numSkins; i++) {
- texNames[i] = bis.readString(64);
- }
-
- // Parse out tex coords
- final float[] texCoords = new float[2 * header.numTexCoords];
- bis.seek(header.offsetTexCoords);
- final float inverseWidth = 1f / header.skinWidth;
- final float inverseHeight = 1f / header.skinHeight;
- for (int i = 0; i < header.numTexCoords; i++) {
- texCoords[i * 2 + 0] = bis.readShort() * inverseWidth;
- texCoords[i * 2 + 1] = bis.readShort() * inverseHeight;
- }
-
- // Parse out triangles
- final short[] triangles = new short[header.numTriangles * 6];
- bis.seek(header.offsetTriangles);
- for (int i = 0; i < header.numTriangles; i++) {
- triangles[i * 6 + 0] = bis.readShort(); // vert index 0
- triangles[i * 6 + 1] = bis.readShort(); // vert index 1
- triangles[i * 6 + 2] = bis.readShort(); // vert index 2
- triangles[i * 6 + 3] = bis.readShort(); // texcoord index 0
- triangles[i * 6 + 4] = bis.readShort(); // texcoord index 1
- triangles[i * 6 + 5] = bis.readShort(); // texcoord index 2
- }
-
- // Parse out gl commands
- final Md2GlCommand[] commands = new Md2GlCommand[header.numGlCommands];
- bis.seek(header.offsetGlCommands);
- int length, absLength;
- Md2GlCommand cmd;
- final List<Integer> fanIndices = Lists.newArrayList();
- final List<Integer> stripIndices = Lists.newArrayList();
- for (int i = 0; i < header.numGlCommands; i++) {
- length = bis.readInt();
- if (length == 0) {
- break;
- }
- absLength = Math.abs(length);
- commands[i] = cmd = new Md2GlCommand(length >= 0 ? IndexMode.TriangleStrip : IndexMode.TriangleFan,
- absLength);
- if (cmd.mode == IndexMode.TriangleFan) {
- fanIndices.add(i);
- } else {
- stripIndices.add(i);
- }
- for (int j = 0; j < absLength; j++) {
- cmd.texCoords[j * 2 + 0] = bis.readFloat();
- cmd.texCoords[j * 2 + 1] = bis.readFloat();
- cmd.vertIndices[j] = bis.readInt();
- }
- }
-
- // Parse out frames
- final Md2Frame[] frames = new Md2Frame[header.numFrames];
- bis.seek(header.offsetFrames);
- final Vector3 scale = new Vector3();
- final Vector3 translate = new Vector3();
- for (int i = 0; i < header.numFrames; i++) {
- scale.set(bis.readFloat(), bis.readFloat(), bis.readFloat());
- translate.set(bis.readFloat(), bis.readFloat(), bis.readFloat());
- final String name = bis.readString(16);
- final byte[] vertData = new byte[header.numVertices * 4];
- bis.readFully(vertData);
- frames[i] = new Md2Frame(vertData, name, scale, translate);
- }
-
- // make index modes/counts to be used throughout meshes
- int vertexCount = 0;
- int fanIndex = stripIndices.size() != 0 ? 1 : 0;
- final IndexMode[] modes = new IndexMode[fanIndices.size() + fanIndex];
- final int[] counts = new int[modes.length];
- for (final Integer index : fanIndices) {
- counts[fanIndex] = commands[index].vertIndices.length;
- modes[fanIndex] = IndexMode.TriangleFan;
- vertexCount += counts[fanIndex];
- fanIndex++;
- }
- if (stripIndices.size() != 0) {
- int triCounts = 0;
- int vertCount;
- int extra = 0;
- for (final Integer index : stripIndices) {
- vertCount = commands[index].vertIndices.length;
- extra = vertCount % 2 == 1 ? 3 : 2;
- triCounts += vertCount + extra;
- }
- counts[0] = triCounts - extra + 1;
- modes[0] = IndexMode.TriangleStrip;
- vertexCount += counts[0];
- }
-
- vertexCount++;
-
- // Create each frame as a Mesh using glcommands if given
- final Mesh[] meshes = new Mesh[header.numFrames];
- MeshData mData;
- for (int i = 0; i < header.numFrames; i++) {
- final Md2Frame frame = frames[i];
-
- meshes[i] = new Mesh(frames[i].name);
- mData = meshes[i].getMeshData();
- mData.setIndexLengths(counts);
- mData.setIndexModes(modes);
-
- final FloatBufferData verts = new FloatBufferData(vertexCount * 3, 3);
- final FloatBufferData norms = new FloatBufferData(vertexCount * 3, 3);
- final FloatBufferData texs = new FloatBufferData(vertexCount * 3, 2);
- mData.setVertexCoords(verts);
- mData.setNormalCoords(norms);
- mData.setTextureCoords(texs, 0);
-
- // go through the triangle strips/fans and add them in
- // first the strips
- if (stripIndices.size() != 0) {
- for (int maxJ = stripIndices.size(), j = 0; j < maxJ; j++) {
- cmd = commands[stripIndices.get(j)];
- if (cmd.vertIndices.length < 3) {
- continue;
- }
-
- addVert(cmd, frame, 0, verts);
- norms.getBuffer().put(0).put(0).put(0);
- texs.getBuffer().put(0).put(0);
-
- // add strip verts / normals
- for (int k = 0; k < cmd.vertIndices.length; k++) {
- addVert(cmd, frame, k, verts);
- addNormal(cmd, frame, k, norms);
- }
-
- // add strip tex coords
- texs.getBuffer().put(cmd.texCoords);
-
- // if we're not the last strip, add a vert or two for degenerate triangle connector
- if (j != maxJ - 1) {
- addVert(cmd, frame, cmd.vertIndices.length - 1, verts);
- norms.getBuffer().put(0).put(0).put(0);
- texs.getBuffer().put(0).put(0);
- if (cmd.vertIndices.length % 2 == 1) {
- // extra vert to maintain wind order
- addVert(cmd, frame, cmd.vertIndices.length - 1, verts);
- norms.getBuffer().put(0).put(0).put(0);
- texs.getBuffer().put(0).put(0);
- }
- }
- }
- }
- // Now the fans
- // XXX: could add these to the strip instead
- for (final int j : fanIndices) {
- cmd = commands[j];
- texs.getBuffer().put(cmd.texCoords[0]).put(cmd.texCoords[1]);
- addNormal(cmd, frame, 0, norms);
- addVert(cmd, frame, 0, verts);
- for (int k = cmd.vertIndices.length; --k >= 1;) {
- texs.getBuffer().put(cmd.texCoords[k * 2]).put(cmd.texCoords[k * 2 + 1]);
- addNormal(cmd, frame, k, norms);
- addVert(cmd, frame, k, verts);
- }
- }
- }
-
- // Clone frame 0 as mesh for initial mesh
- final Mesh mesh = meshes[0].makeCopy(false);
- mesh.setModelBound(new BoundingBox());
-
- // Use resource name for mesh
- mesh.setName(resource.getName());
-
- // Add controller
- final KeyframeController<Mesh> controller = new KeyframeController<Mesh>();
- mesh.addController(controller);
- controller.setMorphingMesh(mesh);
- controller.setInterpTex(false);
- int i = 0;
- for (final Mesh meshX : meshes) {
- controller.setKeyframe(i, meshX);
- i++;
- }
-
- // Make a store object to return
- final Md2DataStore store = new Md2DataStore(mesh, controller);
-
- // store names
- for (final Md2Frame frame : frames) {
- store.getFrameNames().add(frame.name);
- }
-
- // store skin names
- for (final String name : texNames) {
- store.getSkinNames().add(name);
- }
-
- // Apply our texture
- if (isLoadTextures()) {
- Texture tex = null;
- for (final String name : texNames) {
- tex = loadTexture(name);
- if (tex != null) {
- break;
- }
- }
-
- // try using model name
- if (tex == null) {
- tex = loadTexture(resource.getName());
- }
-
- if (tex != null) {
- final TextureState ts = new TextureState();
- ts.setTexture(tex);
- mesh.setRenderState(ts);
- }
- }
-
- return store;
- } catch (final Exception e) {
- throw new Error("Unable to load md2 resource from URL: " + resource, e);
- }
- }
-
- private Texture loadTexture(final String name) {
- Texture tex = null;
- if (_textureLocator == null) {
- tex = TextureManager.load(name, getMinificationFilter(),
- isUseCompression() ? TextureStoreFormat.GuessCompressedFormat
- : TextureStoreFormat.GuessNoCompressedFormat, isFlipTextureVertically());
- } else {
- final ResourceSource source = _textureLocator.locateResource(name);
- if (source != null) {
- tex = TextureManager.load(source, getMinificationFilter(),
- isUseCompression() ? TextureStoreFormat.GuessCompressedFormat
- : TextureStoreFormat.GuessNoCompressedFormat, isFlipTextureVertically());
- }
- }
- return tex;
- }
-
- private void addNormal(final Md2GlCommand cmd, final Md2Frame frame, final int normalIndex,
- final FloatBufferData norms) {
- final int index = cmd.vertIndices[normalIndex];
- final byte[] vertData = frame.vertData;
- Md2Normals.getNormalVector(vertData[index * 4 + 3], calcVert);
- norms.getBuffer().put(calcVert.getXf()).put(calcVert.getYf()).put(calcVert.getZf());
- }
-
- private void addVert(final Md2GlCommand cmd, final Md2Frame frame, final int vertIndex, final FloatBufferData verts) {
- final int index = cmd.vertIndices[vertIndex];
- final byte[] vertData = frame.vertData;
- calcVert.set((vertData[index * 4 + 0] & 0xFF), (vertData[index * 4 + 1] & 0xFF),
- (vertData[index * 4 + 2] & 0xFF));
- calcVert.multiplyLocal(frame.scale).addLocal(frame.translate);
- verts.getBuffer().put(calcVert.getXf()).put(calcVert.getYf()).put(calcVert.getZf());
- }
-
- /**
- * Reads a MD2 file from the given resource
- *
- * @param resource
- * the name of the resource to find.
- * @return an ObjGeometryStore data object containing the scene and other useful elements.
- */
- public Md2DataStore load(final String resource) {
- final ResourceSource source;
- if (_modelLocator == null) {
- source = ResourceLocatorTool.locateResource(ResourceLocatorTool.TYPE_MODEL, resource);
- } else {
- source = _modelLocator.locateResource(resource);
- }
-
- if (source == null) {
- throw new Error("Unable to locate '" + resource + "'");
- }
-
- return load(source);
- }
-
- static class Md2GlCommand {
- IndexMode mode;
- float[] texCoords;
- int[] vertIndices;
-
- Md2GlCommand(final IndexMode indexMode, final int length) {
- mode = indexMode;
- texCoords = new float[length * 2];
- vertIndices = new int[length];
- }
- }
-}
+/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.model.md2; + +import java.io.InputStream; +import java.util.List; + +import com.ardor3d.bounding.BoundingBox; +import com.ardor3d.extension.model.util.KeyframeController; +import com.ardor3d.image.Texture; +import com.ardor3d.image.TextureStoreFormat; +import com.ardor3d.image.Texture.MinificationFilter; +import com.ardor3d.math.Vector3; +import com.ardor3d.renderer.IndexMode; +import com.ardor3d.renderer.state.TextureState; +import com.ardor3d.scenegraph.FloatBufferData; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.scenegraph.MeshData; +import com.ardor3d.util.Ardor3dException; +import com.ardor3d.util.LittleEndianRandomAccessDataInput; +import com.ardor3d.util.TextureManager; +import com.ardor3d.util.resource.ResourceLocator; +import com.ardor3d.util.resource.ResourceLocatorTool; +import com.ardor3d.util.resource.ResourceSource; +import com.google.common.collect.Lists; + +public class Md2Importer { + + private final Vector3 calcVert = new Vector3(); + + private boolean _loadTextures = true; + private ResourceLocator _textureLocator; + private ResourceLocator _modelLocator; + + // texture defaults + private MinificationFilter _minificationFilter = MinificationFilter.Trilinear; + private boolean _useCompression = true; + private boolean _flipTextureVertically = false; + + public boolean isLoadTextures() { + return _loadTextures; + } + + public Md2Importer setLoadTextures(final boolean loadTextures) { + _loadTextures = loadTextures; + return this; + } + + public Md2Importer setTextureLocator(final ResourceLocator locator) { + _textureLocator = locator; + return this; + } + + public Md2Importer setModelLocator(final ResourceLocator locator) { + _modelLocator = locator; + return this; + } + + public Md2Importer setFlipTextureVertically(final boolean flipTextureVertically) { + _flipTextureVertically = flipTextureVertically; + return this; + } + + public boolean isFlipTextureVertically() { + return _flipTextureVertically; + } + + public Md2Importer setUseCompression(final boolean useCompression) { + _useCompression = useCompression; + return this; + } + + public boolean isUseCompression() { + return _useCompression; + } + + public Md2Importer setMinificationFilter(final MinificationFilter minificationFilter) { + _minificationFilter = minificationFilter; + return this; + } + + public MinificationFilter getMinificationFilter() { + return _minificationFilter; + } + + /** + * Reads an MD2 file from the given resource + * + * @param resource + * a resource pointing to the model we wish to load. + * @return an Md2DataStore data object containing the scene and other useful elements. + */ + public Md2DataStore load(final ResourceSource resource) { + if (resource == null) { + throw new NullPointerException("Unable to load null resource"); + } + + try { + final InputStream md2Stream = resource.openStream(); + if (md2Stream == null) { + throw new NullPointerException("Unable to load null streams"); + } + // final Md2DataStore store = new Md2DataStore(); + final LittleEndianRandomAccessDataInput bis = new LittleEndianRandomAccessDataInput(md2Stream); + + // parse the header + final Md2Header header = new Md2Header(bis.readInt(), bis.readInt(), bis.readInt(), bis.readInt(), bis + .readInt(), bis.readInt(), bis.readInt(), bis.readInt(), bis.readInt(), bis.readInt(), bis + .readInt(), bis.readInt(), bis.readInt(), bis.readInt(), bis.readInt(), bis.readInt(), bis + .readInt()); + + // Check magic word and version + if (header.magic != ('2' << 24) + ('P' << 16) + ('D' << 8) + 'I') { + throw new Ardor3dException("Not an MD2 file."); + } + if (header.version != 8) { + throw new Ardor3dException("Invalid file version (Version not 8)!"); + } + + // Parse out texture names + final String[] texNames = new String[header.numSkins]; + bis.seek(header.offsetSkins); + for (int i = 0; i < header.numSkins; i++) { + texNames[i] = bis.readString(64); + } + + // Parse out tex coords + final float[] texCoords = new float[2 * header.numTexCoords]; + bis.seek(header.offsetTexCoords); + final float inverseWidth = 1f / header.skinWidth; + final float inverseHeight = 1f / header.skinHeight; + for (int i = 0; i < header.numTexCoords; i++) { + texCoords[i * 2 + 0] = bis.readShort() * inverseWidth; + texCoords[i * 2 + 1] = bis.readShort() * inverseHeight; + } + + // Parse out triangles + final short[] triangles = new short[header.numTriangles * 6]; + bis.seek(header.offsetTriangles); + for (int i = 0; i < header.numTriangles; i++) { + triangles[i * 6 + 0] = bis.readShort(); // vert index 0 + triangles[i * 6 + 1] = bis.readShort(); // vert index 1 + triangles[i * 6 + 2] = bis.readShort(); // vert index 2 + triangles[i * 6 + 3] = bis.readShort(); // texcoord index 0 + triangles[i * 6 + 4] = bis.readShort(); // texcoord index 1 + triangles[i * 6 + 5] = bis.readShort(); // texcoord index 2 + } + + // Parse out gl commands + final Md2GlCommand[] commands = new Md2GlCommand[header.numGlCommands]; + bis.seek(header.offsetGlCommands); + int length, absLength; + Md2GlCommand cmd; + final List<Integer> fanIndices = Lists.newArrayList(); + final List<Integer> stripIndices = Lists.newArrayList(); + for (int i = 0; i < header.numGlCommands; i++) { + length = bis.readInt(); + if (length == 0) { + break; + } + absLength = Math.abs(length); + commands[i] = cmd = new Md2GlCommand(length >= 0 ? IndexMode.TriangleStrip : IndexMode.TriangleFan, + absLength); + if (cmd.mode == IndexMode.TriangleFan) { + fanIndices.add(i); + } else { + stripIndices.add(i); + } + for (int j = 0; j < absLength; j++) { + cmd.texCoords[j * 2 + 0] = bis.readFloat(); + cmd.texCoords[j * 2 + 1] = bis.readFloat(); + cmd.vertIndices[j] = bis.readInt(); + } + } + + // Parse out frames + final Md2Frame[] frames = new Md2Frame[header.numFrames]; + bis.seek(header.offsetFrames); + final Vector3 scale = new Vector3(); + final Vector3 translate = new Vector3(); + for (int i = 0; i < header.numFrames; i++) { + scale.set(bis.readFloat(), bis.readFloat(), bis.readFloat()); + translate.set(bis.readFloat(), bis.readFloat(), bis.readFloat()); + final String name = bis.readString(16); + final byte[] vertData = new byte[header.numVertices * 4]; + bis.readFully(vertData); + frames[i] = new Md2Frame(vertData, name, scale, translate); + } + + // make index modes/counts to be used throughout meshes + int vertexCount = 0; + int fanIndex = stripIndices.size() != 0 ? 1 : 0; + final IndexMode[] modes = new IndexMode[fanIndices.size() + fanIndex]; + final int[] counts = new int[modes.length]; + for (final Integer index : fanIndices) { + counts[fanIndex] = commands[index].vertIndices.length; + modes[fanIndex] = IndexMode.TriangleFan; + vertexCount += counts[fanIndex]; + fanIndex++; + } + if (stripIndices.size() != 0) { + int triCounts = 0; + int vertCount; + int extra = 0; + for (final Integer index : stripIndices) { + vertCount = commands[index].vertIndices.length; + extra = vertCount % 2 == 1 ? 3 : 2; + triCounts += vertCount + extra; + } + counts[0] = triCounts - extra + 1; + modes[0] = IndexMode.TriangleStrip; + vertexCount += counts[0]; + } + + vertexCount++; + + // Create each frame as a Mesh using glcommands if given + final Mesh[] meshes = new Mesh[header.numFrames]; + MeshData mData; + for (int i = 0; i < header.numFrames; i++) { + final Md2Frame frame = frames[i]; + + meshes[i] = new Mesh(frames[i].name); + mData = meshes[i].getMeshData(); + mData.setIndexLengths(counts); + mData.setIndexModes(modes); + + final FloatBufferData verts = new FloatBufferData(vertexCount * 3, 3); + final FloatBufferData norms = new FloatBufferData(vertexCount * 3, 3); + final FloatBufferData texs = new FloatBufferData(vertexCount * 3, 2); + mData.setVertexCoords(verts); + mData.setNormalCoords(norms); + mData.setTextureCoords(texs, 0); + + // go through the triangle strips/fans and add them in + // first the strips + if (stripIndices.size() != 0) { + for (int maxJ = stripIndices.size(), j = 0; j < maxJ; j++) { + cmd = commands[stripIndices.get(j)]; + if (cmd.vertIndices.length < 3) { + continue; + } + + addVert(cmd, frame, 0, verts); + norms.getBuffer().put(0).put(0).put(0); + texs.getBuffer().put(0).put(0); + + // add strip verts / normals + for (int k = 0; k < cmd.vertIndices.length; k++) { + addVert(cmd, frame, k, verts); + addNormal(cmd, frame, k, norms); + } + + // add strip tex coords + texs.getBuffer().put(cmd.texCoords); + + // if we're not the last strip, add a vert or two for degenerate triangle connector + if (j != maxJ - 1) { + addVert(cmd, frame, cmd.vertIndices.length - 1, verts); + norms.getBuffer().put(0).put(0).put(0); + texs.getBuffer().put(0).put(0); + if (cmd.vertIndices.length % 2 == 1) { + // extra vert to maintain wind order + addVert(cmd, frame, cmd.vertIndices.length - 1, verts); + norms.getBuffer().put(0).put(0).put(0); + texs.getBuffer().put(0).put(0); + } + } + } + } + // Now the fans + // XXX: could add these to the strip instead + for (final int j : fanIndices) { + cmd = commands[j]; + texs.getBuffer().put(cmd.texCoords[0]).put(cmd.texCoords[1]); + addNormal(cmd, frame, 0, norms); + addVert(cmd, frame, 0, verts); + for (int k = cmd.vertIndices.length; --k >= 1;) { + texs.getBuffer().put(cmd.texCoords[k * 2]).put(cmd.texCoords[k * 2 + 1]); + addNormal(cmd, frame, k, norms); + addVert(cmd, frame, k, verts); + } + } + } + + // Clone frame 0 as mesh for initial mesh + final Mesh mesh = meshes[0].makeCopy(false); + mesh.setModelBound(new BoundingBox()); + + // Use resource name for mesh + mesh.setName(resource.getName()); + + // Add controller + final KeyframeController<Mesh> controller = new KeyframeController<Mesh>(); + mesh.addController(controller); + controller.setMorphingMesh(mesh); + controller.setInterpTex(false); + int i = 0; + for (final Mesh meshX : meshes) { + controller.setKeyframe(i, meshX); + i++; + } + + // Make a store object to return + final Md2DataStore store = new Md2DataStore(mesh, controller); + + // store names + for (final Md2Frame frame : frames) { + store.getFrameNames().add(frame.name); + } + + // store skin names + for (final String name : texNames) { + store.getSkinNames().add(name); + } + + // Apply our texture + if (isLoadTextures()) { + Texture tex = null; + for (final String name : texNames) { + tex = loadTexture(name); + if (tex != null) { + break; + } + } + + // try using model name + if (tex == null) { + tex = loadTexture(resource.getName()); + } + + if (tex != null) { + final TextureState ts = new TextureState(); + ts.setTexture(tex); + mesh.setRenderState(ts); + } + } + + return store; + } catch (final Exception e) { + throw new Error("Unable to load md2 resource from URL: " + resource, e); + } + } + + private Texture loadTexture(final String name) { + Texture tex = null; + if (_textureLocator == null) { + tex = TextureManager.load(name, getMinificationFilter(), + isUseCompression() ? TextureStoreFormat.GuessCompressedFormat + : TextureStoreFormat.GuessNoCompressedFormat, isFlipTextureVertically()); + } else { + final ResourceSource source = _textureLocator.locateResource(name); + if (source != null) { + tex = TextureManager.load(source, getMinificationFilter(), + isUseCompression() ? TextureStoreFormat.GuessCompressedFormat + : TextureStoreFormat.GuessNoCompressedFormat, isFlipTextureVertically()); + } + } + return tex; + } + + private void addNormal(final Md2GlCommand cmd, final Md2Frame frame, final int normalIndex, + final FloatBufferData norms) { + final int index = cmd.vertIndices[normalIndex]; + final byte[] vertData = frame.vertData; + Md2Normals.getNormalVector(vertData[index * 4 + 3], calcVert); + norms.getBuffer().put(calcVert.getXf()).put(calcVert.getYf()).put(calcVert.getZf()); + } + + private void addVert(final Md2GlCommand cmd, final Md2Frame frame, final int vertIndex, final FloatBufferData verts) { + final int index = cmd.vertIndices[vertIndex]; + final byte[] vertData = frame.vertData; + calcVert.set((vertData[index * 4 + 0] & 0xFF), (vertData[index * 4 + 1] & 0xFF), + (vertData[index * 4 + 2] & 0xFF)); + calcVert.multiplyLocal(frame.scale).addLocal(frame.translate); + verts.getBuffer().put(calcVert.getXf()).put(calcVert.getYf()).put(calcVert.getZf()); + } + + /** + * Reads a MD2 file from the given resource + * + * @param resource + * the name of the resource to find. + * @return an ObjGeometryStore data object containing the scene and other useful elements. + */ + public Md2DataStore load(final String resource) { + final ResourceSource source; + if (_modelLocator == null) { + source = ResourceLocatorTool.locateResource(ResourceLocatorTool.TYPE_MODEL, resource); + } else { + source = _modelLocator.locateResource(resource); + } + + if (source == null) { + throw new Error("Unable to locate '" + resource + "'"); + } + + return load(source); + } + + static class Md2GlCommand { + IndexMode mode; + float[] texCoords; + int[] vertIndices; + + Md2GlCommand(final IndexMode indexMode, final int length) { + mode = indexMode; + texCoords = new float[length * 2]; + vertIndices = new int[length]; + } + } +} diff --git a/ardor3d-extras/src/main/java/com/ardor3d/extension/model/md2/Md2Normals.java b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/md2/Md2Normals.java index 20afdc2..fcb0f86 100644 --- a/ardor3d-extras/src/main/java/com/ardor3d/extension/model/md2/Md2Normals.java +++ b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/md2/Md2Normals.java @@ -1,190 +1,190 @@ -/**
- * Copyright (c) 2008-2012 Ardor Labs, Inc.
- *
- * This file is part of Ardor3D.
- *
- * Ardor3D is free software: you can redistribute it and/or modify it
- * under the terms of its license which may be found in the accompanying
- * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
- */
-
-package com.ardor3d.extension.model.md2;
-
-import com.ardor3d.math.Vector3;
-
-final class Md2Normals {
- /** Pre-generated md2 normals. See also http://tfc.duke.free.fr/coding/src/anorms.h */
- final static float[] NORMALS = { //
- -0.525731f, 0.000000f, 0.850651f, //
- -0.442863f, 0.238856f, 0.864188f, //
- -0.295242f, 0.000000f, 0.955423f, //
- -0.309017f, 0.500000f, 0.809017f, //
- -0.162460f, 0.262866f, 0.951056f, //
- 0.000000f, 0.000000f, 1.000000f, //
- 0.000000f, 0.850651f, 0.525731f, //
- -0.147621f, 0.716567f, 0.681718f, //
- 0.147621f, 0.716567f, 0.681718f, //
- 0.000000f, 0.525731f, 0.850651f, //
- 0.309017f, 0.500000f, 0.809017f, //
- 0.525731f, 0.000000f, 0.850651f, //
- 0.295242f, 0.000000f, 0.955423f, //
- 0.442863f, 0.238856f, 0.864188f, //
- 0.162460f, 0.262866f, 0.951056f, //
- -0.681718f, 0.147621f, 0.716567f, //
- -0.809017f, 0.309017f, 0.500000f, //
- -0.587785f, 0.425325f, 0.688191f, //
- -0.850651f, 0.525731f, 0.000000f, //
- -0.864188f, 0.442863f, 0.238856f, //
- -0.716567f, 0.681718f, 0.147621f, //
- -0.688191f, 0.587785f, 0.425325f, //
- -0.500000f, 0.809017f, 0.309017f, //
- -0.238856f, 0.864188f, 0.442863f, //
- -0.425325f, 0.688191f, 0.587785f, //
- -0.716567f, 0.681718f, -0.147621f, //
- -0.500000f, 0.809017f, -0.309017f, //
- -0.525731f, 0.850651f, 0.000000f, //
- 0.000000f, 0.850651f, -0.525731f, //
- -0.238856f, 0.864188f, -0.442863f, //
- 0.000000f, 0.955423f, -0.295242f, //
- -0.262866f, 0.951056f, -0.162460f, //
- 0.000000f, 1.000000f, 0.000000f, //
- 0.000000f, 0.955423f, 0.295242f, //
- -0.262866f, 0.951056f, 0.162460f, //
- 0.238856f, 0.864188f, 0.442863f, //
- 0.262866f, 0.951056f, 0.162460f, //
- 0.500000f, 0.809017f, 0.309017f, //
- 0.238856f, 0.864188f, -0.442863f, //
- 0.262866f, 0.951056f, -0.162460f, //
- 0.500000f, 0.809017f, -0.309017f, //
- 0.850651f, 0.525731f, 0.000000f, //
- 0.716567f, 0.681718f, 0.147621f, //
- 0.716567f, 0.681718f, -0.147621f, //
- 0.525731f, 0.850651f, 0.000000f, //
- 0.425325f, 0.688191f, 0.587785f, //
- 0.864188f, 0.442863f, 0.238856f, //
- 0.688191f, 0.587785f, 0.425325f, //
- 0.809017f, 0.309017f, 0.500000f, //
- 0.681718f, 0.147621f, 0.716567f, //
- 0.587785f, 0.425325f, 0.688191f, //
- 0.955423f, 0.295242f, 0.000000f, //
- 1.000000f, 0.000000f, 0.000000f, //
- 0.951056f, 0.162460f, 0.262866f, //
- 0.850651f, -0.525731f, 0.000000f, //
- 0.955423f, -0.295242f, 0.000000f, //
- 0.864188f, -0.442863f, 0.238856f, //
- 0.951056f, -0.162460f, 0.262866f, //
- 0.809017f, -0.309017f, 0.500000f, //
- 0.681718f, -0.147621f, 0.716567f, //
- 0.850651f, 0.000000f, 0.525731f, //
- 0.864188f, 0.442863f, -0.238856f, //
- 0.809017f, 0.309017f, -0.500000f, //
- 0.951056f, 0.162460f, -0.262866f, //
- 0.525731f, 0.000000f, -0.850651f, //
- 0.681718f, 0.147621f, -0.716567f, //
- 0.681718f, -0.147621f, -0.716567f, //
- 0.850651f, 0.000000f, -0.525731f, //
- 0.809017f, -0.309017f, -0.500000f, //
- 0.864188f, -0.442863f, -0.238856f, //
- 0.951056f, -0.162460f, -0.262866f, //
- 0.147621f, 0.716567f, -0.681718f, //
- 0.309017f, 0.500000f, -0.809017f, //
- 0.425325f, 0.688191f, -0.587785f, //
- 0.442863f, 0.238856f, -0.864188f, //
- 0.587785f, 0.425325f, -0.688191f, //
- 0.688191f, 0.587785f, -0.425325f, //
- -0.147621f, 0.716567f, -0.681718f, //
- -0.309017f, 0.500000f, -0.809017f, //
- 0.000000f, 0.525731f, -0.850651f, //
- -0.525731f, 0.000000f, -0.850651f, //
- -0.442863f, 0.238856f, -0.864188f, //
- -0.295242f, 0.000000f, -0.955423f, //
- -0.162460f, 0.262866f, -0.951056f, //
- 0.000000f, 0.000000f, -1.000000f, //
- 0.295242f, 0.000000f, -0.955423f, //
- 0.162460f, 0.262866f, -0.951056f, //
- -0.442863f, -0.238856f, -0.864188f, //
- -0.309017f, -0.500000f, -0.809017f, //
- -0.162460f, -0.262866f, -0.951056f, //
- 0.000000f, -0.850651f, -0.525731f, //
- -0.147621f, -0.716567f, -0.681718f, //
- 0.147621f, -0.716567f, -0.681718f, //
- 0.000000f, -0.525731f, -0.850651f, //
- 0.309017f, -0.500000f, -0.809017f, //
- 0.442863f, -0.238856f, -0.864188f, //
- 0.162460f, -0.262866f, -0.951056f, //
- 0.238856f, -0.864188f, -0.442863f, //
- 0.500000f, -0.809017f, -0.309017f, //
- 0.425325f, -0.688191f, -0.587785f, //
- 0.716567f, -0.681718f, -0.147621f, //
- 0.688191f, -0.587785f, -0.425325f, //
- 0.587785f, -0.425325f, -0.688191f, //
- 0.000000f, -0.955423f, -0.295242f, //
- 0.000000f, -1.000000f, 0.000000f, //
- 0.262866f, -0.951056f, -0.162460f, //
- 0.000000f, -0.850651f, 0.525731f, //
- 0.000000f, -0.955423f, 0.295242f, //
- 0.238856f, -0.864188f, 0.442863f, //
- 0.262866f, -0.951056f, 0.162460f, //
- 0.500000f, -0.809017f, 0.309017f, //
- 0.716567f, -0.681718f, 0.147621f, //
- 0.525731f, -0.850651f, 0.000000f, //
- -0.238856f, -0.864188f, -0.442863f, //
- -0.500000f, -0.809017f, -0.309017f, //
- -0.262866f, -0.951056f, -0.162460f, //
- -0.850651f, -0.525731f, 0.000000f, //
- -0.716567f, -0.681718f, -0.147621f, //
- -0.716567f, -0.681718f, 0.147621f, //
- -0.525731f, -0.850651f, 0.000000f, //
- -0.500000f, -0.809017f, 0.309017f, //
- -0.238856f, -0.864188f, 0.442863f, //
- -0.262866f, -0.951056f, 0.162460f, //
- -0.864188f, -0.442863f, 0.238856f, //
- -0.809017f, -0.309017f, 0.500000f, //
- -0.688191f, -0.587785f, 0.425325f, //
- -0.681718f, -0.147621f, 0.716567f, //
- -0.442863f, -0.238856f, 0.864188f, //
- -0.587785f, -0.425325f, 0.688191f, //
- -0.309017f, -0.500000f, 0.809017f, //
- -0.147621f, -0.716567f, 0.681718f, //
- -0.425325f, -0.688191f, 0.587785f, //
- -0.162460f, -0.262866f, 0.951056f, //
- 0.442863f, -0.238856f, 0.864188f, //
- 0.162460f, -0.262866f, 0.951056f, //
- 0.309017f, -0.500000f, 0.809017f, //
- 0.147621f, -0.716567f, 0.681718f, //
- 0.000000f, -0.525731f, 0.850651f, //
- 0.425325f, -0.688191f, 0.587785f, //
- 0.587785f, -0.425325f, 0.688191f, //
- 0.688191f, -0.587785f, 0.425325f, //
- -0.955423f, 0.295242f, 0.000000f, //
- -0.951056f, 0.162460f, 0.262866f, //
- -1.000000f, 0.000000f, 0.000000f, //
- -0.850651f, 0.000000f, 0.525731f, //
- -0.955423f, -0.295242f, 0.000000f, //
- -0.951056f, -0.162460f, 0.262866f, //
- -0.864188f, 0.442863f, -0.238856f, //
- -0.951056f, 0.162460f, -0.262866f, //
- -0.809017f, 0.309017f, -0.500000f, //
- -0.864188f, -0.442863f, -0.238856f, //
- -0.951056f, -0.162460f, -0.262866f, //
- -0.809017f, -0.309017f, -0.500000f, //
- -0.681718f, 0.147621f, -0.716567f, //
- -0.681718f, -0.147621f, -0.716567f, //
- -0.850651f, 0.000000f, -0.525731f, //
- -0.688191f, 0.587785f, -0.425325f, //
- -0.587785f, 0.425325f, -0.688191f, //
- -0.425325f, 0.688191f, -0.587785f, //
- -0.425325f, -0.688191f, -0.587785f, //
- -0.587785f, -0.425325f, -0.688191f, //
- -0.688191f, -0.587785f, -0.425325f //
- };
-
- public static void getNormalVector(final byte i, final Vector3 store) {
- final int index = 3 * (i & 0xff);
- if (index < 0 || index > Md2Normals.NORMALS.length) {
- store.set(0, 1, 0);
- } else {
- store.set(Md2Normals.NORMALS[index + 0], Md2Normals.NORMALS[index + 1], Md2Normals.NORMALS[index + 2]);
- }
- }
-}
+/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.model.md2; + +import com.ardor3d.math.Vector3; + +final class Md2Normals { + /** Pre-generated md2 normals. See also http://tfc.duke.free.fr/coding/src/anorms.h */ + final static float[] NORMALS = { // + -0.525731f, 0.000000f, 0.850651f, // + -0.442863f, 0.238856f, 0.864188f, // + -0.295242f, 0.000000f, 0.955423f, // + -0.309017f, 0.500000f, 0.809017f, // + -0.162460f, 0.262866f, 0.951056f, // + 0.000000f, 0.000000f, 1.000000f, // + 0.000000f, 0.850651f, 0.525731f, // + -0.147621f, 0.716567f, 0.681718f, // + 0.147621f, 0.716567f, 0.681718f, // + 0.000000f, 0.525731f, 0.850651f, // + 0.309017f, 0.500000f, 0.809017f, // + 0.525731f, 0.000000f, 0.850651f, // + 0.295242f, 0.000000f, 0.955423f, // + 0.442863f, 0.238856f, 0.864188f, // + 0.162460f, 0.262866f, 0.951056f, // + -0.681718f, 0.147621f, 0.716567f, // + -0.809017f, 0.309017f, 0.500000f, // + -0.587785f, 0.425325f, 0.688191f, // + -0.850651f, 0.525731f, 0.000000f, // + -0.864188f, 0.442863f, 0.238856f, // + -0.716567f, 0.681718f, 0.147621f, // + -0.688191f, 0.587785f, 0.425325f, // + -0.500000f, 0.809017f, 0.309017f, // + -0.238856f, 0.864188f, 0.442863f, // + -0.425325f, 0.688191f, 0.587785f, // + -0.716567f, 0.681718f, -0.147621f, // + -0.500000f, 0.809017f, -0.309017f, // + -0.525731f, 0.850651f, 0.000000f, // + 0.000000f, 0.850651f, -0.525731f, // + -0.238856f, 0.864188f, -0.442863f, // + 0.000000f, 0.955423f, -0.295242f, // + -0.262866f, 0.951056f, -0.162460f, // + 0.000000f, 1.000000f, 0.000000f, // + 0.000000f, 0.955423f, 0.295242f, // + -0.262866f, 0.951056f, 0.162460f, // + 0.238856f, 0.864188f, 0.442863f, // + 0.262866f, 0.951056f, 0.162460f, // + 0.500000f, 0.809017f, 0.309017f, // + 0.238856f, 0.864188f, -0.442863f, // + 0.262866f, 0.951056f, -0.162460f, // + 0.500000f, 0.809017f, -0.309017f, // + 0.850651f, 0.525731f, 0.000000f, // + 0.716567f, 0.681718f, 0.147621f, // + 0.716567f, 0.681718f, -0.147621f, // + 0.525731f, 0.850651f, 0.000000f, // + 0.425325f, 0.688191f, 0.587785f, // + 0.864188f, 0.442863f, 0.238856f, // + 0.688191f, 0.587785f, 0.425325f, // + 0.809017f, 0.309017f, 0.500000f, // + 0.681718f, 0.147621f, 0.716567f, // + 0.587785f, 0.425325f, 0.688191f, // + 0.955423f, 0.295242f, 0.000000f, // + 1.000000f, 0.000000f, 0.000000f, // + 0.951056f, 0.162460f, 0.262866f, // + 0.850651f, -0.525731f, 0.000000f, // + 0.955423f, -0.295242f, 0.000000f, // + 0.864188f, -0.442863f, 0.238856f, // + 0.951056f, -0.162460f, 0.262866f, // + 0.809017f, -0.309017f, 0.500000f, // + 0.681718f, -0.147621f, 0.716567f, // + 0.850651f, 0.000000f, 0.525731f, // + 0.864188f, 0.442863f, -0.238856f, // + 0.809017f, 0.309017f, -0.500000f, // + 0.951056f, 0.162460f, -0.262866f, // + 0.525731f, 0.000000f, -0.850651f, // + 0.681718f, 0.147621f, -0.716567f, // + 0.681718f, -0.147621f, -0.716567f, // + 0.850651f, 0.000000f, -0.525731f, // + 0.809017f, -0.309017f, -0.500000f, // + 0.864188f, -0.442863f, -0.238856f, // + 0.951056f, -0.162460f, -0.262866f, // + 0.147621f, 0.716567f, -0.681718f, // + 0.309017f, 0.500000f, -0.809017f, // + 0.425325f, 0.688191f, -0.587785f, // + 0.442863f, 0.238856f, -0.864188f, // + 0.587785f, 0.425325f, -0.688191f, // + 0.688191f, 0.587785f, -0.425325f, // + -0.147621f, 0.716567f, -0.681718f, // + -0.309017f, 0.500000f, -0.809017f, // + 0.000000f, 0.525731f, -0.850651f, // + -0.525731f, 0.000000f, -0.850651f, // + -0.442863f, 0.238856f, -0.864188f, // + -0.295242f, 0.000000f, -0.955423f, // + -0.162460f, 0.262866f, -0.951056f, // + 0.000000f, 0.000000f, -1.000000f, // + 0.295242f, 0.000000f, -0.955423f, // + 0.162460f, 0.262866f, -0.951056f, // + -0.442863f, -0.238856f, -0.864188f, // + -0.309017f, -0.500000f, -0.809017f, // + -0.162460f, -0.262866f, -0.951056f, // + 0.000000f, -0.850651f, -0.525731f, // + -0.147621f, -0.716567f, -0.681718f, // + 0.147621f, -0.716567f, -0.681718f, // + 0.000000f, -0.525731f, -0.850651f, // + 0.309017f, -0.500000f, -0.809017f, // + 0.442863f, -0.238856f, -0.864188f, // + 0.162460f, -0.262866f, -0.951056f, // + 0.238856f, -0.864188f, -0.442863f, // + 0.500000f, -0.809017f, -0.309017f, // + 0.425325f, -0.688191f, -0.587785f, // + 0.716567f, -0.681718f, -0.147621f, // + 0.688191f, -0.587785f, -0.425325f, // + 0.587785f, -0.425325f, -0.688191f, // + 0.000000f, -0.955423f, -0.295242f, // + 0.000000f, -1.000000f, 0.000000f, // + 0.262866f, -0.951056f, -0.162460f, // + 0.000000f, -0.850651f, 0.525731f, // + 0.000000f, -0.955423f, 0.295242f, // + 0.238856f, -0.864188f, 0.442863f, // + 0.262866f, -0.951056f, 0.162460f, // + 0.500000f, -0.809017f, 0.309017f, // + 0.716567f, -0.681718f, 0.147621f, // + 0.525731f, -0.850651f, 0.000000f, // + -0.238856f, -0.864188f, -0.442863f, // + -0.500000f, -0.809017f, -0.309017f, // + -0.262866f, -0.951056f, -0.162460f, // + -0.850651f, -0.525731f, 0.000000f, // + -0.716567f, -0.681718f, -0.147621f, // + -0.716567f, -0.681718f, 0.147621f, // + -0.525731f, -0.850651f, 0.000000f, // + -0.500000f, -0.809017f, 0.309017f, // + -0.238856f, -0.864188f, 0.442863f, // + -0.262866f, -0.951056f, 0.162460f, // + -0.864188f, -0.442863f, 0.238856f, // + -0.809017f, -0.309017f, 0.500000f, // + -0.688191f, -0.587785f, 0.425325f, // + -0.681718f, -0.147621f, 0.716567f, // + -0.442863f, -0.238856f, 0.864188f, // + -0.587785f, -0.425325f, 0.688191f, // + -0.309017f, -0.500000f, 0.809017f, // + -0.147621f, -0.716567f, 0.681718f, // + -0.425325f, -0.688191f, 0.587785f, // + -0.162460f, -0.262866f, 0.951056f, // + 0.442863f, -0.238856f, 0.864188f, // + 0.162460f, -0.262866f, 0.951056f, // + 0.309017f, -0.500000f, 0.809017f, // + 0.147621f, -0.716567f, 0.681718f, // + 0.000000f, -0.525731f, 0.850651f, // + 0.425325f, -0.688191f, 0.587785f, // + 0.587785f, -0.425325f, 0.688191f, // + 0.688191f, -0.587785f, 0.425325f, // + -0.955423f, 0.295242f, 0.000000f, // + -0.951056f, 0.162460f, 0.262866f, // + -1.000000f, 0.000000f, 0.000000f, // + -0.850651f, 0.000000f, 0.525731f, // + -0.955423f, -0.295242f, 0.000000f, // + -0.951056f, -0.162460f, 0.262866f, // + -0.864188f, 0.442863f, -0.238856f, // + -0.951056f, 0.162460f, -0.262866f, // + -0.809017f, 0.309017f, -0.500000f, // + -0.864188f, -0.442863f, -0.238856f, // + -0.951056f, -0.162460f, -0.262866f, // + -0.809017f, -0.309017f, -0.500000f, // + -0.681718f, 0.147621f, -0.716567f, // + -0.681718f, -0.147621f, -0.716567f, // + -0.850651f, 0.000000f, -0.525731f, // + -0.688191f, 0.587785f, -0.425325f, // + -0.587785f, 0.425325f, -0.688191f, // + -0.425325f, 0.688191f, -0.587785f, // + -0.425325f, -0.688191f, -0.587785f, // + -0.587785f, -0.425325f, -0.688191f, // + -0.688191f, -0.587785f, -0.425325f // + }; + + public static void getNormalVector(final byte i, final Vector3 store) { + final int index = 3 * (i & 0xff); + if (index < 0 || index > Md2Normals.NORMALS.length) { + store.set(0, 1, 0); + } else { + store.set(Md2Normals.NORMALS[index + 0], Md2Normals.NORMALS[index + 1], Md2Normals.NORMALS[index + 2]); + } + } +} diff --git a/ardor3d-extras/src/main/java/com/ardor3d/extension/model/util/KeyframeController.java b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/util/KeyframeController.java index 6609544..596dead 100644 --- a/ardor3d-extras/src/main/java/com/ardor3d/extension/model/util/KeyframeController.java +++ b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/util/KeyframeController.java @@ -1,763 +1,763 @@ -/**
- * Copyright (c) 2008-2012 Ardor Labs, Inc.
- *
- * This file is part of Ardor3D.
- *
- * Ardor3D is free software: you can redistribute it and/or modify it
- * under the terms of its license which may be found in the accompanying
- * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
- */
-
-package com.ardor3d.extension.model.util;
-
-import java.io.IOException;
-import java.nio.FloatBuffer;
-import java.util.ArrayList;
-import java.util.logging.Logger;
-
-import com.ardor3d.scenegraph.FloatBufferData;
-import com.ardor3d.scenegraph.IndexBufferData;
-import com.ardor3d.scenegraph.Mesh;
-import com.ardor3d.scenegraph.Spatial;
-import com.ardor3d.scenegraph.controller.ComplexSpatialController;
-import com.ardor3d.util.export.InputCapsule;
-import com.ardor3d.util.export.OutputCapsule;
-import com.ardor3d.util.export.Savable;
-import com.ardor3d.util.geom.BufferUtils;
-
-/**
- * TODO: Revisit for better Ardor3D integration.
- *
- * Started Date: Jun 12, 2004 <br>
- *
- *
- * Class can do linear interpolation of a Mesh between units of time. Similar to VertexKeyframeController but
- * interpolates float units of time instead of integer key frames.
- *
- * setSpeed(float) sets a speed relative to the defined speed. For example, the default is 1. A speed of 2 would run
- * twice as fast and a speed of .5 would run half as fast
- *
- * setMinTime(float) and setMaxTime(float) both define the bounds that KeyframeController should follow. It is the
- * programmer's responsibility to make sure that the MinTime and MaxTime are within the span of the defined setKeyframe
- *
- * Controller functions RepeatType and isActive are both defined in their default way for KeyframeController
- *
- * When this controller is saved/loaded to XML format, it assumes that the mesh it morphs is the Mesh it belongs to, so
- * it is recommended to only attach this controller to the Mesh it animates.
- *
- * (Based on work by Jack Lindamood, kevglass (parts), hevee (blend time), Julien Gouesse (port to Ardor3D))
- */
-
-public class KeyframeController<T extends Spatial> extends ComplexSpatialController<T> {
-
- private static final Logger logger = Logger.getLogger(KeyframeController.class.getName());
-
- private static final long serialVersionUID = 1L;
-
- /**
- * An array of PointInTime s that defines the animation
- */
- transient public ArrayList<PointInTime> _keyframes;
-
- /**
- * A special array used with SmoothTransform to store temporary smooth transforms
- */
- transient private ArrayList<PointInTime> _prevKeyframes;
-
- /**
- * The mesh that is actually morphed
- */
- private Mesh _morphMesh;
-
- /**
- * The current time in the animation
- */
- transient private double _curTime;
-
- /**
- * The current frame of the animation
- */
- transient private int _curFrame;
-
- /**
- * The frame of animation we're heading towards
- */
- transient private int _nextFrame;
-
- /**
- * The PointInTime before curTime
- */
- transient private PointInTime _before;
-
- /**
- * The PointInTime after curTime
- */
- transient private PointInTime _after;
-
- /**
- * If true, the animation is moving forward, if false the animation is moving backwards
- */
- transient private boolean _movingForward = true;
-
- /**
- * Used with SmoothTransform to signal it is doing a smooth transform
- */
- transient private boolean _isSmooth;
-
- /**
- * Used with SmoothTransform to signal it is doing a smooth transform
- */
- transient private boolean _interpTex = true;
-
- /**
- * Used with SmoothTransform to hold the new beginning and ending time once the transform is complete
- */
- transient private double _tempNewBeginTime;
-
- transient private double _tempNewEndTime;
-
- /** If true, the model's bounding volume will update every frame. */
- private boolean _updateBounding = true;
-
- /**
- * Default constructor. Speed is 1, MinTime is 0 MaxTime is 0. Both MinTime and MaxTime are automatically adjusted
- * by setKeyframe if the setKeyframe time is less than MinTime or greater than MaxTime. Default RepeatType is WRAP.
- */
- public KeyframeController() {
- setSpeed(1);
- _keyframes = new ArrayList<PointInTime>();
- _curFrame = 0;
- setRepeatType(ComplexSpatialController.RepeatType.WRAP);
- setMinTime(0);
- setMaxTime(0);
- }
-
- public double getCurrentTime() {
- return _curTime;
- }
-
- public int getCurrentFrame() {
- return _curFrame;
- }
-
- /**
- * Gets the current time in the animation
- */
- public double getCurTime() {
- return _curTime;
- }
-
- /**
- * Sets the current time in the animation
- *
- * @param time
- * The time this Controller should continue at
- */
- public void setCurTime(final double time) {
- _curTime = time;
- }
-
- /**
- * Sets the Mesh that will be physically changed by this KeyframeController
- *
- * @param morph
- * The new mesh to morph
- */
- public void setMorphingMesh(final Mesh morph) {
- _morphMesh = morph;
- _keyframes.clear();
- _keyframes.add(new PointInTime(0, null));
- }
-
- public void shallowSetMorphMesh(final Mesh morph) {
- _morphMesh = morph;
- }
-
- /**
- * Tells the controller to change its morphMesh to shape at time seconds. Time must be >=0 and shape must be
- * non-null and shape must have the same number of vertexes as the current shape. If not, then nothing happens. It
- * is also required that setMorphingMesh(TriMesh) is called before setKeyframe. It is assumed that shape.indices ==
- * morphMesh.indices, otherwise morphing may look funny
- *
- * @param time
- * The time for the change
- * @param shape
- * The new shape at that time
- */
- public void setKeyframe(final double time, final Mesh shape) {
- if (_morphMesh == null
- || time < 0
- || shape.getMeshData().getVertexBuffer().capacity() != _morphMesh.getMeshData().getVertexBuffer()
- .capacity()) {
- return;
- }
- for (int i = 0; i < _keyframes.size(); i++) {
- final PointInTime lookingTime = _keyframes.get(i);
- if (lookingTime._time == time) {
- lookingTime._newShape = shape;
- return;
- }
- if (lookingTime._time > time) {
- _keyframes.add(i, new PointInTime(time, shape));
- return;
- }
- }
- _keyframes.add(new PointInTime(time, shape));
- if (time > getMaxTime()) {
- setMaxTime(time);
- }
- if (time < getMinTime()) {
- setMinTime(time);
- }
- }
-
- /**
- * This function will do a smooth translation between a keframe's current look, to the look directly at
- * newTimeToReach. It takes translationLen time (in sec) to do that translation, and once translated will animate
- * like normal between newBeginTime and newEndTime <br>
- * <br>
- * This would be usefull for example when a figure stops running and tries to raise an arm. Instead of "teleporting"
- * to the raise-arm animation begining, a smooth translation can occur.
- *
- * @param newTimeToReach
- * The time to reach.
- * @param translationLen
- * How long it takes
- * @param newBeginTime
- * The new cycle begining time
- * @param newEndTime
- * The new cycle ending time.
- */
- public void setSmoothTranslation(final float newTimeToReach, final float translationLen, final float newBeginTime,
- final float newEndTime) {
- if (!isActive() || _isSmooth) {
- return;
- }
- if (newBeginTime < 0 || newBeginTime > _keyframes.get(_keyframes.size() - 1)._time) {
- KeyframeController.logger.warning("Attempt to set invalid begintime:" + newBeginTime);
- return;
- }
- if (newEndTime < 0 || newEndTime > _keyframes.get(_keyframes.size() - 1)._time) {
- KeyframeController.logger.warning("Attempt to set invalid endtime:" + newEndTime);
- return;
- }
- Mesh begin = null, end = null;
- if (_prevKeyframes == null) {
- _prevKeyframes = new ArrayList<PointInTime>();
- begin = new Mesh();
- end = new Mesh();
- } else {
- begin = _prevKeyframes.get(0)._newShape;
- end = _prevKeyframes.get(1)._newShape;
- _prevKeyframes.clear();
- }
-
- getCurrent(begin);
-
- _curTime = newTimeToReach;
- _curFrame = 0;
- setMinTime(0);
- setMaxTime(_keyframes.get(_keyframes.size() - 1)._time);
- update(0.0d, null);
- getCurrent(end);
-
- swapKeyframeSets();
- _curTime = 0;
- _curFrame = 0;
- setMinTime(0);
- setMaxTime(translationLen);
- setKeyframe(0, begin);
- setKeyframe(translationLen, end);
- _isSmooth = true;
- _tempNewBeginTime = newBeginTime;
- _tempNewEndTime = newEndTime;
- }
-
- /**
- * Swaps prevKeyframes and keyframes
- */
- private void swapKeyframeSets() {
- final ArrayList<PointInTime> temp = _keyframes;
- _keyframes = _prevKeyframes;
- _prevKeyframes = temp;
- }
-
- /**
- * Sets the new animation boundaries for this controller. This will start at newBeginTime and proceed in the
- * direction of newEndTime (either forwards or backwards). If both are the same, then the animation is set to their
- * time and turned off, otherwise the animation is turned on to start the animation acording to the repeat type. If
- * either BeginTime or EndTime are invalid times (less than 0 or greater than the maximum set keyframe time) then a
- * warning is set and nothing happens. <br>
- * It is suggested that this function be called if new animation boundaries need to be set, instead of setMinTime
- * and setMaxTime directly.
- *
- * @param newBeginTime
- * The starting time
- * @param newEndTime
- * The ending time
- */
- public void setNewAnimationTimes(final double newBeginTime, final double newEndTime) {
- if (_isSmooth) {
- return;
- }
- if (newBeginTime < 0 || newBeginTime > _keyframes.get(_keyframes.size() - 1)._time) {
- KeyframeController.logger.warning("Attempt to set invalid begintime:" + newBeginTime);
- return;
- }
- if (newEndTime < 0 || newEndTime > _keyframes.get(_keyframes.size() - 1)._time) {
- KeyframeController.logger.warning("Attempt to set invalid endtime:" + newEndTime);
- return;
- }
- setMinTime(newBeginTime);
- setMaxTime(newEndTime);
- setActive(true);
- if (newBeginTime <= newEndTime) { // Moving forward
- _movingForward = true;
- _curTime = newBeginTime;
- if (newBeginTime == newEndTime) {
- update(0.0d, null);
- setActive(false);
- }
- } else { // Moving backwards
- _movingForward = false;
- _curTime = newEndTime;
- }
- }
-
- /**
- * Saves whatever the current morphMesh looks like into the dataCopy
- *
- * @param dataCopy
- * The copy to save the current mesh into
- */
- private void getCurrent(final Mesh dataCopy) {
- if (_morphMesh.getMeshData().getColorBuffer() != null) {
- FloatBuffer dcColors = dataCopy.getMeshData().getColorBuffer();
- if (dcColors != null) {
- dcColors.clear();
- }
- final FloatBuffer mmColors = _morphMesh.getMeshData().getColorBuffer();
- mmColors.clear();
- if (dcColors == null || dcColors.capacity() != mmColors.capacity()) {
- dcColors = BufferUtils.createFloatBuffer(mmColors.capacity());
- dcColors.clear();
- dataCopy.getMeshData().setColorBuffer(dcColors);
- }
-
- dcColors.put(mmColors);
- dcColors.flip();
- }
- if (_morphMesh.getMeshData().getVertexBuffer() != null) {
- FloatBuffer dcVerts = dataCopy.getMeshData().getVertexBuffer();
- if (dcVerts != null) {
- dcVerts.clear();
- }
- final FloatBuffer mmVerts = _morphMesh.getMeshData().getVertexBuffer();
- mmVerts.clear();
- if (dcVerts == null || dcVerts.capacity() != mmVerts.capacity()) {
- dcVerts = BufferUtils.createFloatBuffer(mmVerts.capacity());
- dcVerts.clear();
- dataCopy.getMeshData().setVertexBuffer(dcVerts);
- }
-
- dcVerts.put(mmVerts);
- dcVerts.flip();
- }
- if (_morphMesh.getMeshData().getNormalBuffer() != null) {
- FloatBuffer dcNorms = dataCopy.getMeshData().getNormalBuffer();
- if (dcNorms != null) {
- dcNorms.clear();
- }
- final FloatBuffer mmNorms = _morphMesh.getMeshData().getNormalBuffer();
- mmNorms.clear();
- if (dcNorms == null || dcNorms.capacity() != mmNorms.capacity()) {
- dcNorms = BufferUtils.createFloatBuffer(mmNorms.capacity());
- dcNorms.clear();
- dataCopy.getMeshData().setNormalBuffer(dcNorms);
- }
-
- dcNorms.put(mmNorms);
- dcNorms.flip();
- }
- if (_morphMesh.getMeshData().getIndices() != null) {
- IndexBufferData<?> dcInds = dataCopy.getMeshData().getIndices();
- if (dcInds != null) {
- dcInds.rewind();
- }
- final IndexBufferData<?> mmInds = _morphMesh.getMeshData().getIndices();
- mmInds.clear();
- if (dcInds == null || dcInds.capacity() != mmInds.capacity() || dcInds.getClass() != mmInds.getClass()) {
- dcInds = BufferUtils.createIndexBufferData(mmInds.capacity(), _morphMesh.getMeshData()
- .getVertexBuffer().capacity() - 1);
- dcInds.clear();
- dataCopy.getMeshData().setIndices(dcInds);
- }
-
- dcInds.put(mmInds);
- dcInds.flip();
- }
- if (_morphMesh.getMeshData().getTextureCoords(0) != null) {
- FloatBuffer dcTexs = dataCopy.getMeshData().getTextureCoords(0).getBuffer();
- if (dcTexs != null) {
- dcTexs.clear();
- }
- final FloatBuffer mmTexs = _morphMesh.getMeshData().getTextureCoords(0).getBuffer();
- mmTexs.clear();
- if (dcTexs == null || dcTexs.capacity() != mmTexs.capacity()) {
- dcTexs = BufferUtils.createFloatBuffer(mmTexs.capacity());
- dcTexs.clear();
- dataCopy.getMeshData().setTextureCoords(new FloatBufferData(dcTexs, 2), 0);
- }
-
- dcTexs.put(mmTexs);
- dcTexs.flip();
- }
- }
-
- /**
- * As defined in Controller
- *
- * @param time
- * as defined in Controller
- */
- @Override
- public void update(final double time, final T caller) {
- if (easyQuit()) {
- return;
- }
- if (_movingForward) {
- _curTime += time * getSpeed();
- } else {
- _curTime -= time * getSpeed();
- }
-
- findFrame();
- _before = _keyframes.get(_curFrame);
- // Change this bit so the next frame we're heading towards isn't always going
- // to be one frame ahead since now we coule be animating from the last to first
- // frames.
- // after = keyframes.get(curFrame + 1));
- _after = _keyframes.get(_nextFrame);
-
- double delta = (_curTime - _before._time) / (_after._time - _before._time);
-
- // If we doing that wrapping bit then delta should be caculated based
- // on the time before the start of the animation we are.
- if (_nextFrame < _curFrame) {
- delta = blendTime - (getMinTime() - _curTime);
- }
-
- final Mesh oldShape = _before._newShape;
- final Mesh newShape = _after._newShape;
-
- final FloatBuffer verts = _morphMesh.getMeshData().getVertexBuffer();
- final FloatBuffer norms = _morphMesh.getMeshData().getNormalBuffer();
- final FloatBuffer texts = _interpTex ? _morphMesh.getMeshData().getTextureCoords(0) != null ? _morphMesh
- .getMeshData().getTextureCoords(0).getBuffer() : null : null;
- final FloatBuffer colors = _morphMesh.getMeshData().getColorBuffer();
-
- final FloatBuffer oldverts = oldShape.getMeshData().getVertexBuffer();
- final FloatBuffer oldnorms = oldShape.getMeshData().getNormalBuffer();
- final FloatBuffer oldtexts = _interpTex ? oldShape.getMeshData().getTextureCoords(0) != null ? oldShape
- .getMeshData().getTextureCoords(0).getBuffer() : null : null;
- final FloatBuffer oldcolors = oldShape.getMeshData().getColorBuffer();
-
- final FloatBuffer newverts = newShape.getMeshData().getVertexBuffer();
- final FloatBuffer newnorms = newShape.getMeshData().getNormalBuffer();
- final FloatBuffer newtexts = _interpTex ? newShape.getMeshData().getTextureCoords(0) != null ? newShape
- .getMeshData().getTextureCoords(0).getBuffer() : null : null;
- final FloatBuffer newcolors = newShape.getMeshData().getColorBuffer();
- if (verts == null || oldverts == null || newverts == null) {
- return;
- }
- final int vertQuantity = verts.capacity() / 3;
- verts.rewind();
- oldverts.rewind();
- newverts.rewind();
-
- if (norms != null) {
- norms.rewind(); // reset to start
- }
- if (oldnorms != null) {
- oldnorms.rewind(); // reset to start
- }
- if (newnorms != null) {
- newnorms.rewind(); // reset to start
- }
-
- if (texts != null) {
- texts.rewind(); // reset to start
- }
- if (oldtexts != null) {
- oldtexts.rewind(); // reset to start
- }
- if (newtexts != null) {
- newtexts.rewind(); // reset to start
- }
-
- if (colors != null) {
- colors.rewind(); // reset to start
- }
- if (oldcolors != null) {
- oldcolors.rewind(); // reset to start
- }
- if (newcolors != null) {
- newcolors.rewind(); // reset to start
- }
-
- for (int i = 0; i < vertQuantity; i++) {
- for (int x = 0; x < 3; x++) {
- verts.put(i * 3 + x, (float) ((1f - delta) * oldverts.get(i * 3 + x) + delta * newverts.get(i * 3 + x)));
- }
-
- if (norms != null && oldnorms != null && newnorms != null) {
- for (int x = 0; x < 3; x++) {
- norms.put(i * 3 + x,
- (float) ((1f - delta) * oldnorms.get(i * 3 + x) + delta * newnorms.get(i * 3 + x)));
- }
- }
-
- if (_interpTex && texts != null && oldtexts != null && newtexts != null) {
- for (int x = 0; x < 2; x++) {
- texts.put(i * 2 + x,
- (float) ((1f - delta) * oldtexts.get(i * 2 + x) + delta * newtexts.get(i * 2 + x)));
- }
- }
-
- if (colors != null && oldcolors != null && newcolors != null) {
- for (int x = 0; x < 4; x++) {
- colors.put(i * 4 + x,
- (float) ((1f - delta) * oldcolors.get(i * 4 + x) + delta * newcolors.get(i * 4 + x)));
- }
- }
- }
-
- if (_updateBounding) {
- _morphMesh.updateModelBound();
- }
- }
-
- /**
- * If both min and max time are equal and the model is already updated, then it's an easy quit, or if it's on CLAMP
- * and I've exceeded my time it's also an easy quit.
- *
- * @return true if update doesn't need to be called, false otherwise
- */
- private boolean easyQuit() {
- if (getMaxTime() == getMinTime() && _curTime != getMinTime()) {
- return true;
- } else if (getRepeatType() == ComplexSpatialController.RepeatType.CLAMP
- && (_curTime > getMaxTime() || _curTime < getMinTime())) {
- return true;
- } else if (_keyframes.size() < 2) {
- return true;
- }
- return false;
- }
-
- /**
- * If true, the model's bounding volume will be updated every frame. If false, it will not.
- *
- * @param update
- * The new update model volume per frame value.
- */
- public void setUpdateBounding(final boolean update) {
- _updateBounding = update;
- }
-
- /**
- * Returns true if the model's bounding volume is being updated every frame.
- *
- * @return True if bounding volume is updating.
- */
- public boolean isUpdateBounding() {
- return _updateBounding;
- }
-
- public void setInterpTex(final boolean interpTex) {
- _interpTex = interpTex;
- }
-
- public boolean isInterpTex() {
- return _interpTex;
- }
-
- private float blendTime = 0;
-
- /**
- * If repeat type RT_WRAP is set, after reaching the last frame of the currently set animation maxTime (see
- * Controller.setMaxTime), there will be an additional blendTime seconds long phase inserted, morphing from the last
- * frame to the first.
- *
- * @param blendTime
- * The blend time to set
- */
- public void setBlendTime(final float blendTime) {
- this.blendTime = blendTime;
- }
-
- /**
- * Gets the currently set blending time for smooth animation transitions
- *
- * @return The current blend time
- * @see #setBlendTime(float blendTime)
- */
- public float getBlendTime() {
- return blendTime;
- }
-
- /**
- * This is used by update(float). It calculates PointInTime before and after as well as makes adjustments on what to
- * do when curTime is beyond the MinTime and MaxTime bounds
- */
- private void findFrame() {
- // If we're in our special wrapping case then just ignore changing
- // frames. Once we get back into the actual series we'll revert back
- // to the normal process
- if (_curTime < getMinTime() && _nextFrame < _curFrame) {
- return;
- }
-
- // Update the rest to maintain our new nextFrame marker as one infront
- // of the curFrame in all cases. The wrap case is where the real work
- // is done.
- if (_curTime > getMaxTime()) {
- if (_isSmooth) {
- swapKeyframeSets();
- _isSmooth = false;
- _curTime = _tempNewBeginTime;
- _curFrame = 0;
- _nextFrame = 1;
- setNewAnimationTimes(_tempNewBeginTime, _tempNewEndTime);
- return;
- }
- if (getRepeatType() == ComplexSpatialController.RepeatType.WRAP) {
- final float delta = blendTime;
- _curTime = getMinTime() - delta;
- _curFrame = Math.min(_curFrame + 1, _keyframes.size() - 1);
-
- for (_nextFrame = 0; _nextFrame < _keyframes.size() - 1; _nextFrame++) {
- if (getMinTime() <= _keyframes.get(_nextFrame)._time) {
- break;
- }
- }
- return;
- } else if (getRepeatType() == ComplexSpatialController.RepeatType.CLAMP) {
- return;
- } else { // Then assume it's RT_CYCLE
- _movingForward = false;
- _curTime = getMaxTime();
- }
- } else if (_curTime < getMinTime()) {
- if (getRepeatType() == ComplexSpatialController.RepeatType.WRAP) {
- _curTime = getMaxTime();
- _curFrame = 0;
- } else if (getRepeatType() == ComplexSpatialController.RepeatType.CLAMP) {
- return;
- } else { // Then assume it's RT_CYCLE
- _movingForward = true;
- _curTime = getMinTime();
- }
- }
-
- _nextFrame = _curFrame + 1;
-
- if (_curTime > _keyframes.get(_curFrame)._time) {
- if (_curTime < _keyframes.get(_curFrame + 1)._time) {
- _nextFrame = _curFrame + 1;
- return;
- }
-
- for (; _curFrame < _keyframes.size() - 1; _curFrame++) {
- if (_curTime <= _keyframes.get(_curFrame + 1)._time) {
- _nextFrame = _curFrame + 1;
- return;
- }
- }
-
- // This -should- be unreachable because of the above
- _curTime = getMinTime();
- _curFrame = 0;
- _nextFrame = _curFrame + 1;
- return;
- }
-
- for (; _curFrame >= 0; _curFrame--) {
- if (_curTime >= _keyframes.get(_curFrame)._time) {
- _nextFrame = _curFrame + 1;
- return;
- }
- }
-
- // This should be unreachable because curTime>=0 and
- // keyframes[0].time=0;
- _curFrame = 0;
- _nextFrame = _curFrame + 1;
- }
-
- /**
- * This class defines a point in time that states _morphShape should look like _newShape at _time seconds
- */
- public static class PointInTime implements Savable {
-
- public Mesh _newShape;
-
- public double _time;
-
- public PointInTime() {}
-
- public PointInTime(final double time, final Mesh shape) {
- this._time = time;
- this._newShape = shape;
- }
-
- public void read(final InputCapsule capsule) throws IOException {
- _time = capsule.readDouble("time", 0);
- _newShape = (Mesh) capsule.readSavable("newShape", null);
- }
-
- public void write(final OutputCapsule capsule) throws IOException {
- capsule.write(_time, "time", 0);
- capsule.write(_newShape, "newShape", null);
- }
-
- public Class<?> getClassTag() {
- return this.getClass();
- }
- }
-
- @SuppressWarnings("unchecked")
- private void readObject(final java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
- in.defaultReadObject();
- _keyframes = (ArrayList<PointInTime>) in.readObject();
- _movingForward = true;
- }
-
- public Mesh getMorphMesh() {
- return _morphMesh;
- }
-
- @SuppressWarnings("rawtypes")
- @Override
- public Class<? extends KeyframeController> getClassTag() {
- return this.getClass();
- }
-
- @Override
- public void read(final InputCapsule capsule) throws IOException {
- super.read(capsule);
- _updateBounding = capsule.readBoolean("updateBounding", false);
- _morphMesh = (Mesh) capsule.readSavable("morphMesh", null);
- _keyframes = (ArrayList<PointInTime>) capsule.readSavableList("keyframes", new ArrayList<PointInTime>());
- _movingForward = true;
- }
-
- @Override
- public void write(final OutputCapsule capsule) throws IOException {
- super.write(capsule);
- capsule.write(_updateBounding, "updateBounding", true);
- capsule.write(_morphMesh, "morphMesh", null);
- capsule.writeSavableList(_keyframes, "keyframes", new ArrayList<PointInTime>());
- }
-
-}
+/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.model.util; + +import java.io.IOException; +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.logging.Logger; + +import com.ardor3d.scenegraph.FloatBufferData; +import com.ardor3d.scenegraph.IndexBufferData; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.scenegraph.Spatial; +import com.ardor3d.scenegraph.controller.ComplexSpatialController; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.export.Savable; +import com.ardor3d.util.geom.BufferUtils; + +/** + * TODO: Revisit for better Ardor3D integration. + * + * Started Date: Jun 12, 2004 <br> + * + * + * Class can do linear interpolation of a Mesh between units of time. Similar to VertexKeyframeController but + * interpolates float units of time instead of integer key frames. + * + * setSpeed(float) sets a speed relative to the defined speed. For example, the default is 1. A speed of 2 would run + * twice as fast and a speed of .5 would run half as fast + * + * setMinTime(float) and setMaxTime(float) both define the bounds that KeyframeController should follow. It is the + * programmer's responsibility to make sure that the MinTime and MaxTime are within the span of the defined setKeyframe + * + * Controller functions RepeatType and isActive are both defined in their default way for KeyframeController + * + * When this controller is saved/loaded to XML format, it assumes that the mesh it morphs is the Mesh it belongs to, so + * it is recommended to only attach this controller to the Mesh it animates. + * + * (Based on work by Jack Lindamood, kevglass (parts), hevee (blend time), Julien Gouesse (port to Ardor3D)) + */ + +public class KeyframeController<T extends Spatial> extends ComplexSpatialController<T> { + + private static final Logger logger = Logger.getLogger(KeyframeController.class.getName()); + + private static final long serialVersionUID = 1L; + + /** + * An array of PointInTime s that defines the animation + */ + transient public ArrayList<PointInTime> _keyframes; + + /** + * A special array used with SmoothTransform to store temporary smooth transforms + */ + transient private ArrayList<PointInTime> _prevKeyframes; + + /** + * The mesh that is actually morphed + */ + private Mesh _morphMesh; + + /** + * The current time in the animation + */ + transient private double _curTime; + + /** + * The current frame of the animation + */ + transient private int _curFrame; + + /** + * The frame of animation we're heading towards + */ + transient private int _nextFrame; + + /** + * The PointInTime before curTime + */ + transient private PointInTime _before; + + /** + * The PointInTime after curTime + */ + transient private PointInTime _after; + + /** + * If true, the animation is moving forward, if false the animation is moving backwards + */ + transient private boolean _movingForward = true; + + /** + * Used with SmoothTransform to signal it is doing a smooth transform + */ + transient private boolean _isSmooth; + + /** + * Used with SmoothTransform to signal it is doing a smooth transform + */ + transient private boolean _interpTex = true; + + /** + * Used with SmoothTransform to hold the new beginning and ending time once the transform is complete + */ + transient private double _tempNewBeginTime; + + transient private double _tempNewEndTime; + + /** If true, the model's bounding volume will update every frame. */ + private boolean _updateBounding = true; + + /** + * Default constructor. Speed is 1, MinTime is 0 MaxTime is 0. Both MinTime and MaxTime are automatically adjusted + * by setKeyframe if the setKeyframe time is less than MinTime or greater than MaxTime. Default RepeatType is WRAP. + */ + public KeyframeController() { + setSpeed(1); + _keyframes = new ArrayList<PointInTime>(); + _curFrame = 0; + setRepeatType(ComplexSpatialController.RepeatType.WRAP); + setMinTime(0); + setMaxTime(0); + } + + public double getCurrentTime() { + return _curTime; + } + + public int getCurrentFrame() { + return _curFrame; + } + + /** + * Gets the current time in the animation + */ + public double getCurTime() { + return _curTime; + } + + /** + * Sets the current time in the animation + * + * @param time + * The time this Controller should continue at + */ + public void setCurTime(final double time) { + _curTime = time; + } + + /** + * Sets the Mesh that will be physically changed by this KeyframeController + * + * @param morph + * The new mesh to morph + */ + public void setMorphingMesh(final Mesh morph) { + _morphMesh = morph; + _keyframes.clear(); + _keyframes.add(new PointInTime(0, null)); + } + + public void shallowSetMorphMesh(final Mesh morph) { + _morphMesh = morph; + } + + /** + * Tells the controller to change its morphMesh to shape at time seconds. Time must be >=0 and shape must be + * non-null and shape must have the same number of vertexes as the current shape. If not, then nothing happens. It + * is also required that setMorphingMesh(TriMesh) is called before setKeyframe. It is assumed that shape.indices == + * morphMesh.indices, otherwise morphing may look funny + * + * @param time + * The time for the change + * @param shape + * The new shape at that time + */ + public void setKeyframe(final double time, final Mesh shape) { + if (_morphMesh == null + || time < 0 + || shape.getMeshData().getVertexBuffer().capacity() != _morphMesh.getMeshData().getVertexBuffer() + .capacity()) { + return; + } + for (int i = 0; i < _keyframes.size(); i++) { + final PointInTime lookingTime = _keyframes.get(i); + if (lookingTime._time == time) { + lookingTime._newShape = shape; + return; + } + if (lookingTime._time > time) { + _keyframes.add(i, new PointInTime(time, shape)); + return; + } + } + _keyframes.add(new PointInTime(time, shape)); + if (time > getMaxTime()) { + setMaxTime(time); + } + if (time < getMinTime()) { + setMinTime(time); + } + } + + /** + * This function will do a smooth translation between a keframe's current look, to the look directly at + * newTimeToReach. It takes translationLen time (in sec) to do that translation, and once translated will animate + * like normal between newBeginTime and newEndTime <br> + * <br> + * This would be usefull for example when a figure stops running and tries to raise an arm. Instead of "teleporting" + * to the raise-arm animation begining, a smooth translation can occur. + * + * @param newTimeToReach + * The time to reach. + * @param translationLen + * How long it takes + * @param newBeginTime + * The new cycle begining time + * @param newEndTime + * The new cycle ending time. + */ + public void setSmoothTranslation(final float newTimeToReach, final float translationLen, final float newBeginTime, + final float newEndTime) { + if (!isActive() || _isSmooth) { + return; + } + if (newBeginTime < 0 || newBeginTime > _keyframes.get(_keyframes.size() - 1)._time) { + KeyframeController.logger.warning("Attempt to set invalid begintime:" + newBeginTime); + return; + } + if (newEndTime < 0 || newEndTime > _keyframes.get(_keyframes.size() - 1)._time) { + KeyframeController.logger.warning("Attempt to set invalid endtime:" + newEndTime); + return; + } + Mesh begin = null, end = null; + if (_prevKeyframes == null) { + _prevKeyframes = new ArrayList<PointInTime>(); + begin = new Mesh(); + end = new Mesh(); + } else { + begin = _prevKeyframes.get(0)._newShape; + end = _prevKeyframes.get(1)._newShape; + _prevKeyframes.clear(); + } + + getCurrent(begin); + + _curTime = newTimeToReach; + _curFrame = 0; + setMinTime(0); + setMaxTime(_keyframes.get(_keyframes.size() - 1)._time); + update(0.0d, null); + getCurrent(end); + + swapKeyframeSets(); + _curTime = 0; + _curFrame = 0; + setMinTime(0); + setMaxTime(translationLen); + setKeyframe(0, begin); + setKeyframe(translationLen, end); + _isSmooth = true; + _tempNewBeginTime = newBeginTime; + _tempNewEndTime = newEndTime; + } + + /** + * Swaps prevKeyframes and keyframes + */ + private void swapKeyframeSets() { + final ArrayList<PointInTime> temp = _keyframes; + _keyframes = _prevKeyframes; + _prevKeyframes = temp; + } + + /** + * Sets the new animation boundaries for this controller. This will start at newBeginTime and proceed in the + * direction of newEndTime (either forwards or backwards). If both are the same, then the animation is set to their + * time and turned off, otherwise the animation is turned on to start the animation acording to the repeat type. If + * either BeginTime or EndTime are invalid times (less than 0 or greater than the maximum set keyframe time) then a + * warning is set and nothing happens. <br> + * It is suggested that this function be called if new animation boundaries need to be set, instead of setMinTime + * and setMaxTime directly. + * + * @param newBeginTime + * The starting time + * @param newEndTime + * The ending time + */ + public void setNewAnimationTimes(final double newBeginTime, final double newEndTime) { + if (_isSmooth) { + return; + } + if (newBeginTime < 0 || newBeginTime > _keyframes.get(_keyframes.size() - 1)._time) { + KeyframeController.logger.warning("Attempt to set invalid begintime:" + newBeginTime); + return; + } + if (newEndTime < 0 || newEndTime > _keyframes.get(_keyframes.size() - 1)._time) { + KeyframeController.logger.warning("Attempt to set invalid endtime:" + newEndTime); + return; + } + setMinTime(newBeginTime); + setMaxTime(newEndTime); + setActive(true); + if (newBeginTime <= newEndTime) { // Moving forward + _movingForward = true; + _curTime = newBeginTime; + if (newBeginTime == newEndTime) { + update(0.0d, null); + setActive(false); + } + } else { // Moving backwards + _movingForward = false; + _curTime = newEndTime; + } + } + + /** + * Saves whatever the current morphMesh looks like into the dataCopy + * + * @param dataCopy + * The copy to save the current mesh into + */ + private void getCurrent(final Mesh dataCopy) { + if (_morphMesh.getMeshData().getColorBuffer() != null) { + FloatBuffer dcColors = dataCopy.getMeshData().getColorBuffer(); + if (dcColors != null) { + dcColors.clear(); + } + final FloatBuffer mmColors = _morphMesh.getMeshData().getColorBuffer(); + mmColors.clear(); + if (dcColors == null || dcColors.capacity() != mmColors.capacity()) { + dcColors = BufferUtils.createFloatBuffer(mmColors.capacity()); + dcColors.clear(); + dataCopy.getMeshData().setColorBuffer(dcColors); + } + + dcColors.put(mmColors); + dcColors.flip(); + } + if (_morphMesh.getMeshData().getVertexBuffer() != null) { + FloatBuffer dcVerts = dataCopy.getMeshData().getVertexBuffer(); + if (dcVerts != null) { + dcVerts.clear(); + } + final FloatBuffer mmVerts = _morphMesh.getMeshData().getVertexBuffer(); + mmVerts.clear(); + if (dcVerts == null || dcVerts.capacity() != mmVerts.capacity()) { + dcVerts = BufferUtils.createFloatBuffer(mmVerts.capacity()); + dcVerts.clear(); + dataCopy.getMeshData().setVertexBuffer(dcVerts); + } + + dcVerts.put(mmVerts); + dcVerts.flip(); + } + if (_morphMesh.getMeshData().getNormalBuffer() != null) { + FloatBuffer dcNorms = dataCopy.getMeshData().getNormalBuffer(); + if (dcNorms != null) { + dcNorms.clear(); + } + final FloatBuffer mmNorms = _morphMesh.getMeshData().getNormalBuffer(); + mmNorms.clear(); + if (dcNorms == null || dcNorms.capacity() != mmNorms.capacity()) { + dcNorms = BufferUtils.createFloatBuffer(mmNorms.capacity()); + dcNorms.clear(); + dataCopy.getMeshData().setNormalBuffer(dcNorms); + } + + dcNorms.put(mmNorms); + dcNorms.flip(); + } + if (_morphMesh.getMeshData().getIndices() != null) { + IndexBufferData<?> dcInds = dataCopy.getMeshData().getIndices(); + if (dcInds != null) { + dcInds.rewind(); + } + final IndexBufferData<?> mmInds = _morphMesh.getMeshData().getIndices(); + mmInds.clear(); + if (dcInds == null || dcInds.capacity() != mmInds.capacity() || dcInds.getClass() != mmInds.getClass()) { + dcInds = BufferUtils.createIndexBufferData(mmInds.capacity(), _morphMesh.getMeshData() + .getVertexBuffer().capacity() - 1); + dcInds.clear(); + dataCopy.getMeshData().setIndices(dcInds); + } + + dcInds.put(mmInds); + dcInds.flip(); + } + if (_morphMesh.getMeshData().getTextureCoords(0) != null) { + FloatBuffer dcTexs = dataCopy.getMeshData().getTextureCoords(0).getBuffer(); + if (dcTexs != null) { + dcTexs.clear(); + } + final FloatBuffer mmTexs = _morphMesh.getMeshData().getTextureCoords(0).getBuffer(); + mmTexs.clear(); + if (dcTexs == null || dcTexs.capacity() != mmTexs.capacity()) { + dcTexs = BufferUtils.createFloatBuffer(mmTexs.capacity()); + dcTexs.clear(); + dataCopy.getMeshData().setTextureCoords(new FloatBufferData(dcTexs, 2), 0); + } + + dcTexs.put(mmTexs); + dcTexs.flip(); + } + } + + /** + * As defined in Controller + * + * @param time + * as defined in Controller + */ + @Override + public void update(final double time, final T caller) { + if (easyQuit()) { + return; + } + if (_movingForward) { + _curTime += time * getSpeed(); + } else { + _curTime -= time * getSpeed(); + } + + findFrame(); + _before = _keyframes.get(_curFrame); + // Change this bit so the next frame we're heading towards isn't always going + // to be one frame ahead since now we coule be animating from the last to first + // frames. + // after = keyframes.get(curFrame + 1)); + _after = _keyframes.get(_nextFrame); + + double delta = (_curTime - _before._time) / (_after._time - _before._time); + + // If we doing that wrapping bit then delta should be caculated based + // on the time before the start of the animation we are. + if (_nextFrame < _curFrame) { + delta = blendTime - (getMinTime() - _curTime); + } + + final Mesh oldShape = _before._newShape; + final Mesh newShape = _after._newShape; + + final FloatBuffer verts = _morphMesh.getMeshData().getVertexBuffer(); + final FloatBuffer norms = _morphMesh.getMeshData().getNormalBuffer(); + final FloatBuffer texts = _interpTex ? _morphMesh.getMeshData().getTextureCoords(0) != null ? _morphMesh + .getMeshData().getTextureCoords(0).getBuffer() : null : null; + final FloatBuffer colors = _morphMesh.getMeshData().getColorBuffer(); + + final FloatBuffer oldverts = oldShape.getMeshData().getVertexBuffer(); + final FloatBuffer oldnorms = oldShape.getMeshData().getNormalBuffer(); + final FloatBuffer oldtexts = _interpTex ? oldShape.getMeshData().getTextureCoords(0) != null ? oldShape + .getMeshData().getTextureCoords(0).getBuffer() : null : null; + final FloatBuffer oldcolors = oldShape.getMeshData().getColorBuffer(); + + final FloatBuffer newverts = newShape.getMeshData().getVertexBuffer(); + final FloatBuffer newnorms = newShape.getMeshData().getNormalBuffer(); + final FloatBuffer newtexts = _interpTex ? newShape.getMeshData().getTextureCoords(0) != null ? newShape + .getMeshData().getTextureCoords(0).getBuffer() : null : null; + final FloatBuffer newcolors = newShape.getMeshData().getColorBuffer(); + if (verts == null || oldverts == null || newverts == null) { + return; + } + final int vertQuantity = verts.capacity() / 3; + verts.rewind(); + oldverts.rewind(); + newverts.rewind(); + + if (norms != null) { + norms.rewind(); // reset to start + } + if (oldnorms != null) { + oldnorms.rewind(); // reset to start + } + if (newnorms != null) { + newnorms.rewind(); // reset to start + } + + if (texts != null) { + texts.rewind(); // reset to start + } + if (oldtexts != null) { + oldtexts.rewind(); // reset to start + } + if (newtexts != null) { + newtexts.rewind(); // reset to start + } + + if (colors != null) { + colors.rewind(); // reset to start + } + if (oldcolors != null) { + oldcolors.rewind(); // reset to start + } + if (newcolors != null) { + newcolors.rewind(); // reset to start + } + + for (int i = 0; i < vertQuantity; i++) { + for (int x = 0; x < 3; x++) { + verts.put(i * 3 + x, (float) ((1f - delta) * oldverts.get(i * 3 + x) + delta * newverts.get(i * 3 + x))); + } + + if (norms != null && oldnorms != null && newnorms != null) { + for (int x = 0; x < 3; x++) { + norms.put(i * 3 + x, + (float) ((1f - delta) * oldnorms.get(i * 3 + x) + delta * newnorms.get(i * 3 + x))); + } + } + + if (_interpTex && texts != null && oldtexts != null && newtexts != null) { + for (int x = 0; x < 2; x++) { + texts.put(i * 2 + x, + (float) ((1f - delta) * oldtexts.get(i * 2 + x) + delta * newtexts.get(i * 2 + x))); + } + } + + if (colors != null && oldcolors != null && newcolors != null) { + for (int x = 0; x < 4; x++) { + colors.put(i * 4 + x, + (float) ((1f - delta) * oldcolors.get(i * 4 + x) + delta * newcolors.get(i * 4 + x))); + } + } + } + + if (_updateBounding) { + _morphMesh.updateModelBound(); + } + } + + /** + * If both min and max time are equal and the model is already updated, then it's an easy quit, or if it's on CLAMP + * and I've exceeded my time it's also an easy quit. + * + * @return true if update doesn't need to be called, false otherwise + */ + private boolean easyQuit() { + if (getMaxTime() == getMinTime() && _curTime != getMinTime()) { + return true; + } else if (getRepeatType() == ComplexSpatialController.RepeatType.CLAMP + && (_curTime > getMaxTime() || _curTime < getMinTime())) { + return true; + } else if (_keyframes.size() < 2) { + return true; + } + return false; + } + + /** + * If true, the model's bounding volume will be updated every frame. If false, it will not. + * + * @param update + * The new update model volume per frame value. + */ + public void setUpdateBounding(final boolean update) { + _updateBounding = update; + } + + /** + * Returns true if the model's bounding volume is being updated every frame. + * + * @return True if bounding volume is updating. + */ + public boolean isUpdateBounding() { + return _updateBounding; + } + + public void setInterpTex(final boolean interpTex) { + _interpTex = interpTex; + } + + public boolean isInterpTex() { + return _interpTex; + } + + private float blendTime = 0; + + /** + * If repeat type RT_WRAP is set, after reaching the last frame of the currently set animation maxTime (see + * Controller.setMaxTime), there will be an additional blendTime seconds long phase inserted, morphing from the last + * frame to the first. + * + * @param blendTime + * The blend time to set + */ + public void setBlendTime(final float blendTime) { + this.blendTime = blendTime; + } + + /** + * Gets the currently set blending time for smooth animation transitions + * + * @return The current blend time + * @see #setBlendTime(float blendTime) + */ + public float getBlendTime() { + return blendTime; + } + + /** + * This is used by update(float). It calculates PointInTime before and after as well as makes adjustments on what to + * do when curTime is beyond the MinTime and MaxTime bounds + */ + private void findFrame() { + // If we're in our special wrapping case then just ignore changing + // frames. Once we get back into the actual series we'll revert back + // to the normal process + if (_curTime < getMinTime() && _nextFrame < _curFrame) { + return; + } + + // Update the rest to maintain our new nextFrame marker as one infront + // of the curFrame in all cases. The wrap case is where the real work + // is done. + if (_curTime > getMaxTime()) { + if (_isSmooth) { + swapKeyframeSets(); + _isSmooth = false; + _curTime = _tempNewBeginTime; + _curFrame = 0; + _nextFrame = 1; + setNewAnimationTimes(_tempNewBeginTime, _tempNewEndTime); + return; + } + if (getRepeatType() == ComplexSpatialController.RepeatType.WRAP) { + final float delta = blendTime; + _curTime = getMinTime() - delta; + _curFrame = Math.min(_curFrame + 1, _keyframes.size() - 1); + + for (_nextFrame = 0; _nextFrame < _keyframes.size() - 1; _nextFrame++) { + if (getMinTime() <= _keyframes.get(_nextFrame)._time) { + break; + } + } + return; + } else if (getRepeatType() == ComplexSpatialController.RepeatType.CLAMP) { + return; + } else { // Then assume it's RT_CYCLE + _movingForward = false; + _curTime = getMaxTime(); + } + } else if (_curTime < getMinTime()) { + if (getRepeatType() == ComplexSpatialController.RepeatType.WRAP) { + _curTime = getMaxTime(); + _curFrame = 0; + } else if (getRepeatType() == ComplexSpatialController.RepeatType.CLAMP) { + return; + } else { // Then assume it's RT_CYCLE + _movingForward = true; + _curTime = getMinTime(); + } + } + + _nextFrame = _curFrame + 1; + + if (_curTime > _keyframes.get(_curFrame)._time) { + if (_curTime < _keyframes.get(_curFrame + 1)._time) { + _nextFrame = _curFrame + 1; + return; + } + + for (; _curFrame < _keyframes.size() - 1; _curFrame++) { + if (_curTime <= _keyframes.get(_curFrame + 1)._time) { + _nextFrame = _curFrame + 1; + return; + } + } + + // This -should- be unreachable because of the above + _curTime = getMinTime(); + _curFrame = 0; + _nextFrame = _curFrame + 1; + return; + } + + for (; _curFrame >= 0; _curFrame--) { + if (_curTime >= _keyframes.get(_curFrame)._time) { + _nextFrame = _curFrame + 1; + return; + } + } + + // This should be unreachable because curTime>=0 and + // keyframes[0].time=0; + _curFrame = 0; + _nextFrame = _curFrame + 1; + } + + /** + * This class defines a point in time that states _morphShape should look like _newShape at _time seconds + */ + public static class PointInTime implements Savable { + + public Mesh _newShape; + + public double _time; + + public PointInTime() {} + + public PointInTime(final double time, final Mesh shape) { + this._time = time; + this._newShape = shape; + } + + public void read(final InputCapsule capsule) throws IOException { + _time = capsule.readDouble("time", 0); + _newShape = (Mesh) capsule.readSavable("newShape", null); + } + + public void write(final OutputCapsule capsule) throws IOException { + capsule.write(_time, "time", 0); + capsule.write(_newShape, "newShape", null); + } + + public Class<?> getClassTag() { + return this.getClass(); + } + } + + @SuppressWarnings("unchecked") + private void readObject(final java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + _keyframes = (ArrayList<PointInTime>) in.readObject(); + _movingForward = true; + } + + public Mesh getMorphMesh() { + return _morphMesh; + } + + @SuppressWarnings("rawtypes") + @Override + public Class<? extends KeyframeController> getClassTag() { + return this.getClass(); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _updateBounding = capsule.readBoolean("updateBounding", false); + _morphMesh = (Mesh) capsule.readSavable("morphMesh", null); + _keyframes = (ArrayList<PointInTime>) capsule.readSavableList("keyframes", new ArrayList<PointInTime>()); + _movingForward = true; + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_updateBounding, "updateBounding", true); + capsule.write(_morphMesh, "morphMesh", null); + capsule.writeSavableList(_keyframes, "keyframes", new ArrayList<PointInTime>()); + } + +} diff --git a/ardor3d-extras/src/main/java/com/ardor3d/extension/model/util/nvtristrip/NvEdgeInfo.java b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/util/nvtristrip/NvEdgeInfo.java index 3ce40c2..ee49d56 100644 --- a/ardor3d-extras/src/main/java/com/ardor3d/extension/model/util/nvtristrip/NvEdgeInfo.java +++ b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/util/nvtristrip/NvEdgeInfo.java @@ -1,36 +1,36 @@ -/**
- * Copyright (c) 2008-2012 Ardor Labs, Inc.
- *
- * This file is part of Ardor3D.
- *
- * Ardor3D is free software: you can redistribute it and/or modify it
- * under the terms of its license which may be found in the accompanying
- * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
- */
-
-package com.ardor3d.extension.model.util.nvtristrip;
-
-/**
- * Ported from <a href="http://developer.nvidia.com/object/nvtristrip_library.html">NVIDIA's NvTriStrip Library</a>
- */
-final class NvEdgeInfo {
- long _refCount;
- NvFaceInfo _face0, _face1;
- int _v0, _v1;
- NvEdgeInfo _nextV0, _nextV1;
-
- // constructor puts 1 ref on us
- NvEdgeInfo(final int v0, final int v1) {
- _v0 = v0;
- _v1 = v1;
- _face0 = null;
- _face1 = null;
- _nextV0 = null;
- _nextV1 = null;
-
- // we will appear in 2 lists. this is a good
- // way to make sure we delete it the second time
- // we hit it in the edge infos
- _refCount = 2;
- }
-}
+/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.model.util.nvtristrip; + +/** + * Ported from <a href="http://developer.nvidia.com/object/nvtristrip_library.html">NVIDIA's NvTriStrip Library</a> + */ +final class NvEdgeInfo { + long _refCount; + NvFaceInfo _face0, _face1; + int _v0, _v1; + NvEdgeInfo _nextV0, _nextV1; + + // constructor puts 1 ref on us + NvEdgeInfo(final int v0, final int v1) { + _v0 = v0; + _v1 = v1; + _face0 = null; + _face1 = null; + _nextV0 = null; + _nextV1 = null; + + // we will appear in 2 lists. this is a good + // way to make sure we delete it the second time + // we hit it in the edge infos + _refCount = 2; + } +} diff --git a/ardor3d-extras/src/main/java/com/ardor3d/extension/model/util/nvtristrip/NvFaceInfo.java b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/util/nvtristrip/NvFaceInfo.java index ae51a6a..688c73d 100644 --- a/ardor3d-extras/src/main/java/com/ardor3d/extension/model/util/nvtristrip/NvFaceInfo.java +++ b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/util/nvtristrip/NvFaceInfo.java @@ -1,45 +1,45 @@ -/**
- * Copyright (c) 2008-2012 Ardor Labs, Inc.
- *
- * This file is part of Ardor3D.
- *
- * Ardor3D is free software: you can redistribute it and/or modify it
- * under the terms of its license which may be found in the accompanying
- * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
- */
-
-package com.ardor3d.extension.model.util.nvtristrip;
-
-/**
- * Ported from <a href="http://developer.nvidia.com/object/nvtristrip_library.html">NVIDIA's NvTriStrip Library</a>
- */
-final class NvFaceInfo {
- int _v0, _v1, _v2;
- int _stripId; // real strip Id
- int _testStripId; // strip Id in an experiment
- int _experimentId; // in what experiment was it given an experiment Id?
- boolean _isFake; // if true, will be deleted when the strip it's in is deleted
-
- NvFaceInfo(final int v0, final int v1, final int v2) {
- this(v0, v1, v2, false);
- }
-
- NvFaceInfo(final int v0, final int v1, final int v2, final boolean bIsFake) {
- _v0 = v0;
- _v1 = v1;
- _v2 = v2;
- _stripId = -1;
- _testStripId = -1;
- _experimentId = -1;
- _isFake = bIsFake;
- }
-
- /**
- * Copies only v0, v1 and v2
- *
- * @param source
- */
- public NvFaceInfo(final NvFaceInfo source) {
- this(source._v0, source._v1, source._v2);
- }
-}
+/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.model.util.nvtristrip; + +/** + * Ported from <a href="http://developer.nvidia.com/object/nvtristrip_library.html">NVIDIA's NvTriStrip Library</a> + */ +final class NvFaceInfo { + int _v0, _v1, _v2; + int _stripId; // real strip Id + int _testStripId; // strip Id in an experiment + int _experimentId; // in what experiment was it given an experiment Id? + boolean _isFake; // if true, will be deleted when the strip it's in is deleted + + NvFaceInfo(final int v0, final int v1, final int v2) { + this(v0, v1, v2, false); + } + + NvFaceInfo(final int v0, final int v1, final int v2, final boolean bIsFake) { + _v0 = v0; + _v1 = v1; + _v2 = v2; + _stripId = -1; + _testStripId = -1; + _experimentId = -1; + _isFake = bIsFake; + } + + /** + * Copies only v0, v1 and v2 + * + * @param source + */ + public NvFaceInfo(final NvFaceInfo source) { + this(source._v0, source._v1, source._v2); + } +} diff --git a/ardor3d-extras/src/main/java/com/ardor3d/extension/model/util/nvtristrip/NvStripInfo.java b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/util/nvtristrip/NvStripInfo.java index 5808e5d..2cf0e18 100644 --- a/ardor3d-extras/src/main/java/com/ardor3d/extension/model/util/nvtristrip/NvStripInfo.java +++ b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/util/nvtristrip/NvStripInfo.java @@ -1,323 +1,323 @@ -/**
- * Copyright (c) 2008-2012 Ardor Labs, Inc.
- *
- * This file is part of Ardor3D.
- *
- * Ardor3D is free software: you can redistribute it and/or modify it
- * under the terms of its license which may be found in the accompanying
- * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
- */
-
-package com.ardor3d.extension.model.util.nvtristrip;
-
-import java.util.List;
-
-import com.google.common.collect.Lists;
-
-final class NvStripInfo {
- NvStripStartInfo _startInfo;
- List<NvFaceInfo> _faces = Lists.newArrayList();
- int _stripId;
- int _experimentId;
-
- boolean _visited;
-
- int _numDegenerates;
-
- // A little information about the creation of the triangle strips
- NvStripInfo(final NvStripStartInfo startInfo, final int stripId) {
- this(startInfo, stripId, -1);
- }
-
- NvStripInfo(final NvStripStartInfo startInfo, final int stripId, final int experimentId) {
- _startInfo = startInfo;
- _stripId = stripId;
- _experimentId = experimentId;
- _visited = false;
- _numDegenerates = 0;
- }
-
- /**
- * @return true if the experiment id is >= 0
- */
- final boolean isExperiment() {
- return _experimentId >= 0;
- }
-
- /**
- * @param faceInfo
- * @return
- */
- final boolean IsInStrip(final NvFaceInfo faceInfo) {
- if (faceInfo == null) {
- return false;
- }
-
- return _experimentId >= 0 ? faceInfo._testStripId == _stripId : faceInfo._stripId == _stripId;
- }
-
- /**
- *
- * @param faceInfo
- * @param edgeInfos
- * @return true if the input face and the current strip share an edge
- */
- boolean sharesEdge(final NvFaceInfo faceInfo, final List<NvEdgeInfo> edgeInfos) {
- // check v0->v1 edge
- NvEdgeInfo currEdge = NvStripifier.findEdgeInfo(edgeInfos, faceInfo._v0, faceInfo._v1);
-
- if (IsInStrip(currEdge._face0) || IsInStrip(currEdge._face1)) {
- return true;
- }
-
- // check v1->v2 edge
- currEdge = NvStripifier.findEdgeInfo(edgeInfos, faceInfo._v1, faceInfo._v2);
-
- if (IsInStrip(currEdge._face0) || IsInStrip(currEdge._face1)) {
- return true;
- }
-
- // check v2->v0 edge
- currEdge = NvStripifier.findEdgeInfo(edgeInfos, faceInfo._v2, faceInfo._v0);
-
- if (IsInStrip(currEdge._face0) || IsInStrip(currEdge._face1)) {
- return true;
- }
-
- return false;
- }
-
- /**
- * take the given forward and backward strips and combine them together
- *
- * @param forward
- * @param backward
- */
- void combine(final List<NvFaceInfo> forward, final List<NvFaceInfo> backward) {
- // add backward faces
- int numFaces = backward.size();
- for (int i = numFaces - 1; i >= 0; i--) {
- _faces.add(backward.get(i));
- }
-
- // add forward faces
- numFaces = forward.size();
- for (int i = 0; i < numFaces; i++) {
- _faces.add(forward.get(i));
- }
- }
-
- /**
- * @param faceVec
- * @param face
- * @return true if the face is "unique", i.e. has a vertex which doesn't exist in the faceVec
- */
- boolean unique(final List<NvFaceInfo> faceVec, final NvFaceInfo face) {
- boolean bv0, bv1, bv2; // bools to indicate whether a vertex is in the faceVec or not
- bv0 = bv1 = bv2 = false;
-
- for (int i = 0; i < faceVec.size(); i++) {
- if (!bv0) {
- if (faceVec.get(i)._v0 == face._v0 || faceVec.get(i)._v1 == face._v0 || faceVec.get(i)._v2 == face._v0) {
- bv0 = true;
- }
- }
-
- if (!bv1) {
- if (faceVec.get(i)._v0 == face._v1 || faceVec.get(i)._v1 == face._v1 || faceVec.get(i)._v2 == face._v1) {
- bv1 = true;
- }
- }
-
- if (!bv2) {
- if (faceVec.get(i)._v0 == face._v2 || faceVec.get(i)._v1 == face._v2 || faceVec.get(i)._v2 == face._v2) {
- bv2 = true;
- }
- }
-
- // the face is not unique, all its vertices exist in the face vector
- if (bv0 && bv1 && bv2) {
- return false;
- }
- }
-
- // if we get out here, it's unique
- return true;
- }
-
- /**
- * If either the faceInfo has a real strip index because it is already assign to a committed strip OR it is assigned
- * in an experiment and the experiment index is the one we are building for, then it is marked and unavailable
- */
- boolean isMarked(final NvFaceInfo faceInfo) {
- return faceInfo._stripId >= 0 || isExperiment() && faceInfo._experimentId == _experimentId;
- }
-
- /**
- * Marks the face with the current strip ID
- *
- * @param faceInfo
- */
- void markTriangle(final NvFaceInfo faceInfo) {
- assert !isMarked(faceInfo);
- if (isExperiment()) {
- faceInfo._experimentId = _experimentId;
- faceInfo._testStripId = _stripId;
- } else {
- faceInfo._experimentId = -1;
- faceInfo._stripId = _stripId;
- }
- }
-
- /**
- * Builds a strip forward as far as we can go, then builds backwards, and joins the two lists
- *
- * @param edgeInfos
- * @param faceInfos
- */
- void build(final List<NvEdgeInfo> edgeInfos, final List<NvFaceInfo> faceInfos) {
- // used in building the strips forward and backward
- final List<Integer> scratchIndices = Lists.newArrayList();
-
- // build forward... start with the initial face
- final List<NvFaceInfo> forwardFaces = Lists.newArrayList();
- final List<NvFaceInfo> backwardFaces = Lists.newArrayList();
- forwardFaces.add(_startInfo._startFace);
-
- markTriangle(_startInfo._startFace);
-
- final int v0 = _startInfo._toV1 ? _startInfo._startEdge._v0 : _startInfo._startEdge._v1;
- final int v1 = _startInfo._toV1 ? _startInfo._startEdge._v1 : _startInfo._startEdge._v0;
-
- // easiest way to get v2 is to use this function which requires the
- // other indices to already be in the list.
- scratchIndices.add(v0);
- scratchIndices.add(v1);
- final int v2 = NvStripifier.getNextIndex(scratchIndices, _startInfo._startFace);
- scratchIndices.add(v2);
-
- //
- // build the forward list
- //
- int nv0 = v1;
- int nv1 = v2;
-
- NvFaceInfo nextFace = NvStripifier.findOtherFace(edgeInfos, nv0, nv1, _startInfo._startFace);
- while (nextFace != null && !isMarked(nextFace)) {
- // check to see if this next face is going to cause us to die soon
- int testnv0 = nv1;
- final int testnv1 = NvStripifier.getNextIndex(scratchIndices, nextFace);
-
- final NvFaceInfo nextNextFace = NvStripifier.findOtherFace(edgeInfos, testnv0, testnv1, nextFace);
-
- if (nextNextFace == null || isMarked(nextNextFace)) {
- // uh, oh, we're following a dead end, try swapping
- final NvFaceInfo testNextFace = NvStripifier.findOtherFace(edgeInfos, nv0, testnv1, nextFace);
-
- if (testNextFace != null && !isMarked(testNextFace)) {
- // we only swap if it buys us something
-
- // add a "fake" degenerate face
- final NvFaceInfo tempFace = new NvFaceInfo(nv0, nv1, nv0, true);
-
- forwardFaces.add(tempFace);
- markTriangle(tempFace);
-
- scratchIndices.add(nv0);
- testnv0 = nv0;
-
- ++_numDegenerates;
- }
-
- }
-
- // add this to the strip
- forwardFaces.add(nextFace);
-
- markTriangle(nextFace);
-
- // add the index
- // nv0 = nv1;
- // nv1 = NvStripifier.GetNextIndex(scratchIndices, nextFace);
- scratchIndices.add(testnv1);
-
- // and get the next face
- nv0 = testnv0;
- nv1 = testnv1;
-
- nextFace = NvStripifier.findOtherFace(edgeInfos, nv0, nv1, nextFace);
-
- }
-
- // tempAllFaces is going to be forwardFaces + backwardFaces
- // it's used for Unique()
- final List<NvFaceInfo> tempAllFaces = Lists.newArrayList();
- for (int i = 0; i < forwardFaces.size(); i++) {
- tempAllFaces.add(forwardFaces.get(i));
- }
-
- //
- // reset the indices for building the strip backwards and do so
- //
- scratchIndices.clear();
- scratchIndices.add(v2);
- scratchIndices.add(v1);
- scratchIndices.add(v0);
- nv0 = v1;
- nv1 = v0;
- nextFace = NvStripifier.findOtherFace(edgeInfos, nv0, nv1, _startInfo._startFace);
- while (nextFace != null && !isMarked(nextFace)) {
- // this tests to see if a face is "unique", meaning that its vertices aren't already in the list
- // so, strips which "wrap-around" are not allowed
- if (!unique(tempAllFaces, nextFace)) {
- break;
- }
-
- // check to see if this next face is going to cause us to die soon
- int testnv0 = nv1;
- final int testnv1 = NvStripifier.getNextIndex(scratchIndices, nextFace);
-
- final NvFaceInfo nextNextFace = NvStripifier.findOtherFace(edgeInfos, testnv0, testnv1, nextFace);
-
- if (nextNextFace == null || isMarked(nextNextFace)) {
- // uh, oh, we're following a dead end, try swapping
- final NvFaceInfo testNextFace = NvStripifier.findOtherFace(edgeInfos, nv0, testnv1, nextFace);
- if (testNextFace != null && !isMarked(testNextFace)) {
- // we only swap if it buys us something
-
- // add a "fake" degenerate face
- final NvFaceInfo tempFace = new NvFaceInfo(nv0, nv1, nv0, true);
-
- backwardFaces.add(tempFace);
- markTriangle(tempFace);
- scratchIndices.add(nv0);
- testnv0 = nv0;
-
- ++_numDegenerates;
- }
-
- }
-
- // add this to the strip
- backwardFaces.add(nextFace);
-
- // this is just so Unique() will work
- tempAllFaces.add(nextFace);
-
- markTriangle(nextFace);
-
- // add the index
- // nv0 = nv1;
- // nv1 = NvStripifier.GetNextIndex(scratchIndices, nextFace);
- scratchIndices.add(testnv1);
-
- // and get the next face
- nv0 = testnv0;
- nv1 = testnv1;
- nextFace = NvStripifier.findOtherFace(edgeInfos, nv0, nv1, nextFace);
- }
-
- // Combine the forward and backwards stripification lists and put into our own face vector
- combine(forwardFaces, backwardFaces);
- }
-}
+/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.model.util.nvtristrip; + +import java.util.List; + +import com.google.common.collect.Lists; + +final class NvStripInfo { + NvStripStartInfo _startInfo; + List<NvFaceInfo> _faces = Lists.newArrayList(); + int _stripId; + int _experimentId; + + boolean _visited; + + int _numDegenerates; + + // A little information about the creation of the triangle strips + NvStripInfo(final NvStripStartInfo startInfo, final int stripId) { + this(startInfo, stripId, -1); + } + + NvStripInfo(final NvStripStartInfo startInfo, final int stripId, final int experimentId) { + _startInfo = startInfo; + _stripId = stripId; + _experimentId = experimentId; + _visited = false; + _numDegenerates = 0; + } + + /** + * @return true if the experiment id is >= 0 + */ + final boolean isExperiment() { + return _experimentId >= 0; + } + + /** + * @param faceInfo + * @return + */ + final boolean IsInStrip(final NvFaceInfo faceInfo) { + if (faceInfo == null) { + return false; + } + + return _experimentId >= 0 ? faceInfo._testStripId == _stripId : faceInfo._stripId == _stripId; + } + + /** + * + * @param faceInfo + * @param edgeInfos + * @return true if the input face and the current strip share an edge + */ + boolean sharesEdge(final NvFaceInfo faceInfo, final List<NvEdgeInfo> edgeInfos) { + // check v0->v1 edge + NvEdgeInfo currEdge = NvStripifier.findEdgeInfo(edgeInfos, faceInfo._v0, faceInfo._v1); + + if (IsInStrip(currEdge._face0) || IsInStrip(currEdge._face1)) { + return true; + } + + // check v1->v2 edge + currEdge = NvStripifier.findEdgeInfo(edgeInfos, faceInfo._v1, faceInfo._v2); + + if (IsInStrip(currEdge._face0) || IsInStrip(currEdge._face1)) { + return true; + } + + // check v2->v0 edge + currEdge = NvStripifier.findEdgeInfo(edgeInfos, faceInfo._v2, faceInfo._v0); + + if (IsInStrip(currEdge._face0) || IsInStrip(currEdge._face1)) { + return true; + } + + return false; + } + + /** + * take the given forward and backward strips and combine them together + * + * @param forward + * @param backward + */ + void combine(final List<NvFaceInfo> forward, final List<NvFaceInfo> backward) { + // add backward faces + int numFaces = backward.size(); + for (int i = numFaces - 1; i >= 0; i--) { + _faces.add(backward.get(i)); + } + + // add forward faces + numFaces = forward.size(); + for (int i = 0; i < numFaces; i++) { + _faces.add(forward.get(i)); + } + } + + /** + * @param faceVec + * @param face + * @return true if the face is "unique", i.e. has a vertex which doesn't exist in the faceVec + */ + boolean unique(final List<NvFaceInfo> faceVec, final NvFaceInfo face) { + boolean bv0, bv1, bv2; // bools to indicate whether a vertex is in the faceVec or not + bv0 = bv1 = bv2 = false; + + for (int i = 0; i < faceVec.size(); i++) { + if (!bv0) { + if (faceVec.get(i)._v0 == face._v0 || faceVec.get(i)._v1 == face._v0 || faceVec.get(i)._v2 == face._v0) { + bv0 = true; + } + } + + if (!bv1) { + if (faceVec.get(i)._v0 == face._v1 || faceVec.get(i)._v1 == face._v1 || faceVec.get(i)._v2 == face._v1) { + bv1 = true; + } + } + + if (!bv2) { + if (faceVec.get(i)._v0 == face._v2 || faceVec.get(i)._v1 == face._v2 || faceVec.get(i)._v2 == face._v2) { + bv2 = true; + } + } + + // the face is not unique, all its vertices exist in the face vector + if (bv0 && bv1 && bv2) { + return false; + } + } + + // if we get out here, it's unique + return true; + } + + /** + * If either the faceInfo has a real strip index because it is already assign to a committed strip OR it is assigned + * in an experiment and the experiment index is the one we are building for, then it is marked and unavailable + */ + boolean isMarked(final NvFaceInfo faceInfo) { + return faceInfo._stripId >= 0 || isExperiment() && faceInfo._experimentId == _experimentId; + } + + /** + * Marks the face with the current strip ID + * + * @param faceInfo + */ + void markTriangle(final NvFaceInfo faceInfo) { + assert !isMarked(faceInfo); + if (isExperiment()) { + faceInfo._experimentId = _experimentId; + faceInfo._testStripId = _stripId; + } else { + faceInfo._experimentId = -1; + faceInfo._stripId = _stripId; + } + } + + /** + * Builds a strip forward as far as we can go, then builds backwards, and joins the two lists + * + * @param edgeInfos + * @param faceInfos + */ + void build(final List<NvEdgeInfo> edgeInfos, final List<NvFaceInfo> faceInfos) { + // used in building the strips forward and backward + final List<Integer> scratchIndices = Lists.newArrayList(); + + // build forward... start with the initial face + final List<NvFaceInfo> forwardFaces = Lists.newArrayList(); + final List<NvFaceInfo> backwardFaces = Lists.newArrayList(); + forwardFaces.add(_startInfo._startFace); + + markTriangle(_startInfo._startFace); + + final int v0 = _startInfo._toV1 ? _startInfo._startEdge._v0 : _startInfo._startEdge._v1; + final int v1 = _startInfo._toV1 ? _startInfo._startEdge._v1 : _startInfo._startEdge._v0; + + // easiest way to get v2 is to use this function which requires the + // other indices to already be in the list. + scratchIndices.add(v0); + scratchIndices.add(v1); + final int v2 = NvStripifier.getNextIndex(scratchIndices, _startInfo._startFace); + scratchIndices.add(v2); + + // + // build the forward list + // + int nv0 = v1; + int nv1 = v2; + + NvFaceInfo nextFace = NvStripifier.findOtherFace(edgeInfos, nv0, nv1, _startInfo._startFace); + while (nextFace != null && !isMarked(nextFace)) { + // check to see if this next face is going to cause us to die soon + int testnv0 = nv1; + final int testnv1 = NvStripifier.getNextIndex(scratchIndices, nextFace); + + final NvFaceInfo nextNextFace = NvStripifier.findOtherFace(edgeInfos, testnv0, testnv1, nextFace); + + if (nextNextFace == null || isMarked(nextNextFace)) { + // uh, oh, we're following a dead end, try swapping + final NvFaceInfo testNextFace = NvStripifier.findOtherFace(edgeInfos, nv0, testnv1, nextFace); + + if (testNextFace != null && !isMarked(testNextFace)) { + // we only swap if it buys us something + + // add a "fake" degenerate face + final NvFaceInfo tempFace = new NvFaceInfo(nv0, nv1, nv0, true); + + forwardFaces.add(tempFace); + markTriangle(tempFace); + + scratchIndices.add(nv0); + testnv0 = nv0; + + ++_numDegenerates; + } + + } + + // add this to the strip + forwardFaces.add(nextFace); + + markTriangle(nextFace); + + // add the index + // nv0 = nv1; + // nv1 = NvStripifier.GetNextIndex(scratchIndices, nextFace); + scratchIndices.add(testnv1); + + // and get the next face + nv0 = testnv0; + nv1 = testnv1; + + nextFace = NvStripifier.findOtherFace(edgeInfos, nv0, nv1, nextFace); + + } + + // tempAllFaces is going to be forwardFaces + backwardFaces + // it's used for Unique() + final List<NvFaceInfo> tempAllFaces = Lists.newArrayList(); + for (int i = 0; i < forwardFaces.size(); i++) { + tempAllFaces.add(forwardFaces.get(i)); + } + + // + // reset the indices for building the strip backwards and do so + // + scratchIndices.clear(); + scratchIndices.add(v2); + scratchIndices.add(v1); + scratchIndices.add(v0); + nv0 = v1; + nv1 = v0; + nextFace = NvStripifier.findOtherFace(edgeInfos, nv0, nv1, _startInfo._startFace); + while (nextFace != null && !isMarked(nextFace)) { + // this tests to see if a face is "unique", meaning that its vertices aren't already in the list + // so, strips which "wrap-around" are not allowed + if (!unique(tempAllFaces, nextFace)) { + break; + } + + // check to see if this next face is going to cause us to die soon + int testnv0 = nv1; + final int testnv1 = NvStripifier.getNextIndex(scratchIndices, nextFace); + + final NvFaceInfo nextNextFace = NvStripifier.findOtherFace(edgeInfos, testnv0, testnv1, nextFace); + + if (nextNextFace == null || isMarked(nextNextFace)) { + // uh, oh, we're following a dead end, try swapping + final NvFaceInfo testNextFace = NvStripifier.findOtherFace(edgeInfos, nv0, testnv1, nextFace); + if (testNextFace != null && !isMarked(testNextFace)) { + // we only swap if it buys us something + + // add a "fake" degenerate face + final NvFaceInfo tempFace = new NvFaceInfo(nv0, nv1, nv0, true); + + backwardFaces.add(tempFace); + markTriangle(tempFace); + scratchIndices.add(nv0); + testnv0 = nv0; + + ++_numDegenerates; + } + + } + + // add this to the strip + backwardFaces.add(nextFace); + + // this is just so Unique() will work + tempAllFaces.add(nextFace); + + markTriangle(nextFace); + + // add the index + // nv0 = nv1; + // nv1 = NvStripifier.GetNextIndex(scratchIndices, nextFace); + scratchIndices.add(testnv1); + + // and get the next face + nv0 = testnv0; + nv1 = testnv1; + nextFace = NvStripifier.findOtherFace(edgeInfos, nv0, nv1, nextFace); + } + + // Combine the forward and backwards stripification lists and put into our own face vector + combine(forwardFaces, backwardFaces); + } +} diff --git a/ardor3d-extras/src/main/java/com/ardor3d/extension/model/util/nvtristrip/NvStripStartInfo.java b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/util/nvtristrip/NvStripStartInfo.java index 4878058..cecb0d3 100644 --- a/ardor3d-extras/src/main/java/com/ardor3d/extension/model/util/nvtristrip/NvStripStartInfo.java +++ b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/util/nvtristrip/NvStripStartInfo.java @@ -1,26 +1,26 @@ -/**
- * Copyright (c) 2008-2012 Ardor Labs, Inc.
- *
- * This file is part of Ardor3D.
- *
- * Ardor3D is free software: you can redistribute it and/or modify it
- * under the terms of its license which may be found in the accompanying
- * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
- */
-
-package com.ardor3d.extension.model.util.nvtristrip;
-
-/**
- * Ported from <a href="http://developer.nvidia.com/object/nvtristrip_library.html">NVIDIA's NvTriStrip Library</a>
- */
-final class NvStripStartInfo {
- NvFaceInfo _startFace;
- NvEdgeInfo _startEdge;
- boolean _toV1;
-
- NvStripStartInfo(final NvFaceInfo startFace, final NvEdgeInfo startEdge, final boolean toV1) {
- _startFace = startFace;
- _startEdge = startEdge;
- _toV1 = toV1;
- }
-}
+/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.model.util.nvtristrip; + +/** + * Ported from <a href="http://developer.nvidia.com/object/nvtristrip_library.html">NVIDIA's NvTriStrip Library</a> + */ +final class NvStripStartInfo { + NvFaceInfo _startFace; + NvEdgeInfo _startEdge; + boolean _toV1; + + NvStripStartInfo(final NvFaceInfo startFace, final NvEdgeInfo startEdge, final boolean toV1) { + _startFace = startFace; + _startEdge = startEdge; + _toV1 = toV1; + } +} diff --git a/ardor3d-extras/src/main/java/com/ardor3d/extension/model/util/nvtristrip/NvStripifier.java b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/util/nvtristrip/NvStripifier.java index 0bcca22..8779e5e 100644 --- a/ardor3d-extras/src/main/java/com/ardor3d/extension/model/util/nvtristrip/NvStripifier.java +++ b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/util/nvtristrip/NvStripifier.java @@ -1,1297 +1,1297 @@ -/**
- * Copyright (c) 2008-2012 Ardor Labs, Inc.
- *
- * This file is part of Ardor3D.
- *
- * Ardor3D is free software: you can redistribute it and/or modify it
- * under the terms of its license which may be found in the accompanying
- * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
- */
-
-package com.ardor3d.extension.model.util.nvtristrip;
-
-import java.util.List;
-import java.util.Set;
-import java.util.logging.Logger;
-
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-
-/**
- * Ported from <a href="http://developer.nvidia.com/object/nvtristrip_library.html">NVIDIA's NvTriStrip Library</a>
- */
-final class NvStripifier {
- private static final Logger logger = Logger.getLogger(NvStripifier.class.getName());
-
- public static int CACHE_INEFFICIENCY = 6;
-
- protected List<Integer> _indices = Lists.newArrayList();
- protected int _cacheSize;
- protected int _minStripLength;
- protected float _meshJump;
- protected boolean _firstTimeResetPoint;
-
- /**
- *
- * @param in_indices
- * the input indices of the mesh to stripify
- * @param in_cacheSize
- * the target cache size
- * @param in_minStripLength
- * @param maxIndex
- * @param outStrips
- * @param outFaceList
- */
- void stripify(final List<Integer> in_indices, final int in_cacheSize, final int in_minStripLength,
- final int maxIndex, final List<NvStripInfo> outStrips, final List<NvFaceInfo> outFaceList) {
- _meshJump = 0.0f;
- _firstTimeResetPoint = true; // used in FindGoodResetPoint()
-
- // the number of times to run the experiments
- final int numSamples = 10;
-
- // the cache size, clamped to one
- _cacheSize = Math.max(1, in_cacheSize - NvStripifier.CACHE_INEFFICIENCY);
-
- // this is the strip size threshold below which we dump the strip into a list
- _minStripLength = in_minStripLength;
-
- _indices = in_indices;
-
- // build the stripification info
- final List<NvFaceInfo> allFaceInfos = Lists.newArrayList();
- final List<NvEdgeInfo> allEdgeInfos = Lists.newArrayList();
-
- buildStripifyInfo(allFaceInfos, allEdgeInfos, maxIndex);
-
- final List<NvStripInfo> allStrips = Lists.newArrayList();
-
- // stripify
- findAllStrips(allStrips, allFaceInfos, allEdgeInfos, numSamples);
-
- // split up the strips into cache friendly pieces, optimize them, then dump these into outStrips
- splitUpStripsAndOptimize(allStrips, outStrips, allEdgeInfos, outFaceList);
- }
-
- /**
- * Generates actual strips from the list-in-strip-order.
- *
- * @param allStrips
- * @param stripIndices
- * @param bStitchStrips
- * @param numSeparateStrips
- * @param bRestart
- * @param restartVal
- */
- int createStrips(final List<NvStripInfo> allStrips, final List<Integer> stripIndices, final boolean bStitchStrips,
- final boolean bRestart, final int restartVal) {
- int numSeparateStrips = 0;
-
- NvFaceInfo tLastFace = new NvFaceInfo(0, 0, 0);
- final int nStripCount = allStrips.size();
- assert nStripCount > 0;
-
- // we infer the cw/ccw ordering depending on the number of indices
- // this is screwed up by the fact that we insert -1s to denote changing strips
- // this is to account for that
- int accountForNegatives = 0;
-
- for (int i = 0; i < nStripCount; i++) {
- final NvStripInfo strip = allStrips.get(i);
- final int nStripFaceCount = strip._faces.size();
- assert nStripFaceCount > 0;
-
- // Handle the first face in the strip
- {
- final NvFaceInfo tFirstFace = new NvFaceInfo(strip._faces.get(0)._v0, strip._faces.get(0)._v1,
- strip._faces.get(0)._v2);
-
- // If there is a second face, reorder vertices such that the
- // unique vertex is first
- if (nStripFaceCount > 1) {
- final int nUnique = NvStripifier.getUniqueVertexInB(strip._faces.get(1), tFirstFace);
- if (nUnique == tFirstFace._v1) {
- final int store = tFirstFace._v1;
- tFirstFace._v1 = tFirstFace._v0;
- tFirstFace._v0 = store;
- } else if (nUnique == tFirstFace._v2) {
- final int store = tFirstFace._v2;
- tFirstFace._v2 = tFirstFace._v0;
- tFirstFace._v0 = store;
- }
-
- // If there is a third face, reorder vertices such that the
- // shared vertex is last
- if (nStripFaceCount > 2) {
- if (NvStripifier.isDegenerate(strip._faces.get(1))) {
- final int pivot = strip._faces.get(1)._v1;
- if (tFirstFace._v1 == pivot) {
- final int store = tFirstFace._v2;
- tFirstFace._v2 = tFirstFace._v1;
- tFirstFace._v1 = store;
- }
- } else {
- final int[] nShared = NvStripifier.getSharedVertices(strip._faces.get(2), tFirstFace);
- if (nShared[0] == tFirstFace._v1 && nShared[1] == -1) {
- final int store = tFirstFace._v2;
- tFirstFace._v2 = tFirstFace._v1;
- tFirstFace._v1 = store;
- }
- }
- }
- }
-
- if (i == 0 || !bStitchStrips || bRestart) {
- if (!NvStripifier.isCW(strip._faces.get(0), tFirstFace._v0, tFirstFace._v1)) {
- stripIndices.add(tFirstFace._v0);
- }
- } else {
- // Double tap the first in the new strip
- stripIndices.add(tFirstFace._v0);
-
- // Check CW/CCW ordering
- if (NvStripifier.nextIsCW(stripIndices.size() - accountForNegatives) != NvStripifier.isCW(
- strip._faces.get(0), tFirstFace._v0, tFirstFace._v1)) {
- stripIndices.add(tFirstFace._v0);
- }
- }
-
- stripIndices.add(tFirstFace._v0);
- stripIndices.add(tFirstFace._v1);
- stripIndices.add(tFirstFace._v2);
-
- // Update last face info
- tLastFace = tFirstFace;
- }
-
- for (int j = 1; j < nStripFaceCount; j++) {
- final int nUnique = NvStripifier.getUniqueVertexInB(tLastFace, strip._faces.get(j));
- if (nUnique != -1) {
- stripIndices.add(nUnique);
-
- // Update last face info
- tLastFace._v0 = tLastFace._v1;
- tLastFace._v1 = tLastFace._v2;
- tLastFace._v2 = nUnique;
- } else {
- // we've hit a degenerate
- stripIndices.add(strip._faces.get(j)._v2);
- tLastFace._v0 = strip._faces.get(j)._v0;// tLastFace.m_v1;
- tLastFace._v1 = strip._faces.get(j)._v1;// tLastFace.m_v2;
- tLastFace._v2 = strip._faces.get(j)._v2;// tLastFace.m_v1;
-
- }
- }
-
- // Double tap between strips.
- if (bStitchStrips && !bRestart) {
- if (i != nStripCount - 1) {
- stripIndices.add(tLastFace._v2);
- }
- } else if (bRestart) {
- stripIndices.add(restartVal);
- } else {
- // -1 index indicates next strip
- stripIndices.add(-1);
- accountForNegatives++;
- numSeparateStrips++;
- }
-
- // Update last face info
- tLastFace._v0 = tLastFace._v1;
- tLastFace._v1 = tLastFace._v2;
- // tLastFace._v2 = tLastFace._v2; // for info purposes.
- }
-
- if (bStitchStrips || bRestart) {
- numSeparateStrips = 1;
- }
-
- return numSeparateStrips;
- }
-
- /**
- * @param faceA
- * @param faceB
- * @return the first vertex unique to faceB
- */
- static int getUniqueVertexInB(final NvFaceInfo faceA, final NvFaceInfo faceB) {
- final int facev0 = faceB._v0;
- if (facev0 != faceA._v0 && facev0 != faceA._v1 && facev0 != faceA._v2) {
- return facev0;
- }
-
- final int facev1 = faceB._v1;
- if (facev1 != faceA._v0 && facev1 != faceA._v1 && facev1 != faceA._v2) {
- return facev1;
- }
-
- final int facev2 = faceB._v2;
- if (facev2 != faceA._v0 && facev2 != faceA._v1 && facev2 != faceA._v2) {
- return facev2;
- }
-
- // nothing is different
- return -1;
- }
-
- /**
- * @param faceA
- * @param faceB
- * @return the (at most) two vertices shared between the two faces
- */
- static int[] getSharedVertices(final NvFaceInfo faceA, final NvFaceInfo faceB) {
- final int[] vertexStore = new int[2];
- vertexStore[0] = vertexStore[1] = -1;
-
- final int facev0 = faceB._v0;
- if (facev0 == faceA._v0 || facev0 == faceA._v1 || facev0 == faceA._v2) {
- if (vertexStore[0] == -1) {
- vertexStore[0] = facev0;
- } else {
- vertexStore[1] = facev0;
- return vertexStore;
- }
- }
-
- final int facev1 = faceB._v1;
- if (facev1 == faceA._v0 || facev1 == faceA._v1 || facev1 == faceA._v2) {
- if (vertexStore[0] == -1) {
- vertexStore[0] = facev1;
- } else {
- vertexStore[1] = facev1;
- return vertexStore;
- }
- }
-
- final int facev2 = faceB._v2;
- if (facev2 == faceA._v0 || facev2 == faceA._v1 || facev2 == faceA._v2) {
- if (vertexStore[0] == -1) {
- vertexStore[0] = facev2;
- } else {
- vertexStore[1] = facev2;
- return vertexStore;
- }
- }
-
- return vertexStore;
- }
-
- static boolean isDegenerate(final NvFaceInfo face) {
- if (face._v0 == face._v1) {
- return true;
- } else if (face._v0 == face._v2) {
- return true;
- } else if (face._v1 == face._v2) {
- return true;
- } else {
- return false;
- }
- }
-
- static boolean isDegenerate(final int v0, final int v1, final int v2) {
- if (v0 == v1) {
- return true;
- } else if (v0 == v2) {
- return true;
- } else if (v1 == v2) {
- return true;
- } else {
- return false;
- }
- }
-
- // ///////////////////////////////////////////////////////////////////////////////
- //
- // Big mess of functions called during stripification
- // Note: I removed some that were orphans - JES
- //
- // ///////////////////////////////////////////////////////////////////////////////
-
- /**
- * @param faceInfo
- * @param v0
- * @param v1
- * @return true if the face is ordered in CW fashion
- */
- static boolean isCW(final NvFaceInfo faceInfo, final int v0, final int v1) {
- if (faceInfo._v0 == v0) {
- return faceInfo._v1 == v1;
- } else if (faceInfo._v1 == v0) {
- return faceInfo._v2 == v1;
- } else {
- return faceInfo._v0 == v1;
- }
- }
-
- /**
- *
- * @param numIndices
- * @return true if the next face should be ordered in CW fashion
- */
- static boolean nextIsCW(final int numIndices) {
- return numIndices % 2 == 0;
- }
-
- /**
- * @param indices
- * @param face
- * @return vertex of the input face which is "next" in the input index list
- */
- static int getNextIndex(final List<Integer> indices, final NvFaceInfo face) {
- final int numIndices = indices.size();
- assert numIndices >= 2;
-
- final int v0 = indices.get(numIndices - 2);
- final int v1 = indices.get(numIndices - 1);
-
- final int fv0 = face._v0;
- final int fv1 = face._v1;
- final int fv2 = face._v2;
-
- if (fv0 != v0 && fv0 != v1) {
- if (fv1 != v0 && fv1 != v1 || fv2 != v0 && fv2 != v1) {
- NvStripifier.logger.warning("getNextIndex: Triangle doesn't have all of its vertices\n");
- NvStripifier.logger.warning("getNextIndex: Duplicate triangle probably got us derailed\n");
- }
- return fv0;
- }
- if (fv1 != v0 && fv1 != v1) {
- if (fv0 != v0 && fv0 != v1 || fv2 != v0 && fv2 != v1) {
- NvStripifier.logger.warning("getNextIndex: Triangle doesn't have all of its vertices\n");
- NvStripifier.logger.warning("getNextIndex: Duplicate triangle probably got us derailed\n");
- }
- return fv1;
- }
- if (fv2 != v0 && fv2 != v1) {
- if (fv0 != v0 && fv0 != v1 || fv1 != v0 && fv1 != v1) {
- NvStripifier.logger.warning("getNextIndex: Triangle doesn't have all of its vertices\n");
- NvStripifier.logger.warning("getNextIndex: Duplicate triangle probably got us derailed\n");
- }
- return fv2;
- }
-
- // shouldn't get here, but let's try and fail gracefully
- if (fv0 == fv1 || fv0 == fv2) {
- return fv0;
- } else if (fv1 == fv0 || fv1 == fv2) {
- return fv1;
- } else if (fv2 == fv0 || fv2 == fv1) {
- return fv2;
- } else {
- return -1;
- }
- }
-
- /**
- * find the edge info for these two indices
- *
- * @param edgeInfos
- * @param v0
- * @param v1
- * @return
- */
- static NvEdgeInfo findEdgeInfo(final List<NvEdgeInfo> edgeInfos, final int v0, final int v1) {
- // we can get to it through either array because the edge infos have a v0 and v1 and there is no order except
- // how it was first created.
- NvEdgeInfo infoIter = edgeInfos.get(v0);
- while (infoIter != null) {
- if (infoIter._v0 == v0) {
- if (infoIter._v1 == v1) {
- return infoIter;
- } else {
- infoIter = infoIter._nextV0;
- }
- } else {
- assert infoIter._v1 == v0;
- if (infoIter._v0 == v1) {
- return infoIter;
- } else {
- infoIter = infoIter._nextV1;
- }
- }
- }
- return null;
- }
-
- /**
- * find the other face sharing these vertices
- *
- * @param edgeInfos
- * @param v0
- * @param v1
- * @param faceInfo
- * @return
- */
- static NvFaceInfo findOtherFace(final List<NvEdgeInfo> edgeInfos, final int v0, final int v1,
- final NvFaceInfo faceInfo) {
- final NvEdgeInfo edgeInfo = NvStripifier.findEdgeInfo(edgeInfos, v0, v1);
-
- if (edgeInfo == null && v0 == v1) {
- // we've hit a degenerate
- return null;
- }
-
- assert edgeInfo != null;
- return edgeInfo._face0 == faceInfo ? edgeInfo._face1 : edgeInfo._face0;
- }
-
- /**
- * A good reset point is one near other committed areas so that we know that when we've made the longest strips its
- * because we're stripifying in the same general orientation.
- *
- * @param faceInfos
- * @param edgeInfos
- * @return
- */
- NvFaceInfo findGoodResetPoint(final List<NvFaceInfo> faceInfos, final List<NvEdgeInfo> edgeInfos) {
- // we hop into different areas of the mesh to try to get other large open spans done. Areas of small strips can
- // just be left to triangle lists added at the end.
- NvFaceInfo result = null;
- final int numFaces = faceInfos.size();
- int startPoint;
- if (_firstTimeResetPoint) {
- // first time, find a face with few neighbors (look for an edge of the mesh)
- startPoint = findStartPoint(faceInfos, edgeInfos);
- _firstTimeResetPoint = false;
- } else {
- startPoint = (int) (((float) numFaces - 1) * _meshJump);
- }
-
- if (startPoint == -1) {
- startPoint = (int) (((float) numFaces - 1) * _meshJump);
-
- // meshJump += 0.1f;
- // if (meshJump > 1.0f)
- // meshJump = .05f;
- }
-
- int i = startPoint;
- do {
-
- // if this guy isn't visited, try him
- if (faceInfos.get(i)._stripId < 0) {
- result = faceInfos.get(i);
- break;
- }
-
- // update the index and clamp to 0-(numFaces-1)
- if (++i >= numFaces) {
- i = 0;
- }
-
- } while (i != startPoint);
-
- // update the meshJump
- _meshJump += 0.1f;
- if (_meshJump > 1.0f) {
- _meshJump = .05f;
- }
-
- // return the best face we found
- return result;
- }
-
- /**
- * Does the stripification, puts output strips into vector allStrips
- *
- * Works by setting running a number of experiments in different areas of the mesh, and accepting the one which
- * results in the longest strips. It then accepts this, and moves on to a different area of the mesh. We try to jump
- * around the mesh some, to ensure that large open spans of strips get generated.
- *
- * @param allStrips
- * @param allFaceInfos
- * @param allEdgeInfos
- * @param numSamples
- */
- @SuppressWarnings("unchecked")
- void findAllStrips(final List<NvStripInfo> allStrips, final List<NvFaceInfo> allFaceInfos,
- final List<NvEdgeInfo> allEdgeInfos, final int numSamples) {
- // the experiments
- int experimentId = 0;
- int stripId = 0;
- boolean done = false;
-
- while (!done) {
-
- //
- // PHASE 1: Set up numSamples * numEdges experiments
- //
- final List<NvStripInfo>[] experiments = new List[numSamples * 6];
- for (int i = 0; i < experiments.length; i++) {
- experiments[i] = Lists.newArrayList();
- }
-
- int experimentIndex = 0;
- final Set<NvFaceInfo> resetPoints = Sets.newHashSet();
- for (int i = 0; i < numSamples; i++) {
-
- // Try to find another good reset point.
- // If there are none to be found, we are done
- final NvFaceInfo nextFace = findGoodResetPoint(allFaceInfos, allEdgeInfos);
- if (nextFace == null) {
- done = true;
- break;
- }
- // If we have already evaluated starting at this face in this slew
- // of experiments, then skip going any further
- else if (resetPoints.contains(nextFace)) {
- continue;
- }
-
- // trying it now...
- resetPoints.add(nextFace);
-
- // otherwise, we shall now try experiments for starting on the 01,12, and 20 edges
- assert nextFace._stripId < 0;
-
- // build the strip off of this face's 0-1 edge
- final NvEdgeInfo edge01 = NvStripifier.findEdgeInfo(allEdgeInfos, nextFace._v0, nextFace._v1);
- final NvStripInfo strip01 = new NvStripInfo(new NvStripStartInfo(nextFace, edge01, true), stripId++,
- experimentId++);
- experiments[experimentIndex++].add(strip01);
-
- // build the strip off of this face's 1-0 edge
- final NvEdgeInfo edge10 = NvStripifier.findEdgeInfo(allEdgeInfos, nextFace._v0, nextFace._v1);
- final NvStripInfo strip10 = new NvStripInfo(new NvStripStartInfo(nextFace, edge10, false), stripId++,
- experimentId++);
- experiments[experimentIndex++].add(strip10);
-
- // build the strip off of this face's 1-2 edge
- final NvEdgeInfo edge12 = NvStripifier.findEdgeInfo(allEdgeInfos, nextFace._v1, nextFace._v2);
- final NvStripInfo strip12 = new NvStripInfo(new NvStripStartInfo(nextFace, edge12, true), stripId++,
- experimentId++);
- experiments[experimentIndex++].add(strip12);
-
- // build the strip off of this face's 2-1 edge
- final NvEdgeInfo edge21 = NvStripifier.findEdgeInfo(allEdgeInfos, nextFace._v1, nextFace._v2);
- final NvStripInfo strip21 = new NvStripInfo(new NvStripStartInfo(nextFace, edge21, false), stripId++,
- experimentId++);
- experiments[experimentIndex++].add(strip21);
-
- // build the strip off of this face's 2-0 edge
- final NvEdgeInfo edge20 = NvStripifier.findEdgeInfo(allEdgeInfos, nextFace._v2, nextFace._v0);
- final NvStripInfo strip20 = new NvStripInfo(new NvStripStartInfo(nextFace, edge20, true), stripId++,
- experimentId++);
- experiments[experimentIndex++].add(strip20);
-
- // build the strip off of this face's 0-2 edge
- final NvEdgeInfo edge02 = NvStripifier.findEdgeInfo(allEdgeInfos, nextFace._v2, nextFace._v0);
- final NvStripInfo strip02 = new NvStripInfo(new NvStripStartInfo(nextFace, edge02, false), stripId++,
- experimentId++);
- experiments[experimentIndex++].add(strip02);
- }
-
- //
- // PHASE 2: Iterate through that we setup in the last phase
- // and really build each of the strips and strips that follow to see how
- // far we get
- //
- final int numExperiments = experimentIndex;
- for (int i = 0; i < numExperiments; i++) {
- // get the strip set
- NvStripInfo stripIter = experiments[i].get(0);
-
- // build the first strip of the list
- stripIter.build(allEdgeInfos, allFaceInfos);
- final int currExperimentId = stripIter._experimentId;
-
- final NvStripStartInfo startInfo = new NvStripStartInfo(null, null, false);
- while (findTraversal(allFaceInfos, allEdgeInfos, stripIter, startInfo)) {
-
- // create the new strip info
- stripIter = new NvStripInfo(startInfo, stripId++, currExperimentId);
-
- // build the next strip
- stripIter.build(allEdgeInfos, allFaceInfos);
-
- // add it to the list
- experiments[i].add(stripIter);
- }
- }
-
- //
- // Phase 3: Find the experiment that has the most promise
- //
- int bestIndex = 0;
- double bestValue = 0;
- for (int i = 0; i < numExperiments; i++) {
- final float avgStripSizeWeight = 1.0f;
- // final float numTrisWeight = 0.0f; // unused
- final float numStripsWeight = 0.0f;
- final float avgStripSize = avgStripSize(experiments[i]);
- final float numStrips = experiments[i].size();
- final float value = avgStripSize * avgStripSizeWeight + numStrips * numStripsWeight;
- // float value = 1.f / numStrips;
- // float value = numStrips * avgStripSize;
-
- if (value > bestValue) {
- bestValue = value;
- bestIndex = i;
- }
- }
-
- //
- // Phase 4: commit the best experiment of the bunch
- //
- commitStrips(allStrips, experiments[bestIndex]);
- }
- }
-
- /**
- * Splits the input vector of strips (allBigStrips) into smaller, cache friendly pieces, then reorders these pieces
- * to maximize cache hits. The final strips are stored in outStrips
- *
- * @param allStrips
- * @param outStrips
- * @param edgeInfos
- * @param outFaceList
- */
- void splitUpStripsAndOptimize(final List<NvStripInfo> allStrips, final List<NvStripInfo> outStrips,
- final List<NvEdgeInfo> edgeInfos, final List<NvFaceInfo> outFaceList) {
- final int threshold = _cacheSize;
- final List<NvStripInfo> tempStrips = Lists.newArrayList();
-
- // split up strips into threshold-sized pieces
- for (int i = 0; i < allStrips.size(); i++) {
- final NvStripInfo allStripI = allStrips.get(i);
- NvStripInfo currentStrip;
- final NvStripStartInfo startInfo = new NvStripStartInfo(null, null, false);
-
- int actualStripSize = 0;
- for (final NvFaceInfo face : allStripI._faces) {
- if (!NvStripifier.isDegenerate(face)) {
- actualStripSize++;
- }
- }
-
- if (actualStripSize > threshold) {
-
- final int numTimes = actualStripSize / threshold;
- int numLeftover = actualStripSize % threshold;
-
- int degenerateCount = 0, j = 0;
- for (; j < numTimes; j++) {
- currentStrip = new NvStripInfo(startInfo, 0, -1);
-
- int faceCtr = j * threshold + degenerateCount;
- boolean bFirstTime = true;
- while (faceCtr < threshold + j * threshold + degenerateCount) {
- if (NvStripifier.isDegenerate(allStripI._faces.get(faceCtr))) {
- degenerateCount++;
-
- // last time or first time through, no need for a degenerate
- if ((faceCtr + 1 != threshold + j * threshold + degenerateCount || j == numTimes - 1
- && numLeftover < 4 && numLeftover > 0)
- && !bFirstTime) {
- currentStrip._faces.add(allStripI._faces.get(faceCtr++));
- } else {
- ++faceCtr;
- }
- } else {
- currentStrip._faces.add(allStripI._faces.get(faceCtr++));
- bFirstTime = false;
- }
- }
- if (j == numTimes - 1) // last time through
- {
- if (numLeftover < 4 && numLeftover > 0) // way too small
- {
- // just add to last strip
- int ctr = 0;
- while (ctr < numLeftover) {
- if (NvStripifier.isDegenerate(allStripI._faces.get(faceCtr))) {
- ++degenerateCount;
- } else {
- ++ctr;
- }
- currentStrip._faces.add(allStripI._faces.get(faceCtr++));
- }
- numLeftover = 0;
- }
- }
- tempStrips.add(currentStrip);
- }
- int leftOff = j * threshold + degenerateCount;
-
- if (numLeftover != 0) {
- currentStrip = new NvStripInfo(startInfo, 0, -1);
-
- int ctr = 0;
- boolean bFirstTime = true;
- while (ctr < numLeftover) {
- if (!NvStripifier.isDegenerate(allStripI._faces.get(leftOff))) {
- ctr++;
- bFirstTime = false;
- currentStrip._faces.add(allStripI._faces.get(leftOff++));
- } else if (!bFirstTime) {
- currentStrip._faces.add(allStripI._faces.get(leftOff++));
- } else {
- leftOff++;
- }
- }
-
- tempStrips.add(currentStrip);
- }
- } else {
- // we're not just doing a tempStrips.add(allBigStrips.get(i)) because
- // this way we can delete allBigStrips later to free the memory
- currentStrip = new NvStripInfo(startInfo, 0, -1);
-
- for (int j = 0; j < allStripI._faces.size(); j++) {
- currentStrip._faces.add(allStripI._faces.get(j));
- }
-
- tempStrips.add(currentStrip);
- }
- }
-
- // add small strips to face list
- final List<NvStripInfo> tempStrips2 = Lists.newArrayList();
- removeSmallStrips(tempStrips, tempStrips2, outFaceList);
-
- outStrips.clear();
- // screw optimization for now
- // for(i = 0; i < tempStrips.size(); ++i)
- // outStrips.add(tempStrips.get(i));
-
- if (tempStrips2.size() != 0) {
- // Optimize for the vertex cache
- final VertexCache vcache = new VertexCache(_cacheSize);
-
- float bestNumHits = -1.0f;
- float numHits;
- int bestIndex = 0;
- int firstIndex = 0;
- float minCost = 10000.0f;
-
- for (int i = 0; i < tempStrips2.size(); i++) {
- final NvStripInfo tempStrips2I = tempStrips2.get(i);
- int numNeighbors = 0;
-
- // find strip with least number of neighbors per face
- for (int j = 0; j < tempStrips2I._faces.size(); j++) {
- numNeighbors += numNeighbors(tempStrips2I._faces.get(j), edgeInfos);
- }
-
- final float currCost = numNeighbors / (float) tempStrips2I._faces.size();
- if (currCost < minCost) {
- minCost = currCost;
- firstIndex = i;
- }
- }
-
- final NvStripInfo tempStrips2FirstIndex = tempStrips2.get(firstIndex);
- updateCacheStrip(vcache, tempStrips2FirstIndex);
- outStrips.add(tempStrips2FirstIndex);
-
- tempStrips2FirstIndex._visited = true;
-
- boolean bWantsCW = tempStrips2FirstIndex._faces.size() % 2 == 0;
-
- // XXX: this n^2 algo is what slows down stripification so much.... needs to be improved
- while (true) {
- bestNumHits = -1.0f;
-
- // find best strip to add next, given the current cache
- for (int i = 0; i < tempStrips2.size(); i++) {
- final NvStripInfo tempStrips2I = tempStrips2.get(i);
- if (tempStrips2I._visited) {
- continue;
- }
-
- numHits = calcNumHitsStrip(vcache, tempStrips2I);
- if (numHits > bestNumHits) {
- bestNumHits = numHits;
- bestIndex = i;
- } else if (numHits >= bestNumHits) {
- // check previous strip to see if this one requires it to switch polarity
- final NvStripInfo strip = tempStrips2I;
- final int nStripFaceCount = strip._faces.size();
-
- final NvFaceInfo tFirstFace = new NvFaceInfo(strip._faces.get(0));
-
- // If there is a second face, reorder vertices such that the
- // unique vertex is first
- if (nStripFaceCount > 1) {
- final int nUnique = NvStripifier.getUniqueVertexInB(strip._faces.get(1), tFirstFace);
- if (nUnique == tFirstFace._v1) {
- final int store = tFirstFace._v1;
- tFirstFace._v1 = tFirstFace._v0;
- tFirstFace._v0 = store;
- } else if (nUnique == tFirstFace._v2) {
- final int store = tFirstFace._v2;
- tFirstFace._v2 = tFirstFace._v0;
- tFirstFace._v0 = store;
- }
-
- // If there is a third face, reorder vertices such that the
- // shared vertex is last
- if (nStripFaceCount > 2) {
- final int[] nShared = NvStripifier.getSharedVertices(strip._faces.get(2), tFirstFace);
- if (nShared[0] == tFirstFace._v1 && nShared[1] == -1) {
- final int store = tFirstFace._v2;
- tFirstFace._v2 = tFirstFace._v1;
- tFirstFace._v1 = store;
- }
- }
- }
-
- // Check CW/CCW ordering
- if (bWantsCW == NvStripifier.isCW(strip._faces.get(0), tFirstFace._v0, tFirstFace._v1)) {
- // I like this one!
- bestIndex = i;
- }
- }
- }
-
- if (bestNumHits == -1.0f) {
- break;
- }
- tempStrips2.get(bestIndex)._visited = true;
- updateCacheStrip(vcache, tempStrips2.get(bestIndex));
- outStrips.add(tempStrips2.get(bestIndex));
- bWantsCW = tempStrips2.get(bestIndex)._faces.size() % 2 == 0 ? bWantsCW : !bWantsCW;
- }
- }
- }
-
- /**
- * @param allStrips
- * the whole strip vector...all small strips will be deleted from this list, to avoid leaking mem
- * @param allBigStrips
- * an out parameter which will contain all strips above minStripLength
- * @param faceList
- * an out parameter which will contain all faces which were removed from the striplist
- */
- void removeSmallStrips(final List<NvStripInfo> allStrips, final List<NvStripInfo> allBigStrips,
- final List<NvFaceInfo> faceList) {
- faceList.clear();
- allBigStrips.clear(); // make sure these are empty
- final List<NvFaceInfo> tempFaceList = Lists.newArrayList();
-
- for (int i = 0; i < allStrips.size(); i++) {
- final NvStripInfo allStripI = allStrips.get(i);
- if (allStripI._faces.size() < _minStripLength) {
- // strip is too small, add faces to faceList
- for (int j = 0; j < allStripI._faces.size(); j++) {
- tempFaceList.add(allStripI._faces.get(j));
- }
- } else {
- allBigStrips.add(allStripI);
- }
- }
-
- if (!tempFaceList.isEmpty()) {
- final boolean[] bVisitedList = new boolean[tempFaceList.size()];
- final VertexCache vcache = new VertexCache(_cacheSize);
-
- int bestNumHits = -1;
- int numHits = 0;
- int bestIndex = 0;
-
- while (true) {
- bestNumHits = -1;
-
- // find best face to add next, given the current cache
- for (int i = 0; i < tempFaceList.size(); i++) {
- if (bVisitedList[i]) {
- continue;
- }
-
- numHits = calcNumHitsFace(vcache, tempFaceList.get(i));
- if (numHits > bestNumHits) {
- bestNumHits = numHits;
- bestIndex = i;
- }
- }
-
- if (bestNumHits == -1.0f) {
- break;
- }
- bVisitedList[bestIndex] = true;
- updateCacheFace(vcache, tempFaceList.get(bestIndex));
- faceList.add(tempFaceList.get(bestIndex));
- }
- }
- }
-
- /**
- * Finds the next face to start the next strip on.
- *
- * @param faceInfos
- * @param edgeInfos
- * @param strip
- * @param startInfo
- * @return
- */
- boolean findTraversal(final List<NvFaceInfo> faceInfos, final List<NvEdgeInfo> edgeInfos, final NvStripInfo strip,
- final NvStripStartInfo startInfo) {
- // if the strip was v0.v1 on the edge, then v1 will be a vertex in the next edge.
- final int v = strip._startInfo._toV1 ? strip._startInfo._startEdge._v1 : strip._startInfo._startEdge._v0;
-
- NvFaceInfo untouchedFace = null;
- NvEdgeInfo edgeIter = edgeInfos.get(v);
- while (edgeIter != null) {
- final NvFaceInfo face0 = edgeIter._face0;
- final NvFaceInfo face1 = edgeIter._face1;
- if (face0 != null && !strip.IsInStrip(face0) && face1 != null && !strip.isMarked(face1)) {
- untouchedFace = face1;
- break;
- }
- if (face1 != null && !strip.IsInStrip(face1) && face0 != null && !strip.isMarked(face0)) {
- untouchedFace = face0;
- break;
- }
-
- // find the next edgeIter
- edgeIter = edgeIter._v0 == v ? edgeIter._nextV0 : edgeIter._nextV1;
- }
-
- startInfo._startFace = untouchedFace;
- startInfo._startEdge = edgeIter;
- if (edgeIter != null) {
- if (strip.sharesEdge(startInfo._startFace, edgeInfos)) {
- startInfo._toV1 = edgeIter._v0 == v; // note! used to be m_v1
- } else {
- startInfo._toV1 = edgeIter._v1 == v;
- }
- }
- return startInfo._startFace != null;
- }
-
- /**
- * "Commits" the input strips by setting their m_experimentId to -1 and adding to the allStrips vector
- *
- * @param allStrips
- * @param strips
- */
- void commitStrips(final List<NvStripInfo> allStrips, final List<NvStripInfo> strips) {
- // Iterate through strips
- for (final NvStripInfo strip : strips) {
- // Tell the strip that it is now real
- strip._experimentId = -1;
-
- // add to the list of real strips
- allStrips.add(strip);
-
- // Iterate through the faces of the strip
- // Tell the faces of the strip that they belong to a real strip now
- final List<NvFaceInfo> faces = strip._faces;
- for (final NvFaceInfo face : faces) {
- strip.markTriangle(face);
- }
- }
- }
-
- /**
- *
- * @param strips
- * @return the average strip size of the input vector of strips
- */
- float avgStripSize(final List<NvStripInfo> strips) {
- int sizeAccum = 0;
- for (final NvStripInfo strip : strips) {
- sizeAccum += strip._faces.size();
- sizeAccum -= strip._numDegenerates;
- }
- return (float) sizeAccum / (float) strips.size();
- }
-
- /**
- * Finds a good starting point, namely one which has only one neighbor
- *
- * @param faceInfos
- * @param edgeInfos
- * @return
- */
- int findStartPoint(final List<NvFaceInfo> faceInfos, final List<NvEdgeInfo> edgeInfos) {
- int bestCtr = -1;
- int bestIndex = -1;
-
- int i = 0;
- for (final NvFaceInfo faceInfo : faceInfos) {
- int ctr = 0;
-
- if (NvStripifier.findOtherFace(edgeInfos, faceInfo._v0, faceInfo._v1, faceInfo) == null) {
- ctr++;
- }
- if (NvStripifier.findOtherFace(edgeInfos, faceInfo._v1, faceInfo._v2, faceInfo) == null) {
- ctr++;
- }
- if (NvStripifier.findOtherFace(edgeInfos, faceInfo._v2, faceInfo._v0, faceInfo) == null) {
- ctr++;
- }
- if (ctr > bestCtr) {
- bestCtr = ctr;
- bestIndex = i;
- }
- i++;
- }
-
- if (bestCtr == 0) {
- return -1;
- } else {
- return bestIndex;
- }
- }
-
- /**
- * Updates the input vertex cache with this strip's vertices
- *
- * @param vcache
- * @param strip
- */
- void updateCacheStrip(final VertexCache vcache, final NvStripInfo strip) {
- for (final NvFaceInfo face : strip._faces) {
- updateCacheFace(vcache, face);
- }
- }
-
- /**
- * Updates the input vertex cache with this face's vertices
- *
- * @param vcache
- * @param face
- */
- void updateCacheFace(final VertexCache vcache, final NvFaceInfo face) {
- if (!vcache.inCache(face._v0)) {
- vcache.addEntry(face._v0);
- }
-
- if (!vcache.inCache(face._v1)) {
- vcache.addEntry(face._v1);
- }
-
- if (!vcache.inCache(face._v2)) {
- vcache.addEntry(face._v2);
- }
- }
-
- /**
- * @param vcache
- * @param strip
- * @return the number of cache hits per face in the strip
- */
- float calcNumHitsStrip(final VertexCache vcache, final NvStripInfo strip) {
- int numHits = 0;
- int numFaces = 0;
-
- for (final NvFaceInfo face : strip._faces) {
- if (vcache.inCache(face._v0)) {
- ++numHits;
- }
-
- if (vcache.inCache(face._v1)) {
- ++numHits;
- }
-
- if (vcache.inCache(face._v2)) {
- ++numHits;
- }
-
- numFaces++;
- }
-
- return (float) numHits / (float) numFaces;
- }
-
- /**
- * @param vcache
- * @param face
- * @return the number of cache hits in the face
- */
- int calcNumHitsFace(final VertexCache vcache, final NvFaceInfo face) {
- int numHits = 0;
-
- if (vcache.inCache(face._v0)) {
- numHits++;
- }
-
- if (vcache.inCache(face._v1)) {
- numHits++;
- }
-
- if (vcache.inCache(face._v2)) {
- numHits++;
- }
-
- return numHits;
- }
-
- /**
- *
- * @param face
- * @param edgeInfoVec
- * @return the number of neighbors that this face has
- */
- int numNeighbors(final NvFaceInfo face, final List<NvEdgeInfo> edgeInfoVec) {
- int numNeighbors = 0;
-
- if (NvStripifier.findOtherFace(edgeInfoVec, face._v0, face._v1, face) != null) {
- numNeighbors++;
- }
-
- if (NvStripifier.findOtherFace(edgeInfoVec, face._v1, face._v2, face) != null) {
- numNeighbors++;
- }
-
- if (NvStripifier.findOtherFace(edgeInfoVec, face._v2, face._v0, face) != null) {
- numNeighbors++;
- }
-
- return numNeighbors;
- }
-
- /**
- * Builds the list of all face and edge infos
- *
- * @param faceInfos
- * @param edgeInfos
- * @param maxIndex
- */
- void buildStripifyInfo(final List<NvFaceInfo> faceInfos, final List<NvEdgeInfo> edgeInfos, final int maxIndex) {
- // reserve space for the face infos, but do not resize them.
- final int numIndices = _indices.size();
-
- // we actually resize the edge infos, so we must initialize to null
- for (int i = 0; i <= maxIndex; i++) {
- edgeInfos.add(null);
- }
-
- // iterate through the triangles of the triangle list
- final int numTriangles = numIndices / 3;
- int index = 0;
- final boolean[] bFaceUpdated = new boolean[3];
-
- for (int i = 0; i < numTriangles; i++) {
- boolean bMightAlreadyExist = true;
- bFaceUpdated[0] = false;
- bFaceUpdated[1] = false;
- bFaceUpdated[2] = false;
-
- // grab the indices
- final int v0 = _indices.get(index++);
- final int v1 = _indices.get(index++);
- final int v2 = _indices.get(index++);
-
- // we disregard degenerates
- if (NvStripifier.isDegenerate(v0, v1, v2)) {
- continue;
- }
-
- // create the face info and add it to the list of faces, but only if this exact face doesn't already
- // exist in the list
- final NvFaceInfo faceInfo = new NvFaceInfo(v0, v1, v2);
-
- // grab the edge infos, creating them if they do not already exist
- NvEdgeInfo edgeInfo01 = NvStripifier.findEdgeInfo(edgeInfos, v0, v1);
- if (edgeInfo01 == null) {
- // since one of it's edges isn't in the edge data structure, it can't already exist in the face
- // structure
- bMightAlreadyExist = false;
-
- // create the info
- edgeInfo01 = new NvEdgeInfo(v0, v1);
-
- // update the linked list on both
- edgeInfo01._nextV0 = edgeInfos.get(v0);
- edgeInfo01._nextV1 = edgeInfos.get(v1);
- edgeInfos.set(v0, edgeInfo01);
- edgeInfos.set(v1, edgeInfo01);
-
- // set face 0
- edgeInfo01._face0 = faceInfo;
- } else {
- if (edgeInfo01._face1 != null) {
- NvStripifier.logger
- .warning("BuildStripifyInfo: > 2 triangles on an edge... uncertain consequences\n");
- } else {
- edgeInfo01._face1 = faceInfo;
- bFaceUpdated[0] = true;
- }
- }
-
- // grab the edge infos, creating them if they do not already exist
- NvEdgeInfo edgeInfo12 = NvStripifier.findEdgeInfo(edgeInfos, v1, v2);
- if (edgeInfo12 == null) {
- bMightAlreadyExist = false;
-
- // create the info
- edgeInfo12 = new NvEdgeInfo(v1, v2);
-
- // update the linked list on both
- edgeInfo12._nextV0 = edgeInfos.get(v1);
- edgeInfo12._nextV1 = edgeInfos.get(v2);
- edgeInfos.set(v1, edgeInfo12);
- edgeInfos.set(v2, edgeInfo12);
-
- // set face 0
- edgeInfo12._face0 = faceInfo;
- } else {
- if (edgeInfo12._face1 != null) {
- NvStripifier.logger
- .warning("BuildStripifyInfo: > 2 triangles on an edge... uncertain consequences\n");
- } else {
- edgeInfo12._face1 = faceInfo;
- bFaceUpdated[1] = true;
- }
- }
-
- // grab the edge infos, creating them if they do not already exist
- NvEdgeInfo edgeInfo20 = NvStripifier.findEdgeInfo(edgeInfos, v2, v0);
- if (edgeInfo20 == null) {
- bMightAlreadyExist = false;
-
- // create the info
- edgeInfo20 = new NvEdgeInfo(v2, v0);
-
- // update the linked list on both
- edgeInfo20._nextV0 = edgeInfos.get(v2);
- edgeInfo20._nextV1 = edgeInfos.get(v0);
- edgeInfos.set(v2, edgeInfo20);
- edgeInfos.set(v0, edgeInfo20);
-
- // set face 0
- edgeInfo20._face0 = faceInfo;
- } else {
- if (edgeInfo20._face1 != null) {
- NvStripifier.logger
- .warning("BuildStripifyInfo: > 2 triangles on an edge... uncertain consequences\n");
- } else {
- edgeInfo20._face1 = faceInfo;
- bFaceUpdated[2] = true;
- }
- }
-
- if (bMightAlreadyExist) {
- if (!alreadyExists(faceInfo, faceInfos)) {
- faceInfos.add(faceInfo);
- } else {
- // cleanup pointers that point to this deleted face
- if (bFaceUpdated[0]) {
- edgeInfo01._face1 = null;
- }
- if (bFaceUpdated[1]) {
- edgeInfo12._face1 = null;
- }
- if (bFaceUpdated[2]) {
- edgeInfo20._face1 = null;
- }
- }
- } else {
- faceInfos.add(faceInfo);
- }
- }
- }
-
- boolean alreadyExists(final NvFaceInfo toFind, final List<NvFaceInfo> faceInfos) {
- for (final NvFaceInfo face : faceInfos) {
- if (face._v0 == toFind._v0 && face._v1 == toFind._v1 && face._v2 == toFind._v2) {
- return true;
- }
- }
-
- return false;
- }
-}
+/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.model.util.nvtristrip; + +import java.util.List; +import java.util.Set; +import java.util.logging.Logger; + +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; + +/** + * Ported from <a href="http://developer.nvidia.com/object/nvtristrip_library.html">NVIDIA's NvTriStrip Library</a> + */ +final class NvStripifier { + private static final Logger logger = Logger.getLogger(NvStripifier.class.getName()); + + public static int CACHE_INEFFICIENCY = 6; + + protected List<Integer> _indices = Lists.newArrayList(); + protected int _cacheSize; + protected int _minStripLength; + protected float _meshJump; + protected boolean _firstTimeResetPoint; + + /** + * + * @param in_indices + * the input indices of the mesh to stripify + * @param in_cacheSize + * the target cache size + * @param in_minStripLength + * @param maxIndex + * @param outStrips + * @param outFaceList + */ + void stripify(final List<Integer> in_indices, final int in_cacheSize, final int in_minStripLength, + final int maxIndex, final List<NvStripInfo> outStrips, final List<NvFaceInfo> outFaceList) { + _meshJump = 0.0f; + _firstTimeResetPoint = true; // used in FindGoodResetPoint() + + // the number of times to run the experiments + final int numSamples = 10; + + // the cache size, clamped to one + _cacheSize = Math.max(1, in_cacheSize - NvStripifier.CACHE_INEFFICIENCY); + + // this is the strip size threshold below which we dump the strip into a list + _minStripLength = in_minStripLength; + + _indices = in_indices; + + // build the stripification info + final List<NvFaceInfo> allFaceInfos = Lists.newArrayList(); + final List<NvEdgeInfo> allEdgeInfos = Lists.newArrayList(); + + buildStripifyInfo(allFaceInfos, allEdgeInfos, maxIndex); + + final List<NvStripInfo> allStrips = Lists.newArrayList(); + + // stripify + findAllStrips(allStrips, allFaceInfos, allEdgeInfos, numSamples); + + // split up the strips into cache friendly pieces, optimize them, then dump these into outStrips + splitUpStripsAndOptimize(allStrips, outStrips, allEdgeInfos, outFaceList); + } + + /** + * Generates actual strips from the list-in-strip-order. + * + * @param allStrips + * @param stripIndices + * @param bStitchStrips + * @param numSeparateStrips + * @param bRestart + * @param restartVal + */ + int createStrips(final List<NvStripInfo> allStrips, final List<Integer> stripIndices, final boolean bStitchStrips, + final boolean bRestart, final int restartVal) { + int numSeparateStrips = 0; + + NvFaceInfo tLastFace = new NvFaceInfo(0, 0, 0); + final int nStripCount = allStrips.size(); + assert nStripCount > 0; + + // we infer the cw/ccw ordering depending on the number of indices + // this is screwed up by the fact that we insert -1s to denote changing strips + // this is to account for that + int accountForNegatives = 0; + + for (int i = 0; i < nStripCount; i++) { + final NvStripInfo strip = allStrips.get(i); + final int nStripFaceCount = strip._faces.size(); + assert nStripFaceCount > 0; + + // Handle the first face in the strip + { + final NvFaceInfo tFirstFace = new NvFaceInfo(strip._faces.get(0)._v0, strip._faces.get(0)._v1, + strip._faces.get(0)._v2); + + // If there is a second face, reorder vertices such that the + // unique vertex is first + if (nStripFaceCount > 1) { + final int nUnique = NvStripifier.getUniqueVertexInB(strip._faces.get(1), tFirstFace); + if (nUnique == tFirstFace._v1) { + final int store = tFirstFace._v1; + tFirstFace._v1 = tFirstFace._v0; + tFirstFace._v0 = store; + } else if (nUnique == tFirstFace._v2) { + final int store = tFirstFace._v2; + tFirstFace._v2 = tFirstFace._v0; + tFirstFace._v0 = store; + } + + // If there is a third face, reorder vertices such that the + // shared vertex is last + if (nStripFaceCount > 2) { + if (NvStripifier.isDegenerate(strip._faces.get(1))) { + final int pivot = strip._faces.get(1)._v1; + if (tFirstFace._v1 == pivot) { + final int store = tFirstFace._v2; + tFirstFace._v2 = tFirstFace._v1; + tFirstFace._v1 = store; + } + } else { + final int[] nShared = NvStripifier.getSharedVertices(strip._faces.get(2), tFirstFace); + if (nShared[0] == tFirstFace._v1 && nShared[1] == -1) { + final int store = tFirstFace._v2; + tFirstFace._v2 = tFirstFace._v1; + tFirstFace._v1 = store; + } + } + } + } + + if (i == 0 || !bStitchStrips || bRestart) { + if (!NvStripifier.isCW(strip._faces.get(0), tFirstFace._v0, tFirstFace._v1)) { + stripIndices.add(tFirstFace._v0); + } + } else { + // Double tap the first in the new strip + stripIndices.add(tFirstFace._v0); + + // Check CW/CCW ordering + if (NvStripifier.nextIsCW(stripIndices.size() - accountForNegatives) != NvStripifier.isCW( + strip._faces.get(0), tFirstFace._v0, tFirstFace._v1)) { + stripIndices.add(tFirstFace._v0); + } + } + + stripIndices.add(tFirstFace._v0); + stripIndices.add(tFirstFace._v1); + stripIndices.add(tFirstFace._v2); + + // Update last face info + tLastFace = tFirstFace; + } + + for (int j = 1; j < nStripFaceCount; j++) { + final int nUnique = NvStripifier.getUniqueVertexInB(tLastFace, strip._faces.get(j)); + if (nUnique != -1) { + stripIndices.add(nUnique); + + // Update last face info + tLastFace._v0 = tLastFace._v1; + tLastFace._v1 = tLastFace._v2; + tLastFace._v2 = nUnique; + } else { + // we've hit a degenerate + stripIndices.add(strip._faces.get(j)._v2); + tLastFace._v0 = strip._faces.get(j)._v0;// tLastFace.m_v1; + tLastFace._v1 = strip._faces.get(j)._v1;// tLastFace.m_v2; + tLastFace._v2 = strip._faces.get(j)._v2;// tLastFace.m_v1; + + } + } + + // Double tap between strips. + if (bStitchStrips && !bRestart) { + if (i != nStripCount - 1) { + stripIndices.add(tLastFace._v2); + } + } else if (bRestart) { + stripIndices.add(restartVal); + } else { + // -1 index indicates next strip + stripIndices.add(-1); + accountForNegatives++; + numSeparateStrips++; + } + + // Update last face info + tLastFace._v0 = tLastFace._v1; + tLastFace._v1 = tLastFace._v2; + // tLastFace._v2 = tLastFace._v2; // for info purposes. + } + + if (bStitchStrips || bRestart) { + numSeparateStrips = 1; + } + + return numSeparateStrips; + } + + /** + * @param faceA + * @param faceB + * @return the first vertex unique to faceB + */ + static int getUniqueVertexInB(final NvFaceInfo faceA, final NvFaceInfo faceB) { + final int facev0 = faceB._v0; + if (facev0 != faceA._v0 && facev0 != faceA._v1 && facev0 != faceA._v2) { + return facev0; + } + + final int facev1 = faceB._v1; + if (facev1 != faceA._v0 && facev1 != faceA._v1 && facev1 != faceA._v2) { + return facev1; + } + + final int facev2 = faceB._v2; + if (facev2 != faceA._v0 && facev2 != faceA._v1 && facev2 != faceA._v2) { + return facev2; + } + + // nothing is different + return -1; + } + + /** + * @param faceA + * @param faceB + * @return the (at most) two vertices shared between the two faces + */ + static int[] getSharedVertices(final NvFaceInfo faceA, final NvFaceInfo faceB) { + final int[] vertexStore = new int[2]; + vertexStore[0] = vertexStore[1] = -1; + + final int facev0 = faceB._v0; + if (facev0 == faceA._v0 || facev0 == faceA._v1 || facev0 == faceA._v2) { + if (vertexStore[0] == -1) { + vertexStore[0] = facev0; + } else { + vertexStore[1] = facev0; + return vertexStore; + } + } + + final int facev1 = faceB._v1; + if (facev1 == faceA._v0 || facev1 == faceA._v1 || facev1 == faceA._v2) { + if (vertexStore[0] == -1) { + vertexStore[0] = facev1; + } else { + vertexStore[1] = facev1; + return vertexStore; + } + } + + final int facev2 = faceB._v2; + if (facev2 == faceA._v0 || facev2 == faceA._v1 || facev2 == faceA._v2) { + if (vertexStore[0] == -1) { + vertexStore[0] = facev2; + } else { + vertexStore[1] = facev2; + return vertexStore; + } + } + + return vertexStore; + } + + static boolean isDegenerate(final NvFaceInfo face) { + if (face._v0 == face._v1) { + return true; + } else if (face._v0 == face._v2) { + return true; + } else if (face._v1 == face._v2) { + return true; + } else { + return false; + } + } + + static boolean isDegenerate(final int v0, final int v1, final int v2) { + if (v0 == v1) { + return true; + } else if (v0 == v2) { + return true; + } else if (v1 == v2) { + return true; + } else { + return false; + } + } + + // /////////////////////////////////////////////////////////////////////////////// + // + // Big mess of functions called during stripification + // Note: I removed some that were orphans - JES + // + // /////////////////////////////////////////////////////////////////////////////// + + /** + * @param faceInfo + * @param v0 + * @param v1 + * @return true if the face is ordered in CW fashion + */ + static boolean isCW(final NvFaceInfo faceInfo, final int v0, final int v1) { + if (faceInfo._v0 == v0) { + return faceInfo._v1 == v1; + } else if (faceInfo._v1 == v0) { + return faceInfo._v2 == v1; + } else { + return faceInfo._v0 == v1; + } + } + + /** + * + * @param numIndices + * @return true if the next face should be ordered in CW fashion + */ + static boolean nextIsCW(final int numIndices) { + return numIndices % 2 == 0; + } + + /** + * @param indices + * @param face + * @return vertex of the input face which is "next" in the input index list + */ + static int getNextIndex(final List<Integer> indices, final NvFaceInfo face) { + final int numIndices = indices.size(); + assert numIndices >= 2; + + final int v0 = indices.get(numIndices - 2); + final int v1 = indices.get(numIndices - 1); + + final int fv0 = face._v0; + final int fv1 = face._v1; + final int fv2 = face._v2; + + if (fv0 != v0 && fv0 != v1) { + if (fv1 != v0 && fv1 != v1 || fv2 != v0 && fv2 != v1) { + NvStripifier.logger.warning("getNextIndex: Triangle doesn't have all of its vertices\n"); + NvStripifier.logger.warning("getNextIndex: Duplicate triangle probably got us derailed\n"); + } + return fv0; + } + if (fv1 != v0 && fv1 != v1) { + if (fv0 != v0 && fv0 != v1 || fv2 != v0 && fv2 != v1) { + NvStripifier.logger.warning("getNextIndex: Triangle doesn't have all of its vertices\n"); + NvStripifier.logger.warning("getNextIndex: Duplicate triangle probably got us derailed\n"); + } + return fv1; + } + if (fv2 != v0 && fv2 != v1) { + if (fv0 != v0 && fv0 != v1 || fv1 != v0 && fv1 != v1) { + NvStripifier.logger.warning("getNextIndex: Triangle doesn't have all of its vertices\n"); + NvStripifier.logger.warning("getNextIndex: Duplicate triangle probably got us derailed\n"); + } + return fv2; + } + + // shouldn't get here, but let's try and fail gracefully + if (fv0 == fv1 || fv0 == fv2) { + return fv0; + } else if (fv1 == fv0 || fv1 == fv2) { + return fv1; + } else if (fv2 == fv0 || fv2 == fv1) { + return fv2; + } else { + return -1; + } + } + + /** + * find the edge info for these two indices + * + * @param edgeInfos + * @param v0 + * @param v1 + * @return + */ + static NvEdgeInfo findEdgeInfo(final List<NvEdgeInfo> edgeInfos, final int v0, final int v1) { + // we can get to it through either array because the edge infos have a v0 and v1 and there is no order except + // how it was first created. + NvEdgeInfo infoIter = edgeInfos.get(v0); + while (infoIter != null) { + if (infoIter._v0 == v0) { + if (infoIter._v1 == v1) { + return infoIter; + } else { + infoIter = infoIter._nextV0; + } + } else { + assert infoIter._v1 == v0; + if (infoIter._v0 == v1) { + return infoIter; + } else { + infoIter = infoIter._nextV1; + } + } + } + return null; + } + + /** + * find the other face sharing these vertices + * + * @param edgeInfos + * @param v0 + * @param v1 + * @param faceInfo + * @return + */ + static NvFaceInfo findOtherFace(final List<NvEdgeInfo> edgeInfos, final int v0, final int v1, + final NvFaceInfo faceInfo) { + final NvEdgeInfo edgeInfo = NvStripifier.findEdgeInfo(edgeInfos, v0, v1); + + if (edgeInfo == null && v0 == v1) { + // we've hit a degenerate + return null; + } + + assert edgeInfo != null; + return edgeInfo._face0 == faceInfo ? edgeInfo._face1 : edgeInfo._face0; + } + + /** + * A good reset point is one near other committed areas so that we know that when we've made the longest strips its + * because we're stripifying in the same general orientation. + * + * @param faceInfos + * @param edgeInfos + * @return + */ + NvFaceInfo findGoodResetPoint(final List<NvFaceInfo> faceInfos, final List<NvEdgeInfo> edgeInfos) { + // we hop into different areas of the mesh to try to get other large open spans done. Areas of small strips can + // just be left to triangle lists added at the end. + NvFaceInfo result = null; + final int numFaces = faceInfos.size(); + int startPoint; + if (_firstTimeResetPoint) { + // first time, find a face with few neighbors (look for an edge of the mesh) + startPoint = findStartPoint(faceInfos, edgeInfos); + _firstTimeResetPoint = false; + } else { + startPoint = (int) (((float) numFaces - 1) * _meshJump); + } + + if (startPoint == -1) { + startPoint = (int) (((float) numFaces - 1) * _meshJump); + + // meshJump += 0.1f; + // if (meshJump > 1.0f) + // meshJump = .05f; + } + + int i = startPoint; + do { + + // if this guy isn't visited, try him + if (faceInfos.get(i)._stripId < 0) { + result = faceInfos.get(i); + break; + } + + // update the index and clamp to 0-(numFaces-1) + if (++i >= numFaces) { + i = 0; + } + + } while (i != startPoint); + + // update the meshJump + _meshJump += 0.1f; + if (_meshJump > 1.0f) { + _meshJump = .05f; + } + + // return the best face we found + return result; + } + + /** + * Does the stripification, puts output strips into vector allStrips + * + * Works by setting running a number of experiments in different areas of the mesh, and accepting the one which + * results in the longest strips. It then accepts this, and moves on to a different area of the mesh. We try to jump + * around the mesh some, to ensure that large open spans of strips get generated. + * + * @param allStrips + * @param allFaceInfos + * @param allEdgeInfos + * @param numSamples + */ + @SuppressWarnings("unchecked") + void findAllStrips(final List<NvStripInfo> allStrips, final List<NvFaceInfo> allFaceInfos, + final List<NvEdgeInfo> allEdgeInfos, final int numSamples) { + // the experiments + int experimentId = 0; + int stripId = 0; + boolean done = false; + + while (!done) { + + // + // PHASE 1: Set up numSamples * numEdges experiments + // + final List<NvStripInfo>[] experiments = new List[numSamples * 6]; + for (int i = 0; i < experiments.length; i++) { + experiments[i] = Lists.newArrayList(); + } + + int experimentIndex = 0; + final Set<NvFaceInfo> resetPoints = Sets.newHashSet(); + for (int i = 0; i < numSamples; i++) { + + // Try to find another good reset point. + // If there are none to be found, we are done + final NvFaceInfo nextFace = findGoodResetPoint(allFaceInfos, allEdgeInfos); + if (nextFace == null) { + done = true; + break; + } + // If we have already evaluated starting at this face in this slew + // of experiments, then skip going any further + else if (resetPoints.contains(nextFace)) { + continue; + } + + // trying it now... + resetPoints.add(nextFace); + + // otherwise, we shall now try experiments for starting on the 01,12, and 20 edges + assert nextFace._stripId < 0; + + // build the strip off of this face's 0-1 edge + final NvEdgeInfo edge01 = NvStripifier.findEdgeInfo(allEdgeInfos, nextFace._v0, nextFace._v1); + final NvStripInfo strip01 = new NvStripInfo(new NvStripStartInfo(nextFace, edge01, true), stripId++, + experimentId++); + experiments[experimentIndex++].add(strip01); + + // build the strip off of this face's 1-0 edge + final NvEdgeInfo edge10 = NvStripifier.findEdgeInfo(allEdgeInfos, nextFace._v0, nextFace._v1); + final NvStripInfo strip10 = new NvStripInfo(new NvStripStartInfo(nextFace, edge10, false), stripId++, + experimentId++); + experiments[experimentIndex++].add(strip10); + + // build the strip off of this face's 1-2 edge + final NvEdgeInfo edge12 = NvStripifier.findEdgeInfo(allEdgeInfos, nextFace._v1, nextFace._v2); + final NvStripInfo strip12 = new NvStripInfo(new NvStripStartInfo(nextFace, edge12, true), stripId++, + experimentId++); + experiments[experimentIndex++].add(strip12); + + // build the strip off of this face's 2-1 edge + final NvEdgeInfo edge21 = NvStripifier.findEdgeInfo(allEdgeInfos, nextFace._v1, nextFace._v2); + final NvStripInfo strip21 = new NvStripInfo(new NvStripStartInfo(nextFace, edge21, false), stripId++, + experimentId++); + experiments[experimentIndex++].add(strip21); + + // build the strip off of this face's 2-0 edge + final NvEdgeInfo edge20 = NvStripifier.findEdgeInfo(allEdgeInfos, nextFace._v2, nextFace._v0); + final NvStripInfo strip20 = new NvStripInfo(new NvStripStartInfo(nextFace, edge20, true), stripId++, + experimentId++); + experiments[experimentIndex++].add(strip20); + + // build the strip off of this face's 0-2 edge + final NvEdgeInfo edge02 = NvStripifier.findEdgeInfo(allEdgeInfos, nextFace._v2, nextFace._v0); + final NvStripInfo strip02 = new NvStripInfo(new NvStripStartInfo(nextFace, edge02, false), stripId++, + experimentId++); + experiments[experimentIndex++].add(strip02); + } + + // + // PHASE 2: Iterate through that we setup in the last phase + // and really build each of the strips and strips that follow to see how + // far we get + // + final int numExperiments = experimentIndex; + for (int i = 0; i < numExperiments; i++) { + // get the strip set + NvStripInfo stripIter = experiments[i].get(0); + + // build the first strip of the list + stripIter.build(allEdgeInfos, allFaceInfos); + final int currExperimentId = stripIter._experimentId; + + final NvStripStartInfo startInfo = new NvStripStartInfo(null, null, false); + while (findTraversal(allFaceInfos, allEdgeInfos, stripIter, startInfo)) { + + // create the new strip info + stripIter = new NvStripInfo(startInfo, stripId++, currExperimentId); + + // build the next strip + stripIter.build(allEdgeInfos, allFaceInfos); + + // add it to the list + experiments[i].add(stripIter); + } + } + + // + // Phase 3: Find the experiment that has the most promise + // + int bestIndex = 0; + double bestValue = 0; + for (int i = 0; i < numExperiments; i++) { + final float avgStripSizeWeight = 1.0f; + // final float numTrisWeight = 0.0f; // unused + final float numStripsWeight = 0.0f; + final float avgStripSize = avgStripSize(experiments[i]); + final float numStrips = experiments[i].size(); + final float value = avgStripSize * avgStripSizeWeight + numStrips * numStripsWeight; + // float value = 1.f / numStrips; + // float value = numStrips * avgStripSize; + + if (value > bestValue) { + bestValue = value; + bestIndex = i; + } + } + + // + // Phase 4: commit the best experiment of the bunch + // + commitStrips(allStrips, experiments[bestIndex]); + } + } + + /** + * Splits the input vector of strips (allBigStrips) into smaller, cache friendly pieces, then reorders these pieces + * to maximize cache hits. The final strips are stored in outStrips + * + * @param allStrips + * @param outStrips + * @param edgeInfos + * @param outFaceList + */ + void splitUpStripsAndOptimize(final List<NvStripInfo> allStrips, final List<NvStripInfo> outStrips, + final List<NvEdgeInfo> edgeInfos, final List<NvFaceInfo> outFaceList) { + final int threshold = _cacheSize; + final List<NvStripInfo> tempStrips = Lists.newArrayList(); + + // split up strips into threshold-sized pieces + for (int i = 0; i < allStrips.size(); i++) { + final NvStripInfo allStripI = allStrips.get(i); + NvStripInfo currentStrip; + final NvStripStartInfo startInfo = new NvStripStartInfo(null, null, false); + + int actualStripSize = 0; + for (final NvFaceInfo face : allStripI._faces) { + if (!NvStripifier.isDegenerate(face)) { + actualStripSize++; + } + } + + if (actualStripSize > threshold) { + + final int numTimes = actualStripSize / threshold; + int numLeftover = actualStripSize % threshold; + + int degenerateCount = 0, j = 0; + for (; j < numTimes; j++) { + currentStrip = new NvStripInfo(startInfo, 0, -1); + + int faceCtr = j * threshold + degenerateCount; + boolean bFirstTime = true; + while (faceCtr < threshold + j * threshold + degenerateCount) { + if (NvStripifier.isDegenerate(allStripI._faces.get(faceCtr))) { + degenerateCount++; + + // last time or first time through, no need for a degenerate + if ((faceCtr + 1 != threshold + j * threshold + degenerateCount || j == numTimes - 1 + && numLeftover < 4 && numLeftover > 0) + && !bFirstTime) { + currentStrip._faces.add(allStripI._faces.get(faceCtr++)); + } else { + ++faceCtr; + } + } else { + currentStrip._faces.add(allStripI._faces.get(faceCtr++)); + bFirstTime = false; + } + } + if (j == numTimes - 1) // last time through + { + if (numLeftover < 4 && numLeftover > 0) // way too small + { + // just add to last strip + int ctr = 0; + while (ctr < numLeftover) { + if (NvStripifier.isDegenerate(allStripI._faces.get(faceCtr))) { + ++degenerateCount; + } else { + ++ctr; + } + currentStrip._faces.add(allStripI._faces.get(faceCtr++)); + } + numLeftover = 0; + } + } + tempStrips.add(currentStrip); + } + int leftOff = j * threshold + degenerateCount; + + if (numLeftover != 0) { + currentStrip = new NvStripInfo(startInfo, 0, -1); + + int ctr = 0; + boolean bFirstTime = true; + while (ctr < numLeftover) { + if (!NvStripifier.isDegenerate(allStripI._faces.get(leftOff))) { + ctr++; + bFirstTime = false; + currentStrip._faces.add(allStripI._faces.get(leftOff++)); + } else if (!bFirstTime) { + currentStrip._faces.add(allStripI._faces.get(leftOff++)); + } else { + leftOff++; + } + } + + tempStrips.add(currentStrip); + } + } else { + // we're not just doing a tempStrips.add(allBigStrips.get(i)) because + // this way we can delete allBigStrips later to free the memory + currentStrip = new NvStripInfo(startInfo, 0, -1); + + for (int j = 0; j < allStripI._faces.size(); j++) { + currentStrip._faces.add(allStripI._faces.get(j)); + } + + tempStrips.add(currentStrip); + } + } + + // add small strips to face list + final List<NvStripInfo> tempStrips2 = Lists.newArrayList(); + removeSmallStrips(tempStrips, tempStrips2, outFaceList); + + outStrips.clear(); + // screw optimization for now + // for(i = 0; i < tempStrips.size(); ++i) + // outStrips.add(tempStrips.get(i)); + + if (tempStrips2.size() != 0) { + // Optimize for the vertex cache + final VertexCache vcache = new VertexCache(_cacheSize); + + float bestNumHits = -1.0f; + float numHits; + int bestIndex = 0; + int firstIndex = 0; + float minCost = 10000.0f; + + for (int i = 0; i < tempStrips2.size(); i++) { + final NvStripInfo tempStrips2I = tempStrips2.get(i); + int numNeighbors = 0; + + // find strip with least number of neighbors per face + for (int j = 0; j < tempStrips2I._faces.size(); j++) { + numNeighbors += numNeighbors(tempStrips2I._faces.get(j), edgeInfos); + } + + final float currCost = numNeighbors / (float) tempStrips2I._faces.size(); + if (currCost < minCost) { + minCost = currCost; + firstIndex = i; + } + } + + final NvStripInfo tempStrips2FirstIndex = tempStrips2.get(firstIndex); + updateCacheStrip(vcache, tempStrips2FirstIndex); + outStrips.add(tempStrips2FirstIndex); + + tempStrips2FirstIndex._visited = true; + + boolean bWantsCW = tempStrips2FirstIndex._faces.size() % 2 == 0; + + // XXX: this n^2 algo is what slows down stripification so much.... needs to be improved + while (true) { + bestNumHits = -1.0f; + + // find best strip to add next, given the current cache + for (int i = 0; i < tempStrips2.size(); i++) { + final NvStripInfo tempStrips2I = tempStrips2.get(i); + if (tempStrips2I._visited) { + continue; + } + + numHits = calcNumHitsStrip(vcache, tempStrips2I); + if (numHits > bestNumHits) { + bestNumHits = numHits; + bestIndex = i; + } else if (numHits >= bestNumHits) { + // check previous strip to see if this one requires it to switch polarity + final NvStripInfo strip = tempStrips2I; + final int nStripFaceCount = strip._faces.size(); + + final NvFaceInfo tFirstFace = new NvFaceInfo(strip._faces.get(0)); + + // If there is a second face, reorder vertices such that the + // unique vertex is first + if (nStripFaceCount > 1) { + final int nUnique = NvStripifier.getUniqueVertexInB(strip._faces.get(1), tFirstFace); + if (nUnique == tFirstFace._v1) { + final int store = tFirstFace._v1; + tFirstFace._v1 = tFirstFace._v0; + tFirstFace._v0 = store; + } else if (nUnique == tFirstFace._v2) { + final int store = tFirstFace._v2; + tFirstFace._v2 = tFirstFace._v0; + tFirstFace._v0 = store; + } + + // If there is a third face, reorder vertices such that the + // shared vertex is last + if (nStripFaceCount > 2) { + final int[] nShared = NvStripifier.getSharedVertices(strip._faces.get(2), tFirstFace); + if (nShared[0] == tFirstFace._v1 && nShared[1] == -1) { + final int store = tFirstFace._v2; + tFirstFace._v2 = tFirstFace._v1; + tFirstFace._v1 = store; + } + } + } + + // Check CW/CCW ordering + if (bWantsCW == NvStripifier.isCW(strip._faces.get(0), tFirstFace._v0, tFirstFace._v1)) { + // I like this one! + bestIndex = i; + } + } + } + + if (bestNumHits == -1.0f) { + break; + } + tempStrips2.get(bestIndex)._visited = true; + updateCacheStrip(vcache, tempStrips2.get(bestIndex)); + outStrips.add(tempStrips2.get(bestIndex)); + bWantsCW = tempStrips2.get(bestIndex)._faces.size() % 2 == 0 ? bWantsCW : !bWantsCW; + } + } + } + + /** + * @param allStrips + * the whole strip vector...all small strips will be deleted from this list, to avoid leaking mem + * @param allBigStrips + * an out parameter which will contain all strips above minStripLength + * @param faceList + * an out parameter which will contain all faces which were removed from the striplist + */ + void removeSmallStrips(final List<NvStripInfo> allStrips, final List<NvStripInfo> allBigStrips, + final List<NvFaceInfo> faceList) { + faceList.clear(); + allBigStrips.clear(); // make sure these are empty + final List<NvFaceInfo> tempFaceList = Lists.newArrayList(); + + for (int i = 0; i < allStrips.size(); i++) { + final NvStripInfo allStripI = allStrips.get(i); + if (allStripI._faces.size() < _minStripLength) { + // strip is too small, add faces to faceList + for (int j = 0; j < allStripI._faces.size(); j++) { + tempFaceList.add(allStripI._faces.get(j)); + } + } else { + allBigStrips.add(allStripI); + } + } + + if (!tempFaceList.isEmpty()) { + final boolean[] bVisitedList = new boolean[tempFaceList.size()]; + final VertexCache vcache = new VertexCache(_cacheSize); + + int bestNumHits = -1; + int numHits = 0; + int bestIndex = 0; + + while (true) { + bestNumHits = -1; + + // find best face to add next, given the current cache + for (int i = 0; i < tempFaceList.size(); i++) { + if (bVisitedList[i]) { + continue; + } + + numHits = calcNumHitsFace(vcache, tempFaceList.get(i)); + if (numHits > bestNumHits) { + bestNumHits = numHits; + bestIndex = i; + } + } + + if (bestNumHits == -1.0f) { + break; + } + bVisitedList[bestIndex] = true; + updateCacheFace(vcache, tempFaceList.get(bestIndex)); + faceList.add(tempFaceList.get(bestIndex)); + } + } + } + + /** + * Finds the next face to start the next strip on. + * + * @param faceInfos + * @param edgeInfos + * @param strip + * @param startInfo + * @return + */ + boolean findTraversal(final List<NvFaceInfo> faceInfos, final List<NvEdgeInfo> edgeInfos, final NvStripInfo strip, + final NvStripStartInfo startInfo) { + // if the strip was v0.v1 on the edge, then v1 will be a vertex in the next edge. + final int v = strip._startInfo._toV1 ? strip._startInfo._startEdge._v1 : strip._startInfo._startEdge._v0; + + NvFaceInfo untouchedFace = null; + NvEdgeInfo edgeIter = edgeInfos.get(v); + while (edgeIter != null) { + final NvFaceInfo face0 = edgeIter._face0; + final NvFaceInfo face1 = edgeIter._face1; + if (face0 != null && !strip.IsInStrip(face0) && face1 != null && !strip.isMarked(face1)) { + untouchedFace = face1; + break; + } + if (face1 != null && !strip.IsInStrip(face1) && face0 != null && !strip.isMarked(face0)) { + untouchedFace = face0; + break; + } + + // find the next edgeIter + edgeIter = edgeIter._v0 == v ? edgeIter._nextV0 : edgeIter._nextV1; + } + + startInfo._startFace = untouchedFace; + startInfo._startEdge = edgeIter; + if (edgeIter != null) { + if (strip.sharesEdge(startInfo._startFace, edgeInfos)) { + startInfo._toV1 = edgeIter._v0 == v; // note! used to be m_v1 + } else { + startInfo._toV1 = edgeIter._v1 == v; + } + } + return startInfo._startFace != null; + } + + /** + * "Commits" the input strips by setting their m_experimentId to -1 and adding to the allStrips vector + * + * @param allStrips + * @param strips + */ + void commitStrips(final List<NvStripInfo> allStrips, final List<NvStripInfo> strips) { + // Iterate through strips + for (final NvStripInfo strip : strips) { + // Tell the strip that it is now real + strip._experimentId = -1; + + // add to the list of real strips + allStrips.add(strip); + + // Iterate through the faces of the strip + // Tell the faces of the strip that they belong to a real strip now + final List<NvFaceInfo> faces = strip._faces; + for (final NvFaceInfo face : faces) { + strip.markTriangle(face); + } + } + } + + /** + * + * @param strips + * @return the average strip size of the input vector of strips + */ + float avgStripSize(final List<NvStripInfo> strips) { + int sizeAccum = 0; + for (final NvStripInfo strip : strips) { + sizeAccum += strip._faces.size(); + sizeAccum -= strip._numDegenerates; + } + return (float) sizeAccum / (float) strips.size(); + } + + /** + * Finds a good starting point, namely one which has only one neighbor + * + * @param faceInfos + * @param edgeInfos + * @return + */ + int findStartPoint(final List<NvFaceInfo> faceInfos, final List<NvEdgeInfo> edgeInfos) { + int bestCtr = -1; + int bestIndex = -1; + + int i = 0; + for (final NvFaceInfo faceInfo : faceInfos) { + int ctr = 0; + + if (NvStripifier.findOtherFace(edgeInfos, faceInfo._v0, faceInfo._v1, faceInfo) == null) { + ctr++; + } + if (NvStripifier.findOtherFace(edgeInfos, faceInfo._v1, faceInfo._v2, faceInfo) == null) { + ctr++; + } + if (NvStripifier.findOtherFace(edgeInfos, faceInfo._v2, faceInfo._v0, faceInfo) == null) { + ctr++; + } + if (ctr > bestCtr) { + bestCtr = ctr; + bestIndex = i; + } + i++; + } + + if (bestCtr == 0) { + return -1; + } else { + return bestIndex; + } + } + + /** + * Updates the input vertex cache with this strip's vertices + * + * @param vcache + * @param strip + */ + void updateCacheStrip(final VertexCache vcache, final NvStripInfo strip) { + for (final NvFaceInfo face : strip._faces) { + updateCacheFace(vcache, face); + } + } + + /** + * Updates the input vertex cache with this face's vertices + * + * @param vcache + * @param face + */ + void updateCacheFace(final VertexCache vcache, final NvFaceInfo face) { + if (!vcache.inCache(face._v0)) { + vcache.addEntry(face._v0); + } + + if (!vcache.inCache(face._v1)) { + vcache.addEntry(face._v1); + } + + if (!vcache.inCache(face._v2)) { + vcache.addEntry(face._v2); + } + } + + /** + * @param vcache + * @param strip + * @return the number of cache hits per face in the strip + */ + float calcNumHitsStrip(final VertexCache vcache, final NvStripInfo strip) { + int numHits = 0; + int numFaces = 0; + + for (final NvFaceInfo face : strip._faces) { + if (vcache.inCache(face._v0)) { + ++numHits; + } + + if (vcache.inCache(face._v1)) { + ++numHits; + } + + if (vcache.inCache(face._v2)) { + ++numHits; + } + + numFaces++; + } + + return (float) numHits / (float) numFaces; + } + + /** + * @param vcache + * @param face + * @return the number of cache hits in the face + */ + int calcNumHitsFace(final VertexCache vcache, final NvFaceInfo face) { + int numHits = 0; + + if (vcache.inCache(face._v0)) { + numHits++; + } + + if (vcache.inCache(face._v1)) { + numHits++; + } + + if (vcache.inCache(face._v2)) { + numHits++; + } + + return numHits; + } + + /** + * + * @param face + * @param edgeInfoVec + * @return the number of neighbors that this face has + */ + int numNeighbors(final NvFaceInfo face, final List<NvEdgeInfo> edgeInfoVec) { + int numNeighbors = 0; + + if (NvStripifier.findOtherFace(edgeInfoVec, face._v0, face._v1, face) != null) { + numNeighbors++; + } + + if (NvStripifier.findOtherFace(edgeInfoVec, face._v1, face._v2, face) != null) { + numNeighbors++; + } + + if (NvStripifier.findOtherFace(edgeInfoVec, face._v2, face._v0, face) != null) { + numNeighbors++; + } + + return numNeighbors; + } + + /** + * Builds the list of all face and edge infos + * + * @param faceInfos + * @param edgeInfos + * @param maxIndex + */ + void buildStripifyInfo(final List<NvFaceInfo> faceInfos, final List<NvEdgeInfo> edgeInfos, final int maxIndex) { + // reserve space for the face infos, but do not resize them. + final int numIndices = _indices.size(); + + // we actually resize the edge infos, so we must initialize to null + for (int i = 0; i <= maxIndex; i++) { + edgeInfos.add(null); + } + + // iterate through the triangles of the triangle list + final int numTriangles = numIndices / 3; + int index = 0; + final boolean[] bFaceUpdated = new boolean[3]; + + for (int i = 0; i < numTriangles; i++) { + boolean bMightAlreadyExist = true; + bFaceUpdated[0] = false; + bFaceUpdated[1] = false; + bFaceUpdated[2] = false; + + // grab the indices + final int v0 = _indices.get(index++); + final int v1 = _indices.get(index++); + final int v2 = _indices.get(index++); + + // we disregard degenerates + if (NvStripifier.isDegenerate(v0, v1, v2)) { + continue; + } + + // create the face info and add it to the list of faces, but only if this exact face doesn't already + // exist in the list + final NvFaceInfo faceInfo = new NvFaceInfo(v0, v1, v2); + + // grab the edge infos, creating them if they do not already exist + NvEdgeInfo edgeInfo01 = NvStripifier.findEdgeInfo(edgeInfos, v0, v1); + if (edgeInfo01 == null) { + // since one of it's edges isn't in the edge data structure, it can't already exist in the face + // structure + bMightAlreadyExist = false; + + // create the info + edgeInfo01 = new NvEdgeInfo(v0, v1); + + // update the linked list on both + edgeInfo01._nextV0 = edgeInfos.get(v0); + edgeInfo01._nextV1 = edgeInfos.get(v1); + edgeInfos.set(v0, edgeInfo01); + edgeInfos.set(v1, edgeInfo01); + + // set face 0 + edgeInfo01._face0 = faceInfo; + } else { + if (edgeInfo01._face1 != null) { + NvStripifier.logger + .warning("BuildStripifyInfo: > 2 triangles on an edge... uncertain consequences\n"); + } else { + edgeInfo01._face1 = faceInfo; + bFaceUpdated[0] = true; + } + } + + // grab the edge infos, creating them if they do not already exist + NvEdgeInfo edgeInfo12 = NvStripifier.findEdgeInfo(edgeInfos, v1, v2); + if (edgeInfo12 == null) { + bMightAlreadyExist = false; + + // create the info + edgeInfo12 = new NvEdgeInfo(v1, v2); + + // update the linked list on both + edgeInfo12._nextV0 = edgeInfos.get(v1); + edgeInfo12._nextV1 = edgeInfos.get(v2); + edgeInfos.set(v1, edgeInfo12); + edgeInfos.set(v2, edgeInfo12); + + // set face 0 + edgeInfo12._face0 = faceInfo; + } else { + if (edgeInfo12._face1 != null) { + NvStripifier.logger + .warning("BuildStripifyInfo: > 2 triangles on an edge... uncertain consequences\n"); + } else { + edgeInfo12._face1 = faceInfo; + bFaceUpdated[1] = true; + } + } + + // grab the edge infos, creating them if they do not already exist + NvEdgeInfo edgeInfo20 = NvStripifier.findEdgeInfo(edgeInfos, v2, v0); + if (edgeInfo20 == null) { + bMightAlreadyExist = false; + + // create the info + edgeInfo20 = new NvEdgeInfo(v2, v0); + + // update the linked list on both + edgeInfo20._nextV0 = edgeInfos.get(v2); + edgeInfo20._nextV1 = edgeInfos.get(v0); + edgeInfos.set(v2, edgeInfo20); + edgeInfos.set(v0, edgeInfo20); + + // set face 0 + edgeInfo20._face0 = faceInfo; + } else { + if (edgeInfo20._face1 != null) { + NvStripifier.logger + .warning("BuildStripifyInfo: > 2 triangles on an edge... uncertain consequences\n"); + } else { + edgeInfo20._face1 = faceInfo; + bFaceUpdated[2] = true; + } + } + + if (bMightAlreadyExist) { + if (!alreadyExists(faceInfo, faceInfos)) { + faceInfos.add(faceInfo); + } else { + // cleanup pointers that point to this deleted face + if (bFaceUpdated[0]) { + edgeInfo01._face1 = null; + } + if (bFaceUpdated[1]) { + edgeInfo12._face1 = null; + } + if (bFaceUpdated[2]) { + edgeInfo20._face1 = null; + } + } + } else { + faceInfos.add(faceInfo); + } + } + } + + boolean alreadyExists(final NvFaceInfo toFind, final List<NvFaceInfo> faceInfos) { + for (final NvFaceInfo face : faceInfos) { + if (face._v0 == toFind._v0 && face._v1 == toFind._v1 && face._v2 == toFind._v2) { + return true; + } + } + + return false; + } +} diff --git a/ardor3d-extras/src/main/java/com/ardor3d/extension/model/util/nvtristrip/NvTriangleStripper.java b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/util/nvtristrip/NvTriangleStripper.java index c16a631..c12d86d 100644 --- a/ardor3d-extras/src/main/java/com/ardor3d/extension/model/util/nvtristrip/NvTriangleStripper.java +++ b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/util/nvtristrip/NvTriangleStripper.java @@ -1,564 +1,564 @@ -/**
- * Copyright (c) 2008-2012 Ardor Labs, Inc.
- *
- * This file is part of Ardor3D.
- *
- * Ardor3D is free software: you can redistribute it and/or modify it
- * under the terms of its license which may be found in the accompanying
- * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
- */
-
-package com.ardor3d.extension.model.util.nvtristrip;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicReference;
-
-import com.ardor3d.renderer.IndexMode;
-import com.ardor3d.scenegraph.IndexBufferData;
-import com.ardor3d.scenegraph.IntBufferData;
-import com.ardor3d.scenegraph.Mesh;
-import com.ardor3d.scenegraph.MeshData;
-import com.ardor3d.scenegraph.Spatial;
-import com.ardor3d.scenegraph.visitor.Visitor;
-import com.ardor3d.util.geom.BufferUtils;
-import com.google.common.collect.Lists;
-
-/**
- * Ported from <a href="http://developer.nvidia.com/object/nvtristrip_library.html">NVIDIA's NvTriStrip Library</a>
- */
-public class NvTriangleStripper implements Visitor {
- /** GeForce1 and 2 cache size */
- public static final int CACHESIZE_GEFORCE1_2 = 16;
-
- /** GeForce3 cache size */
- public static final int CACHESIZE_GEFORCE3 = 24;
-
- private int _cacheSize = NvTriangleStripper.CACHESIZE_GEFORCE3;
- private int _minStripSize = 0;
- private int _restartVal = 0;
- private boolean _stitchStrips = true;
- private boolean _listsOnly = false;
- private boolean _restart = false;
- private boolean _reorderVertices = false;
-
- /**
- * For GPUs that support primitive restart, this sets a value as the restart index
- *
- * Restart is meaningless if strips are not being stitched together, so enabling restart makes NvTriStrip forcing
- * stitching. So, you'll get back one strip.
- *
- * @param restartVal
- */
- public void enableRestart(final int restartVal) {
- _restart = true;
- _restartVal = restartVal;
- }
-
- /**
- * For GPUs that support primitive restart, this disables using primitive restart
- */
- public void disableRestart() {
- _restart = false;
- }
-
- public boolean isRestart() {
- return _restart;
- }
-
- /**
- * Sets the cache size which the stripfier uses to optimize the data. Controls the length of the generated
- * individual strips. This is the "actual" cache size, so 24 for GeForce3 and 16 for GeForce1/2 You may want to play
- * around with this number to tweak performance.
- *
- * @param cacheSize
- * (Default value: 24)
- */
- public void setCacheSize(final int cacheSize) {
- _cacheSize = cacheSize;
- }
-
- public int getCacheSize() {
- return _cacheSize;
- }
-
- /**
- * boolean to indicate whether to stitch together strips into one huge strip or not. If set to true, you'll get back
- * one huge strip stitched together using degenerate triangles. If set to false, you'll get back a large number of
- * separate strips.
- *
- * @param bStitchStrips
- * (Default value: true)
- */
- public void setStitchStrips(final boolean bStitchStrips) {
- _stitchStrips = bStitchStrips;
- }
-
- public boolean isStitchStrips() {
- return _stitchStrips;
- }
-
- /**
- *
- * Sets the minimum acceptable size for a strip, in triangles. All strips generated which are shorter than this will
- * be thrown into one big, separate list.
- *
- * @param minSize
- * (Default value: 0)
- */
- public void setMinStripSize(final int minSize) {
- _minStripSize = minSize;
- }
-
- public int getMinStripSize() {
- return _minStripSize;
- }
-
- /**
- * If set to true, will return an optimized list, with no strips at all.
- *
- * @param bListsOnly
- * (Default value: false)
- */
- public void setListsOnly(final boolean bListsOnly) {
- _listsOnly = bListsOnly;
- }
-
- public boolean isListsOnly() {
- return _listsOnly;
- }
-
- /**
- * If set to true, will call remapIndices after generateStrips.
- *
- * @param reorder
- * (Default value: false)
- * @see #remapIndices(PrimitiveGroup[], AtomicReference, int)
- */
- public void setReorderVertices(final boolean reorder) {
- _reorderVertices = reorder;
- }
-
- public boolean isReorderVertices() {
- return _reorderVertices;
- }
-
- /**
- *
- Returns true if the two triangles defined by firstTri and secondTri are the same The "same" is defined in this
- * case as having the same indices with the same winding order
- *
- * @param firstTri0
- * @param firstTri1
- * @param firstTri2
- * @param secondTri0
- * @param secondTri1
- * @param secondTri2
- * @return
- */
- boolean sameTriangle(final int firstTri0, final int firstTri1, final int firstTri2, final int secondTri0,
- final int secondTri1, final int secondTri2) {
- boolean isSame = false;
-
- if (firstTri0 == secondTri0) {
- if (firstTri1 == secondTri1) {
- if (firstTri2 == secondTri2) {
- isSame = true;
- }
- }
- } else if (firstTri0 == secondTri1) {
- if (firstTri1 == secondTri2) {
- if (firstTri2 == secondTri0) {
- isSame = true;
- }
- }
- } else if (firstTri0 == secondTri2) {
- if (firstTri1 == secondTri0) {
- if (firstTri2 == secondTri1) {
- isSame = true;
- }
- }
- }
-
- return isSame;
- }
-
- boolean testTriangle(final int v0, final int v1, final int v2, final List<NvFaceInfo> in_bins[], final int NUMBINS) {
- // hash this triangle
- boolean isLegit = false;
- int ctr = v0 % NUMBINS;
- NvFaceInfo face;
- for (int k = 0; k < in_bins[ctr].size(); ++k) {
- // check triangles in this bin
- face = in_bins[ctr].get(k);
- if (sameTriangle(face._v0, face._v1, face._v2, v0, v1, v2)) {
- isLegit = true;
- break;
- }
- }
- if (!isLegit) {
- ctr = v1 % NUMBINS;
- for (int k = 0; k < in_bins[ctr].size(); ++k) {
- face = in_bins[ctr].get(k);
- // check triangles in this bin
- if (sameTriangle(face._v0, face._v1, face._v2, v0, v1, v2)) {
- isLegit = true;
- break;
- }
- }
-
- if (!isLegit) {
- ctr = v2 % NUMBINS;
- for (int k = 0; k < in_bins[ctr].size(); ++k) {
- face = in_bins[ctr].get(k);
- // check triangles in this bin
- if (sameTriangle(face._v0, face._v1, face._v2, v0, v1, v2)) {
- isLegit = true;
- break;
- }
- }
-
- }
- }
-
- return isLegit;
- }
-
- /**
- *
- * @param in_indices
- * input index list, the indices you would use to render
- * @param validate
- * @return array of optimized/stripified PrimitiveGroups
- */
- @SuppressWarnings("unchecked")
- public PrimitiveGroup[] generateStrips(final int[] in_indices, final boolean validate) {
- if (in_indices == null || in_indices.length == 0) {
- return new PrimitiveGroup[0];
- }
-
- int numGroups = 0;
- PrimitiveGroup[] primGroups;
-
- // put data in format that the stripifier likes
- final List<Integer> tempIndices = Lists.newArrayList();
- int maxIndex = 0;
- for (int i = 0; i < in_indices.length; i++) {
- tempIndices.add(in_indices[i]);
- if (in_indices[i] > maxIndex) {
- maxIndex = in_indices[i];
- }
- }
- final List<NvStripInfo> tempStrips = Lists.newArrayList();
- final List<NvFaceInfo> tempFaces = Lists.newArrayList();
-
- final NvStripifier stripifier = new NvStripifier();
-
- // do actual stripification
- stripifier.stripify(tempIndices, _cacheSize, _minStripSize, maxIndex, tempStrips, tempFaces);
-
- // stitch strips together
- final List<Integer> stripIndices = Lists.newArrayList();
- int numSeparateStrips = 0;
-
- if (_listsOnly) {
- // if we're outputting only lists, we're done
- numGroups = 1;
- primGroups = new PrimitiveGroup[numGroups];
- primGroups[0] = new PrimitiveGroup();
- final PrimitiveGroup[] primGroupArray = primGroups;
-
- // count the total number of indices
- int numIndices = 0;
- for (int i = 0; i < tempStrips.size(); i++) {
- numIndices += tempStrips.get(i)._faces.size() * 3;
- }
-
- // add in the list
- numIndices += tempFaces.size() * 3;
-
- primGroupArray[0].setType(IndexMode.Triangles);
- primGroupArray[0].setIndices(new int[numIndices]);
- primGroupArray[0].setNumIndices(numIndices);
-
- // do strips
- int indexCtr = 0;
- for (final NvStripInfo strip : tempStrips) {
- for (final NvFaceInfo face : strip._faces) {
- // degenerates are of no use with lists
- if (!NvStripifier.isDegenerate(face)) {
- primGroupArray[0]._getIndices()[indexCtr++] = face._v0;
- primGroupArray[0]._getIndices()[indexCtr++] = face._v1;
- primGroupArray[0]._getIndices()[indexCtr++] = face._v2;
- } else {
- // we've removed a tri, reduce the number of indices
- primGroupArray[0].setNumIndices(primGroupArray[0].getNumIndices() - 3);
- }
- }
- }
-
- // do lists
- for (final NvFaceInfo face : tempFaces) {
- primGroupArray[0]._getIndices()[indexCtr++] = face._v0;
- primGroupArray[0]._getIndices()[indexCtr++] = face._v1;
- primGroupArray[0]._getIndices()[indexCtr++] = face._v2;
- }
- } else {
- numSeparateStrips = stripifier.createStrips(tempStrips, stripIndices, _stitchStrips, _restart, _restartVal);
-
- // if we're stitching strips together, we better get back only one strip from createStrips()
- assert _stitchStrips && numSeparateStrips == 1 || !_stitchStrips;
-
- // convert to output format
- numGroups = numSeparateStrips; // for the strips
- if (tempFaces.size() != 0) {
- numGroups++;
- } // we've got a list as well, increment
- primGroups = new PrimitiveGroup[numGroups];
- for (int i = 0; i < primGroups.length; i++) {
- primGroups[i] = new PrimitiveGroup();
- }
- final PrimitiveGroup[] primGroupArray = primGroups;
-
- // first, the strips
- int startingLoc = 0;
- for (int stripCtr = 0; stripCtr < numSeparateStrips; stripCtr++) {
- int stripLength = 0;
-
- if (!_stitchStrips) {
- int i = startingLoc;
- // if we've got multiple strips, we need to figure out the correct length
- for (; i < stripIndices.size(); i++) {
- if (stripIndices.get(i) == -1) {
- break;
- }
- }
-
- stripLength = i - startingLoc;
- } else {
- stripLength = stripIndices.size();
- }
-
- primGroupArray[stripCtr].setType(IndexMode.TriangleStrip);
- primGroupArray[stripCtr].setIndices(new int[stripLength]);
- primGroupArray[stripCtr].setNumIndices(stripLength);
-
- int indexCtr = 0;
- for (int i = startingLoc; i < stripLength + startingLoc; i++) {
- primGroupArray[stripCtr]._getIndices()[indexCtr++] = stripIndices.get(i);
- }
-
- // we add 1 to account for the -1 separating strips
- // this doesn't break the stitched case since we'll exit the loop
- startingLoc += stripLength + 1;
- }
-
- // next, the list
- if (tempFaces.size() != 0) {
- final int faceGroupLoc = numGroups - 1; // the face group is the last one
- primGroupArray[faceGroupLoc].setType(IndexMode.Triangles);
- primGroupArray[faceGroupLoc].setIndices(new int[tempFaces.size() * 3]);
- primGroupArray[faceGroupLoc].setNumIndices(tempFaces.size() * 3);
- int indexCtr = 0;
- for (final NvFaceInfo face : tempFaces) {
- primGroupArray[faceGroupLoc]._getIndices()[indexCtr++] = face._v0;
- primGroupArray[faceGroupLoc]._getIndices()[indexCtr++] = face._v1;
- primGroupArray[faceGroupLoc]._getIndices()[indexCtr++] = face._v2;
- }
- }
- }
-
- // validate generated data against input
- if (validate) {
- final int NUMBINS = 100;
-
- final List<NvFaceInfo> in_bins[] = new List[NUMBINS];
- for (int i = 0; i < NUMBINS; i++) {
- in_bins[i] = Lists.newArrayList();
- }
-
- // hash input indices on first index
- for (int i = 0; i < in_indices.length; i += 3) {
- final NvFaceInfo faceInfo = new NvFaceInfo(in_indices[i], in_indices[i + 1], in_indices[i + 2]);
- in_bins[in_indices[i] % NUMBINS].add(faceInfo);
- }
-
- for (int i = 0; i < numGroups; ++i) {
- switch (primGroups[i].getType()) {
- case Triangles: {
- for (int j = 0; j < primGroups[i].getNumIndices(); j += 3) {
- final int v0 = primGroups[i]._getIndices()[j];
- final int v1 = primGroups[i]._getIndices()[j + 1];
- final int v2 = primGroups[i]._getIndices()[j + 2];
-
- // ignore degenerates
- if (NvStripifier.isDegenerate(v0, v1, v2)) {
- continue;
- }
-
- if (!testTriangle(v0, v1, v2, in_bins, NUMBINS)) {
- throw new IllegalStateException("failed validation");
- }
- }
- break;
- }
-
- case TriangleStrip: {
- boolean flip = false;
- for (int j = 2; j < primGroups[i].getNumIndices(); ++j) {
- final int v0 = primGroups[i]._getIndices()[j - 2];
- int v1 = primGroups[i]._getIndices()[j - 1];
- int v2 = primGroups[i]._getIndices()[j];
-
- if (flip) {
- // swap v1 and v2
- final int swap = v1;
- v1 = v2;
- v2 = swap;
- }
-
- // ignore degenerates
- if (NvStripifier.isDegenerate(v0, v1, v2)) {
- flip = !flip;
- continue;
- }
-
- if (!testTriangle(v0, v1, v2, in_bins, NUMBINS)) {
- throw new IllegalStateException("failed validation");
- }
-
- flip = !flip;
- }
- break;
- }
-
- case TriangleFan:
- default:
- break;
- }
- }
-
- }
-
- return primGroups;
- }
-
- /**
- * Function to remap your indices to improve spatial locality in your vertex buffer.
- *
- * Note that you must reorder your vertex buffer according to the remapping handed back to you.
- *
- * Credit goes to the MS Xbox crew for the idea for this interface.
- *
- * @param in_primGroups
- * array of PrimitiveGroups you want remapped
- * @param numVerts
- * number of vertices in your vertex buffer, also can be thought of as the range of acceptable values for
- * indices in your primitive groups.
- * @return index remap. old index is key into array, value there is the old location for the vertex. -1 means vertex
- * was never referenced
- */
- public PrimitiveGroup[] remapIndices(final PrimitiveGroup[] in_primGroups,
- final AtomicReference<int[]> remappedVertices, final int numVerts) {
- final PrimitiveGroup[] remappedGroups = new PrimitiveGroup[in_primGroups.length];
-
- // caches oldIndex --> newIndex conversion
- final int[] indexCache = new int[numVerts];
- Arrays.fill(indexCache, -1);
-
- // loop over primitive groups
- int indexCtr = 0;
- for (int i = 0; i < in_primGroups.length; i++) {
- final int numIndices = in_primGroups[i].getNumIndices();
-
- // init remapped group
- remappedGroups[i] = new PrimitiveGroup();
- remappedGroups[i].setType(in_primGroups[i].getType());
- remappedGroups[i].setNumIndices(numIndices);
- remappedGroups[i].setIndices(new int[numIndices]);
-
- for (int j = 0; j < numIndices; j++) {
- final int cachedIndex = indexCache[in_primGroups[i]._getIndices()[j]];
- if (cachedIndex == -1) // we haven't seen this index before
- {
- // point to "last" vertex in VB
- remappedGroups[i]._getIndices()[j] = indexCtr;
-
- // add to index cache, increment
- indexCache[in_primGroups[i]._getIndices()[j]] = indexCtr++;
- } else {
- // we've seen this index before
- remappedGroups[i]._getIndices()[j] = cachedIndex;
- }
- }
- }
- if (remappedVertices != null) {
- remappedVertices.set(indexCache);
- }
-
- return remappedGroups;
- }
-
- @Override
- public void visit(final Spatial spatial) {
- if (spatial instanceof Mesh) {
- final Mesh mesh = (Mesh) spatial;
- final MeshData md = mesh.getMeshData();
- if (md.getTotalPrimitiveCount() < 1 || md.getVertexCount() < 3) {
- return;
- }
- for (final IndexMode mode : md.getIndexModes()) {
- if (mode != IndexMode.Triangles) {
- return;
- }
- }
-
- final int[] indices;
- if (md.getIndices() == null) {
- indices = new int[md.getVertexCount()];
- for (int i = 0; i < indices.length; i++) {
- indices[i] = i;
- }
- } else {
- indices = BufferUtils.getIntArray(md.getIndices());
- }
- PrimitiveGroup[] strips = generateStrips(indices, false);
-
- if (_reorderVertices) {
- final AtomicReference<int[]> newOrder = new AtomicReference<int[]>();
- strips = remapIndices(strips, newOrder, md.getVertexCount());
-
- // ask mesh to apply new vertex order
- mesh.reorderVertexData(newOrder.get());
- }
-
- // construct our new index buffer, modes and counts
- int indexCount = 0, j = 0, count = 0;
- for (final PrimitiveGroup group : strips) {
- if (group.getIndices().length > 0) {
- count++;
- }
- }
- final int[] counts = new int[count];
- final IndexMode[] modes = new IndexMode[count];
- for (final PrimitiveGroup group : strips) {
- indexCount += group.getIndices().length;
- if (group.getIndices().length > 0) {
- modes[j] = group.getType();
- counts[j++] = group.getIndices().length;
- }
- }
- final IndexBufferData<?> newIndices = BufferUtils.createIndexBufferData(indexCount, md.getVertexCount());
- for (final PrimitiveGroup group : strips) {
- final IntBufferData data = new IntBufferData(group.getIndices().length);
- data.getBuffer().put(group.getIndices());
- data.rewind();
- newIndices.put(data);
- }
- newIndices.rewind();
-
- // ask mesh to apply new index data
- mesh.reorderIndices(newIndices, modes, counts);
- }
- }
-}
+/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.model.util.nvtristrip; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import com.ardor3d.renderer.IndexMode; +import com.ardor3d.scenegraph.IndexBufferData; +import com.ardor3d.scenegraph.IntBufferData; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.scenegraph.MeshData; +import com.ardor3d.scenegraph.Spatial; +import com.ardor3d.scenegraph.visitor.Visitor; +import com.ardor3d.util.geom.BufferUtils; +import com.google.common.collect.Lists; + +/** + * Ported from <a href="http://developer.nvidia.com/object/nvtristrip_library.html">NVIDIA's NvTriStrip Library</a> + */ +public class NvTriangleStripper implements Visitor { + /** GeForce1 and 2 cache size */ + public static final int CACHESIZE_GEFORCE1_2 = 16; + + /** GeForce3 cache size */ + public static final int CACHESIZE_GEFORCE3 = 24; + + private int _cacheSize = NvTriangleStripper.CACHESIZE_GEFORCE3; + private int _minStripSize = 0; + private int _restartVal = 0; + private boolean _stitchStrips = true; + private boolean _listsOnly = false; + private boolean _restart = false; + private boolean _reorderVertices = false; + + /** + * For GPUs that support primitive restart, this sets a value as the restart index + * + * Restart is meaningless if strips are not being stitched together, so enabling restart makes NvTriStrip forcing + * stitching. So, you'll get back one strip. + * + * @param restartVal + */ + public void enableRestart(final int restartVal) { + _restart = true; + _restartVal = restartVal; + } + + /** + * For GPUs that support primitive restart, this disables using primitive restart + */ + public void disableRestart() { + _restart = false; + } + + public boolean isRestart() { + return _restart; + } + + /** + * Sets the cache size which the stripfier uses to optimize the data. Controls the length of the generated + * individual strips. This is the "actual" cache size, so 24 for GeForce3 and 16 for GeForce1/2 You may want to play + * around with this number to tweak performance. + * + * @param cacheSize + * (Default value: 24) + */ + public void setCacheSize(final int cacheSize) { + _cacheSize = cacheSize; + } + + public int getCacheSize() { + return _cacheSize; + } + + /** + * boolean to indicate whether to stitch together strips into one huge strip or not. If set to true, you'll get back + * one huge strip stitched together using degenerate triangles. If set to false, you'll get back a large number of + * separate strips. + * + * @param bStitchStrips + * (Default value: true) + */ + public void setStitchStrips(final boolean bStitchStrips) { + _stitchStrips = bStitchStrips; + } + + public boolean isStitchStrips() { + return _stitchStrips; + } + + /** + * + * Sets the minimum acceptable size for a strip, in triangles. All strips generated which are shorter than this will + * be thrown into one big, separate list. + * + * @param minSize + * (Default value: 0) + */ + public void setMinStripSize(final int minSize) { + _minStripSize = minSize; + } + + public int getMinStripSize() { + return _minStripSize; + } + + /** + * If set to true, will return an optimized list, with no strips at all. + * + * @param bListsOnly + * (Default value: false) + */ + public void setListsOnly(final boolean bListsOnly) { + _listsOnly = bListsOnly; + } + + public boolean isListsOnly() { + return _listsOnly; + } + + /** + * If set to true, will call remapIndices after generateStrips. + * + * @param reorder + * (Default value: false) + * @see #remapIndices(PrimitiveGroup[], AtomicReference, int) + */ + public void setReorderVertices(final boolean reorder) { + _reorderVertices = reorder; + } + + public boolean isReorderVertices() { + return _reorderVertices; + } + + /** + * + Returns true if the two triangles defined by firstTri and secondTri are the same The "same" is defined in this + * case as having the same indices with the same winding order + * + * @param firstTri0 + * @param firstTri1 + * @param firstTri2 + * @param secondTri0 + * @param secondTri1 + * @param secondTri2 + * @return + */ + boolean sameTriangle(final int firstTri0, final int firstTri1, final int firstTri2, final int secondTri0, + final int secondTri1, final int secondTri2) { + boolean isSame = false; + + if (firstTri0 == secondTri0) { + if (firstTri1 == secondTri1) { + if (firstTri2 == secondTri2) { + isSame = true; + } + } + } else if (firstTri0 == secondTri1) { + if (firstTri1 == secondTri2) { + if (firstTri2 == secondTri0) { + isSame = true; + } + } + } else if (firstTri0 == secondTri2) { + if (firstTri1 == secondTri0) { + if (firstTri2 == secondTri1) { + isSame = true; + } + } + } + + return isSame; + } + + boolean testTriangle(final int v0, final int v1, final int v2, final List<NvFaceInfo> in_bins[], final int NUMBINS) { + // hash this triangle + boolean isLegit = false; + int ctr = v0 % NUMBINS; + NvFaceInfo face; + for (int k = 0; k < in_bins[ctr].size(); ++k) { + // check triangles in this bin + face = in_bins[ctr].get(k); + if (sameTriangle(face._v0, face._v1, face._v2, v0, v1, v2)) { + isLegit = true; + break; + } + } + if (!isLegit) { + ctr = v1 % NUMBINS; + for (int k = 0; k < in_bins[ctr].size(); ++k) { + face = in_bins[ctr].get(k); + // check triangles in this bin + if (sameTriangle(face._v0, face._v1, face._v2, v0, v1, v2)) { + isLegit = true; + break; + } + } + + if (!isLegit) { + ctr = v2 % NUMBINS; + for (int k = 0; k < in_bins[ctr].size(); ++k) { + face = in_bins[ctr].get(k); + // check triangles in this bin + if (sameTriangle(face._v0, face._v1, face._v2, v0, v1, v2)) { + isLegit = true; + break; + } + } + + } + } + + return isLegit; + } + + /** + * + * @param in_indices + * input index list, the indices you would use to render + * @param validate + * @return array of optimized/stripified PrimitiveGroups + */ + @SuppressWarnings("unchecked") + public PrimitiveGroup[] generateStrips(final int[] in_indices, final boolean validate) { + if (in_indices == null || in_indices.length == 0) { + return new PrimitiveGroup[0]; + } + + int numGroups = 0; + PrimitiveGroup[] primGroups; + + // put data in format that the stripifier likes + final List<Integer> tempIndices = Lists.newArrayList(); + int maxIndex = 0; + for (int i = 0; i < in_indices.length; i++) { + tempIndices.add(in_indices[i]); + if (in_indices[i] > maxIndex) { + maxIndex = in_indices[i]; + } + } + final List<NvStripInfo> tempStrips = Lists.newArrayList(); + final List<NvFaceInfo> tempFaces = Lists.newArrayList(); + + final NvStripifier stripifier = new NvStripifier(); + + // do actual stripification + stripifier.stripify(tempIndices, _cacheSize, _minStripSize, maxIndex, tempStrips, tempFaces); + + // stitch strips together + final List<Integer> stripIndices = Lists.newArrayList(); + int numSeparateStrips = 0; + + if (_listsOnly) { + // if we're outputting only lists, we're done + numGroups = 1; + primGroups = new PrimitiveGroup[numGroups]; + primGroups[0] = new PrimitiveGroup(); + final PrimitiveGroup[] primGroupArray = primGroups; + + // count the total number of indices + int numIndices = 0; + for (int i = 0; i < tempStrips.size(); i++) { + numIndices += tempStrips.get(i)._faces.size() * 3; + } + + // add in the list + numIndices += tempFaces.size() * 3; + + primGroupArray[0].setType(IndexMode.Triangles); + primGroupArray[0].setIndices(new int[numIndices]); + primGroupArray[0].setNumIndices(numIndices); + + // do strips + int indexCtr = 0; + for (final NvStripInfo strip : tempStrips) { + for (final NvFaceInfo face : strip._faces) { + // degenerates are of no use with lists + if (!NvStripifier.isDegenerate(face)) { + primGroupArray[0]._getIndices()[indexCtr++] = face._v0; + primGroupArray[0]._getIndices()[indexCtr++] = face._v1; + primGroupArray[0]._getIndices()[indexCtr++] = face._v2; + } else { + // we've removed a tri, reduce the number of indices + primGroupArray[0].setNumIndices(primGroupArray[0].getNumIndices() - 3); + } + } + } + + // do lists + for (final NvFaceInfo face : tempFaces) { + primGroupArray[0]._getIndices()[indexCtr++] = face._v0; + primGroupArray[0]._getIndices()[indexCtr++] = face._v1; + primGroupArray[0]._getIndices()[indexCtr++] = face._v2; + } + } else { + numSeparateStrips = stripifier.createStrips(tempStrips, stripIndices, _stitchStrips, _restart, _restartVal); + + // if we're stitching strips together, we better get back only one strip from createStrips() + assert _stitchStrips && numSeparateStrips == 1 || !_stitchStrips; + + // convert to output format + numGroups = numSeparateStrips; // for the strips + if (tempFaces.size() != 0) { + numGroups++; + } // we've got a list as well, increment + primGroups = new PrimitiveGroup[numGroups]; + for (int i = 0; i < primGroups.length; i++) { + primGroups[i] = new PrimitiveGroup(); + } + final PrimitiveGroup[] primGroupArray = primGroups; + + // first, the strips + int startingLoc = 0; + for (int stripCtr = 0; stripCtr < numSeparateStrips; stripCtr++) { + int stripLength = 0; + + if (!_stitchStrips) { + int i = startingLoc; + // if we've got multiple strips, we need to figure out the correct length + for (; i < stripIndices.size(); i++) { + if (stripIndices.get(i) == -1) { + break; + } + } + + stripLength = i - startingLoc; + } else { + stripLength = stripIndices.size(); + } + + primGroupArray[stripCtr].setType(IndexMode.TriangleStrip); + primGroupArray[stripCtr].setIndices(new int[stripLength]); + primGroupArray[stripCtr].setNumIndices(stripLength); + + int indexCtr = 0; + for (int i = startingLoc; i < stripLength + startingLoc; i++) { + primGroupArray[stripCtr]._getIndices()[indexCtr++] = stripIndices.get(i); + } + + // we add 1 to account for the -1 separating strips + // this doesn't break the stitched case since we'll exit the loop + startingLoc += stripLength + 1; + } + + // next, the list + if (tempFaces.size() != 0) { + final int faceGroupLoc = numGroups - 1; // the face group is the last one + primGroupArray[faceGroupLoc].setType(IndexMode.Triangles); + primGroupArray[faceGroupLoc].setIndices(new int[tempFaces.size() * 3]); + primGroupArray[faceGroupLoc].setNumIndices(tempFaces.size() * 3); + int indexCtr = 0; + for (final NvFaceInfo face : tempFaces) { + primGroupArray[faceGroupLoc]._getIndices()[indexCtr++] = face._v0; + primGroupArray[faceGroupLoc]._getIndices()[indexCtr++] = face._v1; + primGroupArray[faceGroupLoc]._getIndices()[indexCtr++] = face._v2; + } + } + } + + // validate generated data against input + if (validate) { + final int NUMBINS = 100; + + final List<NvFaceInfo> in_bins[] = new List[NUMBINS]; + for (int i = 0; i < NUMBINS; i++) { + in_bins[i] = Lists.newArrayList(); + } + + // hash input indices on first index + for (int i = 0; i < in_indices.length; i += 3) { + final NvFaceInfo faceInfo = new NvFaceInfo(in_indices[i], in_indices[i + 1], in_indices[i + 2]); + in_bins[in_indices[i] % NUMBINS].add(faceInfo); + } + + for (int i = 0; i < numGroups; ++i) { + switch (primGroups[i].getType()) { + case Triangles: { + for (int j = 0; j < primGroups[i].getNumIndices(); j += 3) { + final int v0 = primGroups[i]._getIndices()[j]; + final int v1 = primGroups[i]._getIndices()[j + 1]; + final int v2 = primGroups[i]._getIndices()[j + 2]; + + // ignore degenerates + if (NvStripifier.isDegenerate(v0, v1, v2)) { + continue; + } + + if (!testTriangle(v0, v1, v2, in_bins, NUMBINS)) { + throw new IllegalStateException("failed validation"); + } + } + break; + } + + case TriangleStrip: { + boolean flip = false; + for (int j = 2; j < primGroups[i].getNumIndices(); ++j) { + final int v0 = primGroups[i]._getIndices()[j - 2]; + int v1 = primGroups[i]._getIndices()[j - 1]; + int v2 = primGroups[i]._getIndices()[j]; + + if (flip) { + // swap v1 and v2 + final int swap = v1; + v1 = v2; + v2 = swap; + } + + // ignore degenerates + if (NvStripifier.isDegenerate(v0, v1, v2)) { + flip = !flip; + continue; + } + + if (!testTriangle(v0, v1, v2, in_bins, NUMBINS)) { + throw new IllegalStateException("failed validation"); + } + + flip = !flip; + } + break; + } + + case TriangleFan: + default: + break; + } + } + + } + + return primGroups; + } + + /** + * Function to remap your indices to improve spatial locality in your vertex buffer. + * + * Note that you must reorder your vertex buffer according to the remapping handed back to you. + * + * Credit goes to the MS Xbox crew for the idea for this interface. + * + * @param in_primGroups + * array of PrimitiveGroups you want remapped + * @param numVerts + * number of vertices in your vertex buffer, also can be thought of as the range of acceptable values for + * indices in your primitive groups. + * @return index remap. old index is key into array, value there is the old location for the vertex. -1 means vertex + * was never referenced + */ + public PrimitiveGroup[] remapIndices(final PrimitiveGroup[] in_primGroups, + final AtomicReference<int[]> remappedVertices, final int numVerts) { + final PrimitiveGroup[] remappedGroups = new PrimitiveGroup[in_primGroups.length]; + + // caches oldIndex --> newIndex conversion + final int[] indexCache = new int[numVerts]; + Arrays.fill(indexCache, -1); + + // loop over primitive groups + int indexCtr = 0; + for (int i = 0; i < in_primGroups.length; i++) { + final int numIndices = in_primGroups[i].getNumIndices(); + + // init remapped group + remappedGroups[i] = new PrimitiveGroup(); + remappedGroups[i].setType(in_primGroups[i].getType()); + remappedGroups[i].setNumIndices(numIndices); + remappedGroups[i].setIndices(new int[numIndices]); + + for (int j = 0; j < numIndices; j++) { + final int cachedIndex = indexCache[in_primGroups[i]._getIndices()[j]]; + if (cachedIndex == -1) // we haven't seen this index before + { + // point to "last" vertex in VB + remappedGroups[i]._getIndices()[j] = indexCtr; + + // add to index cache, increment + indexCache[in_primGroups[i]._getIndices()[j]] = indexCtr++; + } else { + // we've seen this index before + remappedGroups[i]._getIndices()[j] = cachedIndex; + } + } + } + if (remappedVertices != null) { + remappedVertices.set(indexCache); + } + + return remappedGroups; + } + + @Override + public void visit(final Spatial spatial) { + if (spatial instanceof Mesh) { + final Mesh mesh = (Mesh) spatial; + final MeshData md = mesh.getMeshData(); + if (md.getTotalPrimitiveCount() < 1 || md.getVertexCount() < 3) { + return; + } + for (final IndexMode mode : md.getIndexModes()) { + if (mode != IndexMode.Triangles) { + return; + } + } + + final int[] indices; + if (md.getIndices() == null) { + indices = new int[md.getVertexCount()]; + for (int i = 0; i < indices.length; i++) { + indices[i] = i; + } + } else { + indices = BufferUtils.getIntArray(md.getIndices()); + } + PrimitiveGroup[] strips = generateStrips(indices, false); + + if (_reorderVertices) { + final AtomicReference<int[]> newOrder = new AtomicReference<int[]>(); + strips = remapIndices(strips, newOrder, md.getVertexCount()); + + // ask mesh to apply new vertex order + mesh.reorderVertexData(newOrder.get()); + } + + // construct our new index buffer, modes and counts + int indexCount = 0, j = 0, count = 0; + for (final PrimitiveGroup group : strips) { + if (group.getIndices().length > 0) { + count++; + } + } + final int[] counts = new int[count]; + final IndexMode[] modes = new IndexMode[count]; + for (final PrimitiveGroup group : strips) { + indexCount += group.getIndices().length; + if (group.getIndices().length > 0) { + modes[j] = group.getType(); + counts[j++] = group.getIndices().length; + } + } + final IndexBufferData<?> newIndices = BufferUtils.createIndexBufferData(indexCount, md.getVertexCount()); + for (final PrimitiveGroup group : strips) { + final IntBufferData data = new IntBufferData(group.getIndices().length); + data.getBuffer().put(group.getIndices()); + data.rewind(); + newIndices.put(data); + } + newIndices.rewind(); + + // ask mesh to apply new index data + mesh.reorderIndices(newIndices, modes, counts); + } + } +} diff --git a/ardor3d-extras/src/main/java/com/ardor3d/extension/model/util/nvtristrip/PrimitiveGroup.java b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/util/nvtristrip/PrimitiveGroup.java index 01eaff8..4ba92c2 100644 --- a/ardor3d-extras/src/main/java/com/ardor3d/extension/model/util/nvtristrip/PrimitiveGroup.java +++ b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/util/nvtristrip/PrimitiveGroup.java @@ -1,62 +1,62 @@ -/**
- * Copyright (c) 2008-2012 Ardor Labs, Inc.
- *
- * This file is part of Ardor3D.
- *
- * Ardor3D is free software: you can redistribute it and/or modify it
- * under the terms of its license which may be found in the accompanying
- * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
- */
-
-package com.ardor3d.extension.model.util.nvtristrip;
-
-import com.ardor3d.renderer.IndexMode;
-
-public class PrimitiveGroup {
-
- private IndexMode _type;
- private int[] _indices;
- private int _numIndices;
-
- PrimitiveGroup() {
- setType(IndexMode.Triangles);
- setIndices(null);
- setNumIndices(0);
- }
-
- int[] _getIndices() {
- return _indices;
- }
-
- public int[] getIndices() {
- if (_indices.length == _numIndices) {
- return _indices;
- }
-
- // crop it down to actual size...
- final int[] realIndices = new int[_numIndices];
- System.arraycopy(_indices, 0, realIndices, 0, _numIndices);
- _indices = realIndices;
- return _indices;
- }
-
- int getNumIndices() {
- return _numIndices;
- }
-
- public IndexMode getType() {
- return _type;
- }
-
- void setType(final IndexMode type) {
- _type = type;
- }
-
- void setIndices(final int[] indices) {
- _indices = indices;
- }
-
- void setNumIndices(final int numIndices) {
- this._numIndices = numIndices;
- }
-}
+/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.model.util.nvtristrip; + +import com.ardor3d.renderer.IndexMode; + +public class PrimitiveGroup { + + private IndexMode _type; + private int[] _indices; + private int _numIndices; + + PrimitiveGroup() { + setType(IndexMode.Triangles); + setIndices(null); + setNumIndices(0); + } + + int[] _getIndices() { + return _indices; + } + + public int[] getIndices() { + if (_indices.length == _numIndices) { + return _indices; + } + + // crop it down to actual size... + final int[] realIndices = new int[_numIndices]; + System.arraycopy(_indices, 0, realIndices, 0, _numIndices); + _indices = realIndices; + return _indices; + } + + int getNumIndices() { + return _numIndices; + } + + public IndexMode getType() { + return _type; + } + + void setType(final IndexMode type) { + _type = type; + } + + void setIndices(final int[] indices) { + _indices = indices; + } + + void setNumIndices(final int numIndices) { + this._numIndices = numIndices; + } +} diff --git a/ardor3d-extras/src/main/java/com/ardor3d/extension/model/util/nvtristrip/VertexCache.java b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/util/nvtristrip/VertexCache.java index cf90e5f..ba93840 100644 --- a/ardor3d-extras/src/main/java/com/ardor3d/extension/model/util/nvtristrip/VertexCache.java +++ b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/util/nvtristrip/VertexCache.java @@ -1,81 +1,81 @@ -/**
- * Copyright (c) 2008-2012 Ardor Labs, Inc.
- *
- * This file is part of Ardor3D.
- *
- * Ardor3D is free software: you can redistribute it and/or modify it
- * under the terms of its license which may be found in the accompanying
- * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
- */
-
-package com.ardor3d.extension.model.util.nvtristrip;
-
-import java.util.Arrays;
-
-/**
- * Ported from <a href="http://developer.nvidia.com/object/nvtristrip_library.html">NVIDIA's NvTriStrip Library</a>
- */
-final class VertexCache {
-
- private final int[] _entries;
- private final int _numEntries;
-
- public VertexCache(final int size) {
- _numEntries = size;
-
- _entries = new int[_numEntries];
-
- for (int i = 0; i < _numEntries; i++) {
- _entries[i] = -1;
- }
- }
-
- public VertexCache() {
- this(16);
- }
-
- public boolean inCache(final int entry) {
- boolean returnVal = false;
- for (int i = 0; i < _numEntries; i++) {
- if (_entries[i] == entry) {
- returnVal = true;
- break;
- }
- }
-
- return returnVal;
- }
-
- public int addEntry(final int entry) {
- int removed;
-
- removed = _entries[_numEntries - 1];
-
- // push everything right one
- for (int i = _numEntries - 2; i >= 0; i--) {
- _entries[i + 1] = _entries[i];
- }
-
- _entries[0] = entry;
-
- return removed;
- }
-
- public void clear() {
- Arrays.fill(_entries, -1);
- }
-
- public void copy(final VertexCache inVcache) {
- for (int i = 0; i < _numEntries; i++) {
- inVcache.set(i, _entries[i]);
- }
- }
-
- public int at(final int index) {
- return _entries[index];
- }
-
- public void set(final int index, final int value) {
- _entries[index] = value;
- }
-}
+/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.model.util.nvtristrip; + +import java.util.Arrays; + +/** + * Ported from <a href="http://developer.nvidia.com/object/nvtristrip_library.html">NVIDIA's NvTriStrip Library</a> + */ +final class VertexCache { + + private final int[] _entries; + private final int _numEntries; + + public VertexCache(final int size) { + _numEntries = size; + + _entries = new int[_numEntries]; + + for (int i = 0; i < _numEntries; i++) { + _entries[i] = -1; + } + } + + public VertexCache() { + this(16); + } + + public boolean inCache(final int entry) { + boolean returnVal = false; + for (int i = 0; i < _numEntries; i++) { + if (_entries[i] == entry) { + returnVal = true; + break; + } + } + + return returnVal; + } + + public int addEntry(final int entry) { + int removed; + + removed = _entries[_numEntries - 1]; + + // push everything right one + for (int i = _numEntries - 2; i >= 0; i--) { + _entries[i + 1] = _entries[i]; + } + + _entries[0] = entry; + + return removed; + } + + public void clear() { + Arrays.fill(_entries, -1); + } + + public void copy(final VertexCache inVcache) { + for (int i = 0; i < _numEntries; i++) { + inVcache.set(i, _entries[i]); + } + } + + public int at(final int index) { + return _entries[index]; + } + + public void set(final int index, final int value) { + _entries[index] = value; + } +} |