Ardor3D Wiki
This is the first attempt of supporting the MD2 format in Ardor3D. It has been tested and works reliably.
/**
* Copyright (c) 2008-20010 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;
import java.io.IOException;
import java.io.Serializable;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.logging.Logger;
import com.ardor3d.scenegraph.FloatBufferData;
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;
/**
* 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.
*
* @author Jack Lindamood, kevglass (parts), hevee (blend time), Julien Gouesse (port to Ardor3D)
* @version $Id$
*/
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;
/**
* Used with SmoothTransform to signal it is doing a smooth transform
*/
transient private boolean _isSmooth;
/**
* 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 _updatePerFrame;
/**
* 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);
_movingForward = true;
setMinTime(0);
setMaxTime(0);
_updatePerFrame = true;
}
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().getIndexBuffer() != null) {
IntBuffer dcInds = (IntBuffer) dataCopy.getMeshData().getIndexBuffer();
if (dcInds != null) {
dcInds.clear();
}
final IntBuffer mmInds = (IntBuffer) _morphMesh.getMeshData().getIndexBuffer();
mmInds.clear();
if (dcInds == null || dcInds.capacity() != mmInds.capacity()) {
dcInds = BufferUtils.createIntBuffer(mmInds.capacity());
dcInds.clear();
dataCopy.getMeshData().setIndexBuffer(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 = _morphMesh.getMeshData().getTextureCoords(0) != null ? _morphMesh.getMeshData()
.getTextureCoords(0).getBuffer() : null;
final FloatBuffer colors = _morphMesh.getMeshData().getColorBuffer();
final FloatBuffer oldverts = oldShape.getMeshData().getVertexBuffer();
final FloatBuffer oldnorms = oldShape.getMeshData().getNormalBuffer();
final FloatBuffer oldtexts = oldShape.getMeshData().getTextureCoords(0) != null ? oldShape.getMeshData()
.getTextureCoords(0).getBuffer() : null;
final FloatBuffer oldcolors = oldShape.getMeshData().getColorBuffer();
final FloatBuffer newverts = newShape.getMeshData().getVertexBuffer();
final FloatBuffer newnorms = newShape.getMeshData().getNormalBuffer();
final FloatBuffer newtexts = newShape.getMeshData().getTextureCoords(0) != null ? newShape.getMeshData()
.getTextureCoords(0).getBuffer() : null;
final FloatBuffer newcolors = newShape.getMeshData().getColorBuffer();
final int vertQuantity = verts.capacity() / 3;
if (verts == null || oldverts == null || newverts == null) {
return;
}
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 (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 (_updatePerFrame) {
_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 setModelUpdate(final boolean update) {
_updatePerFrame = update;
}
/**
* Returns true if the model's bounding volume is being updated every frame.
*
* @return True if bounding volume is updating.
*/
public boolean getModelUpdate() {
return _updatePerFrame;
}
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 Serializable, Savable {
private static final long serialVersionUID = 1L;
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;
}
private void writeObject(final java.io.ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
if (_isSmooth) {
out.writeObject(_prevKeyframes);
} else {
out.writeObject(_keyframes);
}
}
public Mesh getMorphMesh() {
return _morphMesh;
}
@SuppressWarnings("unchecked")
@Override
public Class<? extends KeyframeController> getClassTag() {
return this.getClass();
}
@Override
public void read(final InputCapsule capsule) throws IOException {
super.read(capsule);
_updatePerFrame = capsule.readBoolean("updatePerFrame", 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(_updatePerFrame, "updatePerFrame", true);
capsule.write(_morphMesh, "morphMesh", null);
capsule.writeSavableList(_keyframes, "keyframes", new ArrayList<PointInTime>());
}
}
* Copyright (c) 2008-20010 Ardor Labs, Inc.
package com.ardor3d.extension.model.md2;
import com.ardor3d.math.Vector2;
public class Md2DataStore {
private Vector2[] _texCoords;
private Md2Face[] _triangles;
private Md2Frame[] _frames;
private int _skinWidth;
private int _skinHeight;
public Md2DataStore() {}
public Vector2 getTexCoord(final int index) {
return _texCoords[index];
}
public int getNumTexCoords() {
return _texCoords == null ? 0 : _texCoords.length;
}
public void setTexCoord(final int index, final Vector2 texCoord) {
_texCoords[index] = texCoord;
}
public void setTexCoords(final Vector2[] texCoords) {
_texCoords = texCoords;
}
public Md2Face getTriangle(final int index) {
return _triangles[index];
}
public int getNumTriangles() {
return _triangles == null ? 0 : _triangles.length;
}
public void setTriangle(final int index, final Md2Face triangle) {
_triangles[index] = triangle;
}
public void setTriangles(final Md2Face[] triangles) {
_triangles = triangles;
}
public Md2Frame getFrame(final int index) {
return _frames[index];
}
public int getNumFrames() {
return _frames == null ? 0 : _frames.length;
}
public void setFrame(final int index, final Md2Frame frame) {
_frames[index] = frame;
}
public void setFrames(final Md2Frame[] frames) {
_frames = frames;
}
public int getSkinWidth() {
return _skinWidth;
}
public void setSkinWidth(final int skinWidth) {
_skinWidth = skinWidth;
}
public int getSkinHeight() {
return _skinHeight;
}
public void setSkinHeight(final int skinHeight) {
_skinHeight = skinHeight;
}
}
/**
* Copyright (c) 2008-20010 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;
final class Md2Face {
private int[] _vertexIndices = new int[3]; // short
private int[] _texCoordIndices = new int[3]; // short
Md2Face(final int vertexIndex1, final int vertexIndex2, final int vertexIndex3, final int texIndex1,
final int texIndex2, final int texIndex3) {
_vertexIndices = new int[] { vertexIndex1, vertexIndex2, vertexIndex3 };
_texCoordIndices = new int[] { texIndex1, texIndex2, texIndex3 };
}
int getVertexIndex(final int index) {
return _vertexIndices[index];
}
int getTexCoordIndex(final int index) {
return _texCoordIndices[index];
}
}
* Copyright (c) 2008-20010 Ardor Labs, Inc.
package com.ardor3d.extension.model.md2;
final class Md2Frame {
private String _name; // char [16]
private final TrianglePointCoordinatesPair[] _trianglePointCoordsPairs;
Md2Frame(final int verticesCount) {
_trianglePointCoordsPairs = new TrianglePointCoordinatesPair[verticesCount];
for (int index = 0; index < verticesCount; index++) {
_trianglePointCoordsPairs[index] = new TrianglePointCoordinatesPair();
}
}
String getName() {
return _name;
}
int getNumVertices() {
return _trianglePointCoordsPairs.length;
}
void setName(final String name) {
_name = name;
}
TrianglePointCoordinatesPair getTrianglePointCoordsPair(final int index) {
return _trianglePointCoordsPairs[index];
}
}
package com.ardor3d.extension.model.md2;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import com.ardor3d.extension.model.KeyframeController;
import com.ardor3d.math.Vector2;
import com.ardor3d.math.Vector3;
import com.ardor3d.scenegraph.FloatBufferData;
import com.ardor3d.scenegraph.Mesh;
import com.ardor3d.scenegraph.Spatial;
import com.ardor3d.util.geom.BufferUtils;
/**
* 95% a Copy/paste of the .md2 loader by Mark Powell modified for efficiency (use of empty Mesh) and VertexController
* as well as a few tiny adjustments here and there on memory.
*
* @author Mark Powell
* @author Jack Lindamood
*/
class Md2GeometryStore {
private final Md2DataStore _md2DataStore;
private final Mesh _mesh;
/**
* Loads an MD2 model. The corresponding Mesh objects are created and attached to the model. Each key
* frame is then loaded and assigned to a KeyframeController. MD2 does not keep track of it's own
* texture or material settings, so the user is responsible for setting these.
*
* @param input
* the InputStream of the file to load.
*/
public Md2GeometryStore() {
_mesh = new Mesh("MD2 mesh" + new Random().nextInt());
_md2DataStore = new Md2DataStore();
}
public Md2DataStore getDataStore() {
return _md2DataStore;
}
void cleanup() {
// should we destroy the data store?
}
public Spatial getScenegraph() {
return _mesh;
}
/**
* turns the MD2 data into a scene graph object and attach to the mesh
*/
void commitObjects() {
final Mesh[] frameMeshes = new Mesh[_md2DataStore.getNumFrames()];
final List<PointCoordinatesIndicesPair> vectorTexcoords = new ArrayList<PointCoordinatesIndicesPair>();
final KeyframeController<Mesh> controller = new KeyframeController<Mesh>();
for (int i = 0; i < _md2DataStore.getNumFrames(); i++) {
final int numOfVerts = _md2DataStore.getFrame(i).getNumVertices();
final int numTexVertex = _md2DataStore.getNumTexCoords();
if (i != 0) {
frameMeshes[i] = new Mesh();
} else {
frameMeshes[i] = _mesh;
}
final Vector3[] uniqueVerts = new Vector3[numOfVerts];
final Vector3[] uniqueNorms = new Vector3[numOfVerts];
final Vector2[] texVerts = new Vector2[numTexVertex];
// assign a vector array for the mesh.
for (int j = 0; j < numOfVerts; j++) {
if (i != 0) {
uniqueVerts[j] = getDataStore().getFrame(i).getTrianglePointCoordsPair(j).getVertex();
uniqueNorms[j] = getDataStore().getFrame(i).getTrianglePointCoordsPair(j).getNormal();
} else {
uniqueVerts[j] = new Vector3(getDataStore().getFrame(i).getTrianglePointCoordsPair(j).getVertex());
uniqueNorms[j] = new Vector3(getDataStore().getFrame(i).getTrianglePointCoordsPair(j).getNormal());
}
}
if (i == 0) {
// texture coordinates.
for (int j = 0; j < numTexVertex; j++) {
texVerts[j] = new Vector2();
texVerts[j].setX(getDataStore().getTexCoord(j).getX() / getDataStore().getSkinWidth());
texVerts[j].setY(1 - getDataStore().getTexCoord(j).getY() / getDataStore().getSkinHeight());
}
// collect all used combinations of vertices and texture coordinates
if (numTexVertex != 0) {
for (int j = 0; j < _md2DataStore.getNumTriangles(); j++) {
for (int k = 0; k < 3; k++) {
final PointCoordinatesIndicesPair l_tex = new PointCoordinatesIndicesPair(getDataStore()
.getTriangle(j).getVertexIndex(k), getDataStore().getTriangle(j)
.getTexCoordIndex(k));
if (!vectorTexcoords.contains(l_tex)) {
vectorTexcoords.add(l_tex);
}
}
}
}
// build indices
final List<Integer> indices = new ArrayList<Integer>();
for (int j = 0; j < _md2DataStore.getNumTriangles(); j++) {
for (int k = 0; k < 3; k++) {
for (int i1 = 0; i1 < vectorTexcoords.size(); i1++) {
final PointCoordinatesIndicesPair vectorTexcoord = vectorTexcoords.get(i1);
if (vectorTexcoord.matches(getDataStore().getTriangle(j), k)) {
indices.add(i1);
break;
}
}
}
}
final int[] indexArray = new int[indices.size()];
for (int x = 0; x < indexArray.length; x++) {
indexArray[x] = indices.get(x).intValue();
}
frameMeshes[0].getMeshData().setIndexBuffer(BufferUtils.createIntBuffer(indexArray));
final Vector2[] extractedTexCoords = extractTexCoords(vectorTexcoords, texVerts, indices.size());
FloatBufferData extractedTexCoordsBufferData;
if (extractedTexCoords == null) {
extractedTexCoordsBufferData = null;
} else {
final FloatBuffer extractedTexCoordsBuffer = BufferUtils
.createFloatBuffer(extractedTexCoords.length * 2);
for (final Vector2 extractedTexCoord : extractedTexCoords) {
if (extractedTexCoord != null) {
extractedTexCoordsBuffer.put(extractedTexCoord.getXf());
extractedTexCoordsBuffer.put(extractedTexCoord.getYf());
} else {
extractedTexCoordsBuffer.put(0);
extractedTexCoordsBuffer.put(0);
}
}
extractedTexCoordsBuffer.rewind();
extractedTexCoordsBufferData = new FloatBufferData(extractedTexCoordsBuffer, 2);
}
frameMeshes[0].getMeshData().setTextureCoords(extractedTexCoordsBufferData, 0);
controller.setMorphingMesh(frameMeshes[0]);
} // End if (i==0)
final Vector3[] allVerts = extractVertices(vectorTexcoords, uniqueVerts);
frameMeshes[i].getMeshData().setVertexBuffer(BufferUtils.createFloatBuffer(allVerts));
final Vector3[] allNorms = extractNormals(vectorTexcoords, uniqueNorms);
frameMeshes[i].getMeshData().setNormalBuffer(BufferUtils.createFloatBuffer(allNorms));
controller.setKeyframe(i, frameMeshes[i]);
}
_mesh.addController(controller);
}
private Vector3[] extractVertices(final List<PointCoordinatesIndicesPair> vectorTexcoords,
final Vector3[] uniqueVerts) {
final List<Vector3> ret = new ArrayList<Vector3>();
for (int i = 0; i < vectorTexcoords.size(); i++) {
final PointCoordinatesIndicesPair vectorTex = vectorTexcoords.get(i);
final Vector3 vert = uniqueVerts[vectorTex.getVertexIndex()];
ret.add(vert);
}
return ret.toArray(new Vector3[ret.size()]);
}
private Vector3[] extractNormals(final List<PointCoordinatesIndicesPair> vectorTexcoords,
final Vector3[] uniqueNormals) {
final List<Vector3> ret = new ArrayList<Vector3>();
for (int i = 0; i < vectorTexcoords.size(); i++) {
final PointCoordinatesIndicesPair vectorTex = vectorTexcoords.get(i);
final Vector3 norm = uniqueNormals[vectorTex.getVertexIndex()];
ret.add(norm);
}
return ret.toArray(new Vector3[ret.size()]);
}
private Vector2[] extractTexCoords(final List<PointCoordinatesIndicesPair> vectorTexcoords,
final Vector2[] texVerts, final int coordCount) {
final Vector2[] ret = new Vector2[coordCount];
int count = 0;
for (int i = 0; i < vectorTexcoords.size(); i++) {
final PointCoordinatesIndicesPair vectorTex = vectorTexcoords.get(i);
final Vector2 vert = texVerts[vectorTex.getTexCoordIndex()];
ret[count++] = vert;
}
return ret;
};
}
* Copyright (c) 2008-20010 Ardor Labs, Inc.
package com.ardor3d.extension.model.md2;
final class Md2Header {
/**
* identifier of the file
*/
private final int _magic;
/**
* version number of the file (must be 8)
*/
private final int _version;
/**
* skin width in pixels
*/
private final int _skinWidth;
/**
* skin height in pixels
*/
private final int _skinHeight;
/**
* size in bytes the frames are
*/
private final int _frameSize;
/**
* number of skins associated with the model
*/
private final int _numSkins;
/**
* number of vertices (constant for each frame)
*/
private final int _numVertices;
/**
* number of texture coordinates
*/
private final int _numTexCoords;
/**
* number of faces (polygons)
*/
private final int _numTriangles;
/**
* number of gl commands
*/
private final int _numGlCommands;
/**
* number of animation frames
*/
private final int _numFrames;
/**
* offset in the file for the skin data
*/
private final int _offsetSkins;
/**
* offset in the file for the texture
*/
private final int _offsetTexCoords;
// data
/**
* offset in the file for the face data
*/
private final int _offsetTriangles;
/**
* offset in the file for the frames data
*/
private final int _offsetFrames;
/**
* offset in the file for the gl commands data
*/
private final int _offsetGlCommands;
/**
* end of the file offset
*/
private 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) {
_magic = magic;
_version = version;
_skinWidth = skinWidth;
_skinHeight = skinHeight;
_frameSize = frameSize;
_numSkins = numSkins;
_numVertices = numVertices;
_numTexCoords = numTexCoords;
_numTriangles = numTriangles;
_numGlCommands = numGlCommands;
_numFrames = numFrames;
_offsetSkins = offsetSkins;
_offsetTexCoords = offsetTexCoords;
_offsetTriangles = offsetTriangles;
_offsetFrames = offsetFrames;
_offsetGlCommands = offsetGlCommands;
_offsetEnd = offsetEnd;
}
int getMagic() {
return _magic;
}
int getVersion() {
return _version;
}
int getSkinWidth() {
return _skinWidth;
}
int getSkinHeight() {
return _skinHeight;
}
int getFrameSize() {
return _frameSize;
}
int getNumSkins() {
return _numSkins;
}
int getNumVertices() {
return _numVertices;
}
int getNumTexCoords() {
return _numTexCoords;
}
int getNumTriangles() {
return _numTriangles;
}
int getNumGlCommands() {
return _numGlCommands;
}
int getNumFrames() {
return _numFrames;
}
int getOffsetSkins() {
return _offsetSkins;
}
int getOffsetTexCoords() {
return _offsetTexCoords;
}
int getOffsetTriangles() {
return _offsetTriangles;
}
int getOffsetFrames() {
return _offsetFrames;
}
int getOffsetGlCommands() {
return _offsetGlCommands;
}
int getOffsetEnd() {
return _offsetEnd;
}
}
/**
* Copyright (c) 2008-20010 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.logging.Logger;
import com.ardor3d.util.LittleEndianRandomAccessDataInput;
import com.ardor3d.math.Vector2;
import com.ardor3d.math.Vector3;
import com.ardor3d.util.Ardor3dException;
import com.ardor3d.util.resource.ResourceLocator;
import com.ardor3d.util.resource.ResourceLocatorTool;
import com.ardor3d.util.resource.ResourceSource;
/**
* Started Date: Jun 14, 2004<br>
* <br>
* This class converts a .md2 file to Ardor3D's binary format.
*
* @author Jack Lindamood, Julien Gouesse (port to Ardor3D)
*/
public class Md2Importer {
private ResourceLocator _modelLocator;
static final Logger logger = Logger.getLogger(Md2Importer.class.getName());
public Md2Importer setModelLocator(final ResourceLocator locator) {
_modelLocator = locator;
return this;
}
/**
* Reads a MD2 file from the given resource
*
* @param resource
* the name of the resource to find.
* @return an Md2GeometryStore data object containing the scene and other useful elements.
*/
public Md2GeometryStore load(final ResourceSource resource) {
InputStream md2Stream = null;
try {
md2Stream = resource.openStream();
if (md2Stream == null) {
throw new NullPointerException("Unable to load null streams");
}
final Md2GeometryStore store = new Md2GeometryStore();
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());
if (header.getVersion() != 8) {
throw new Ardor3dException("Invalid file format (Version not 8)!");
}
// kludge: copy a part of the header in the data store because commitObjects has no access to the header
store.getDataStore().setSkinWidth(header.getSkinWidth());
store.getDataStore().setSkinHeight(header.getSkinHeight());
// parse the body (containing the mesh data)
final String[] skins = new String[header.getNumSkins()];
store.getDataStore().setTexCoords(new Vector2[header.getNumTexCoords()]);
store.getDataStore().setTriangles(new Md2Face[header.getNumTriangles()]);
store.getDataStore().setFrames(new Md2Frame[header.getNumFrames()]);
// start with skins. Move the file pointer to the correct position.
bis.seek(header.getOffsetSkins());
// Read in each skin for this model
for (int j = 0; j < header.getNumSkins(); j++) {
skins[j] = bis.readString(64);
}
// Now read in texture coordinates.
bis.seek(header.getOffsetTexCoords());
for (int j = 0; j < header.getNumTexCoords(); j++) {
store.getDataStore().setTexCoord(j, new Vector2(bis.readShort(), bis.readShort()));
}
// read the vertex data.
bis.seek(header.getOffsetTriangles());
for (int j = 0; j < header.getNumTriangles(); j++) {
store.getDataStore().setTriangle(
j,
new Md2Face(bis.readShort(), bis.readShort(), bis.readShort(), bis.readShort(),
bis.readShort(), bis.readShort()));
}
bis.seek(header.getOffsetFrames());
// Each keyframe has the same type of data, so read each
// keyframe one at a time.
for (int i = 0; i < header.getNumFrames(); i++) {
final VectorKeyframe frame = new VectorKeyframe(bis.readFloat(), bis.readFloat(), bis.readFloat(), bis
.readFloat(), bis.readFloat(), bis.readFloat(), bis.readString(16));
store.getDataStore().setFrame(i, new Md2Frame(header.getNumVertices()));
final Vector3[] aliasVertices = new Vector3[header.getNumVertices()];
final int[] aliasLightNormals = new int[header.getNumVertices()];
// Read in the first frame of animation
for (int j = 0; j < header.getNumVertices(); j++) {
aliasVertices[j] = new Vector3(bis.readUnsignedByte(), bis.readUnsignedByte(), bis.readUnsignedByte());
aliasLightNormals[j] = bis.readUnsignedByte();
}
// Copy the name of the animation to our frames array
store.getDataStore().getFrame(i).setName(frame.getName());
TrianglePointCoordinatesPair triangleCoordsPair;
for (int j = 0; j < header.getNumVertices(); j++) {
triangleCoordsPair = store.getDataStore().getFrame(i).getTrianglePointCoordsPair(j);
triangleCoordsPair.getVertex().setX(
aliasVertices[j].getX() * frame.getScaleX() + frame.getTranslateX());
// FIXME: explain to me why Y and Z are inverted
triangleCoordsPair.getVertex().setZ(
-1 * (aliasVertices[j].getY() * frame.getScaleY() + frame.getTranslateY()));
triangleCoordsPair.getVertex().setY(
aliasVertices[j].getZ() * frame.getScaleZ() + frame.getTranslateZ());
if (aliasLightNormals[j] < Md2Importer.norms.length) {
triangleCoordsPair.getNormal().setX(Md2Importer.norms[aliasLightNormals[j]][0]);
triangleCoordsPair.getNormal().setY(Md2Importer.norms[aliasLightNormals[j]][2]);
triangleCoordsPair.getNormal().setZ(-Md2Importer.norms[aliasLightNormals[j]][1]);
} else {
triangleCoordsPair.getNormal().set(0, 1, 0); // DEFAULT?
Md2Importer.logger.warning("Referenced an invalid normal: " + aliasLightNormals[j]);
}
}
}
// TODO: Read OPENGL commands here...
bis.seek(header.getOffsetGlCommands());
store.commitObjects();
store.cleanup();
return store;
} catch (final Exception e) {
throw new Error("Unable to load md2 resource from URL: " + resource, e);
}
}
/**
* 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 Md2GeometryStore 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);
}
private static final float[][] norms = { { -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 } };
}
* Copyright (c) 2008-20010 Ardor Labs, Inc.
package com.ardor3d.extension.model.md2;
final class PointCoordinatesIndicesPair {
private final int _vertexIndex;
private final int _texCoordIndex;
PointCoordinatesIndicesPair(final int vIndex, final int tIndex) {
_vertexIndex = vIndex;
_texCoordIndex = tIndex;
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final PointCoordinatesIndicesPair that = (PointCoordinatesIndicesPair) o;
if (_texCoordIndex != that._texCoordIndex) {
return false;
}
if (_vertexIndex != that._vertexIndex) {
return false;
}
return true;
}
@Override
public int hashCode() {
int l_result;
l_result = _vertexIndex;
l_result = 31 * l_result + _texCoordIndex;
return l_result;
}
public boolean matches(final Md2Face face, final int faceVert) {
return face.getVertexIndex(faceVert) == _vertexIndex && face.getTexCoordIndex(faceVert) == _texCoordIndex;
}
int getVertexIndex() {
return _vertexIndex;
}
int getTexCoordIndex() {
return _texCoordIndex;
}
}
/**
* Copyright (c) 2008-20010 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;
/**
* contains the vertex coordinates and the texture coordinates of a point composing a triangle
*/
final class TrianglePointCoordinatesPair {
private final Vector3 _vertex;
private final Vector3 _normal;
TrianglePointCoordinatesPair() {
_vertex = new Vector3();
_normal = new Vector3();
}
Vector3 getVertex() {
return _vertex;
}
Vector3 getNormal() {
return _normal;
}
}
* Copyright (c) 2008-20010 Ardor Labs, Inc.
package com.ardor3d.extension.model.md2;
import com.ardor3d.math.Vector3;
final class VectorKeyframe {
private final Vector3 _scale;
private final Vector3 _translate;
private final String _name;
VectorKeyframe(final float scaleX, final float scaleY, final float scaleZ, final float translateX,
final float translateY, final float translateZ, final String name) {
_scale = new Vector3(scaleX, scaleY, scaleZ);
_translate = new Vector3(translateX, translateY, translateZ);
_name = name;
}
double getScaleX() {
return _scale.getX();
}
double getScaleY() {
return _scale.getY();
}
double getScaleZ() {
return _scale.getZ();
}
double getTranslateX() {
return _translate.getX();
}
double getTranslateY() {
return _translate.getY();
}
double getTranslateZ() {
return _translate.getZ();
}
String getName() {
return _name;
}
}
/**
* Copyright (c) 2008-2010 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.example.pipeline;
import com.ardor3d.example.ExampleBase;
import com.ardor3d.example.Purpose;
import com.ardor3d.extension.model.md2.Md2GeometryStore;
import com.ardor3d.extension.model.md2.Md2Importer;
import com.ardor3d.math.Vector3;
/**
* Simplest example of loading a Wavefront OBJ model.
*/
@Purpose(htmlDescriptionKey = "com.ardor3d.example.pipeline.SimpleMd2Example", //
thumbnailPath = "/com/ardor3d/example/media/thumbnails/pipeline_SimpleObjExample.jpg", //
maxHeapMemory = 64)
public class SimpleMd2Example extends ExampleBase {
public static void main(final String[] args) {
ExampleBase.start(SimpleMd2Example.class);
}
@Override
protected void initExample() {
_canvas.setTitle("Ardor3D - Simple Md2 Example");
_canvas.getCanvasRenderer().getCamera().setLocation(new Vector3(0, 5, 20));
// Load the scene
final long time = System.currentTimeMillis();
final Md2Importer importer = new Md2Importer();
/*
* try { importer.setTextureLocator(new SimpleResourceLocator(ExampleBase.class.getClassLoader().getResource(
* "com/ardor3d/example/media/models/obj/"))); } catch (final URISyntaxException ex) { ex.printStackTrace(); }
*/
final Md2GeometryStore storage = importer.load("md2/Demon.md2");
System.out.println("Importing Took " + (System.currentTimeMillis() - time) + " ms");
_root.attachChild(storage.getScenegraph());
}
}
You can find the MD2 file used in this demo here.
Except where otherwise noted, content on this wiki is licensed under the following license:CC Attribution-Share Alike 3.0 Unported