More work but no prototype yet

This commit is contained in:
Retera 2020-01-17 02:13:56 -06:00
parent c132b0d984
commit c157b0e368
64 changed files with 1892 additions and 343 deletions

View File

@ -1,65 +1,154 @@
package com.etheller.warsmash;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.file.Paths;
import javax.imageio.ImageIO;
import java.util.Arrays;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.Texture.TextureFilter;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.Quaternion;
import com.badlogic.gdx.math.Vector3;
import com.etheller.warsmash.datasources.CompoundDataSource;
import com.etheller.warsmash.datasources.DataSource;
import com.etheller.warsmash.datasources.FolderDataSource;
import com.etheller.warsmash.util.ImageUtils;
import com.etheller.warsmash.util.War3ID;
import com.etheller.warsmash.datasources.DataSourceDescriptor;
import com.etheller.warsmash.datasources.FolderDataSourceDescriptor;
import com.etheller.warsmash.viewer5.Camera;
import com.etheller.warsmash.viewer5.CanvasProvider;
import com.etheller.warsmash.viewer5.ModelViewer;
import com.etheller.warsmash.viewer5.PathSolver;
import com.etheller.warsmash.viewer5.Scene;
import com.etheller.warsmash.viewer5.SolvedPath;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxHandler;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxSimpleInstance;
public class WarsmashGdxGame extends ApplicationAdapter {
private SpriteBatch batch;
private BitmapFont font;
public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvider {
private DataSource codebase;
private Texture texture;
private ModelViewer viewer;
private MdxModel model;
private CameraManager cameraManager;
@Override
public void create() {
this.codebase = new FolderDataSource(Paths.get("C:/MPQBuild/War3.mpq/war3.mpq"));
final String renderer = Gdx.gl.glGetString(GL20.GL_RENDERER);
System.err.println("Renderer: " + renderer);
final FolderDataSourceDescriptor war3mpq = new FolderDataSourceDescriptor(
"D:\\NEEDS_ORGANIZING\\MPQBuild\\War3.mpq\\war3.mpq");
final FolderDataSourceDescriptor testingFolder = new FolderDataSourceDescriptor(
"D:\\NEEDS_ORGANIZING\\MPQBuild\\Test");
this.codebase = new CompoundDataSource(Arrays.<DataSourceDescriptor>asList(war3mpq, testingFolder));
this.viewer = new ModelViewer(this.codebase, this);
this.viewer.addHandler(new MdxHandler());
final Scene scene = this.viewer.addScene();
this.cameraManager = new CameraManager();
this.cameraManager.setupCamera(scene);
// this.model = (MdxModel) this.viewer.load("units\\human\\footman\\footman.mdx", new PathSolver() {
this.model = (MdxModel) this.viewer.load("Cube.mdx", new PathSolver() {
@Override
public SolvedPath solve(final String src, final Object solverParams) {
return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true);
}
}, null);
final MdxSimpleInstance instance = (MdxSimpleInstance) this.model.addInstance(1);
instance.setScene(scene);
// instance.setSequence(1);
//
// instance.setSequenceLoopMode(2);
final War3ID id = War3ID.fromString("ipea");
try {
final String path = "terrainart\\lordaeronsummer\\lords_dirt.blp";
final boolean has = this.codebase.has(path);
final BufferedImage img = ImageIO.read(this.codebase.getResourceAsStream(path));
this.texture = ImageUtils.getTexture(ImageUtils.forceBufferedImagesRGB(img));
this.texture.setFilter(TextureFilter.Linear, TextureFilter.Linear);
}
catch (final IOException e) {
throw new RuntimeException(e);
}
this.batch = new SpriteBatch();
this.font = new BitmapFont();
}
@Override
public void render() {
Gdx.gl.glClearColor(0, 1, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
final int srcFunc = this.batch.getBlendSrcFunc();
final int dstFunc = this.batch.getBlendDstFunc();
this.viewer.updateAndRender();
this.batch.enableBlending();
this.batch.begin();
// this.font.draw(this.batch, "Hello World", 100, 100);
this.batch.setBlendFunction(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);
this.batch.draw(this.texture, 0, 0);
this.batch.end();
this.batch.setBlendFunction(srcFunc, dstFunc);
// gl.glDrawElements(GL20.GL_TRIANGLES, this.elements, GL20.GL_UNSIGNED_SHORT, this.faceOffset);
}
@Override
public void dispose() {
}
@Override
public float getWidth() {
return Gdx.graphics.getWidth();
}
@Override
public float getHeight() {
return Gdx.graphics.getHeight();
}
class CameraManager {
private CanvasProvider canvas;
private Camera camera;
private float moveSpeed;
private float rotationSpeed;
private float zoomFactor;
private float horizontalAngle;
private float verticalAngle;
private float distance;
private Vector3 position;
private Vector3 target;
private Vector3 worldUp;
private Vector3 vecHeap;
private Quaternion quatHeap;
private Quaternion quatHeap2;
// An orbit camera setup example.
// Left mouse button controls the orbit itself.
// The right mouse button allows to move the camera and the point it's looking
// at on the XY plane.
// Scrolling zooms in and out.
private void setupCamera(final Scene scene) {
this.canvas = scene.viewer.canvas;
this.camera = scene.camera;
this.moveSpeed = 2;
this.rotationSpeed = (float) (Math.PI / 180);
this.zoomFactor = 0.1f;
this.horizontalAngle = (float) (Math.PI / 2);
this.verticalAngle = (float) (Math.PI / 4);
this.distance = 500;
this.position = new Vector3();
this.target = new Vector3();
this.worldUp = new Vector3(0, 0, 1);
this.vecHeap = new Vector3();
this.quatHeap = new Quaternion();
this.quatHeap2 = new Quaternion();
updateCamera();
// cameraUpdate();
}
private void updateCamera() {
// Limit the vertical angle so it doesn't flip.
// Since the camera uses a quaternion, flips don't matter to it, but this feels
// better.
this.verticalAngle = (float) Math.min(Math.max(0.01, this.verticalAngle), Math.PI - 0.01);
this.quatHeap.idt();
this.quatHeap.setFromAxisRad(0, 0, 1, this.horizontalAngle);
this.quatHeap2.idt();
this.quatHeap2.setFromAxisRad(1, 0, 0, this.verticalAngle);
this.quatHeap.mul(this.quatHeap2);
this.position.set(0, 0, 1);
this.quatHeap.transform(this.position);
this.position.scl(this.distance);
this.position = this.position.add(this.target);
this.camera.moveToAndFace(this.position, this.target, this.worldUp);
}
// private void cameraUpdate() {
//
// }
}
}

View File

@ -44,4 +44,12 @@ public class Extent {
public float getBoundsRadius() {
return this.boundsRadius;
}
public float[] getMin() {
return this.min;
}
public float[] getMax() {
return this.max;
}
}

View File

@ -118,4 +118,24 @@ public class Material implements MdlxBlock, Chunk {
return size;
}
public int getPriorityPlane() {
return this.priorityPlane;
}
public void setPriorityPlane(final int priorityPlane) {
this.priorityPlane = priorityPlane;
}
public int getFlags() {
return this.flags;
}
public void setFlags(final int flags) {
this.flags = flags;
}
public List<Layer> getLayers() {
return this.layers;
}
}

View File

@ -654,4 +654,88 @@ public class MdlxModel {
public List<float[]> getPivotPoints() {
return this.pivotPoints;
}
public int getVersion() {
return this.version;
}
public String getName() {
return this.name;
}
public String getAnimationFile() {
return this.animationFile;
}
public Extent getExtent() {
return this.extent;
}
public long getBlendTime() {
return this.blendTime;
}
public List<Material> getMaterials() {
return this.materials;
}
public List<Texture> getTextures() {
return this.textures;
}
public List<TextureAnimation> getTextureAnimations() {
return this.textureAnimations;
}
public List<Geoset> getGeosets() {
return this.geosets;
}
public List<GeosetAnimation> getGeosetAnimations() {
return this.geosetAnimations;
}
public List<Bone> getBones() {
return this.bones;
}
public List<Light> getLights() {
return this.lights;
}
public List<Helper> getHelpers() {
return this.helpers;
}
public List<Attachment> getAttachments() {
return this.attachments;
}
public List<ParticleEmitter> getParticleEmitters() {
return this.particleEmitters;
}
public List<ParticleEmitter2> getParticleEmitters2() {
return this.particleEmitters2;
}
public List<RibbonEmitter> getRibbonEmitters() {
return this.ribbonEmitters;
}
public List<Camera> getCameras() {
return this.cameras;
}
public List<EventObject> getEventObjects() {
return this.eventObjects;
}
public List<CollisionShape> getCollisionShapes() {
return this.collisionShapes;
}
public List<UnknownChunk> getUnknownChunks() {
return this.unknownChunks;
}
}

View File

@ -103,6 +103,10 @@ public class Sequence implements MdlxBlock {
}
public long[] getInterval() {
return interval;
return this.interval;
}
public int getFlags() {
return this.flags;
}
}

View File

@ -78,4 +78,16 @@ public class Texture implements MdlxBlock {
stream.endBlock();
}
public int getReplaceableId() {
return this.replaceableId;
}
public String getPath() {
return this.path;
}
public int getFlags() {
return this.flags;
}
}

View File

@ -337,7 +337,7 @@ public enum RenderMathUtils {
return (plane.x * px) + (plane.y * py) + plane.w;
}
public static int testSphere(final Vector4[] planes, final float x, final float y, final float z, final int r,
public static int testSphere(final Vector4[] planes, final float x, final float y, final float z, final float r,
int first) {
if (first == -1) {
first = 0;

View File

@ -4,7 +4,7 @@ import java.util.ArrayList;
import java.util.List;
public class SlkFile {
public List<List<Object>> rows;
public List<List<Object>> rows = new ArrayList<>();
public SlkFile(final String buffer) {
if (buffer != null) {
@ -44,7 +44,7 @@ public class SlkFile {
this.rows.set(y, new ArrayList<>());
}
if (valueString.charAt('0') == '"') {
if (valueString.charAt(0) == '"') {
value = valueString.substring(1, valueString.length() - 1);
}
else if ("TRUE".equals(valueString)) {

View File

@ -0,0 +1,16 @@
package com.etheller.warsmash.viewer5;
/**
* A batched model instance.
*/
public abstract class BatchedInstance extends ModelInstance {
public BatchedInstance(final Model model) {
super(model);
}
@Override
public boolean isBatched() {
return true;
}
}

View File

@ -1,5 +1,19 @@
package com.etheller.warsmash.viewer5;
public class Bounds {
public int x, y, r;
public float x, y, z, r;
public void fromExtents(final float[] min, final float[] max) {
final float x = min[0];
final float y = min[1];
final float z = min[2];
final float w = max[0] - x;
final float d = max[1] - y;
final float h = max[2] - z;
this.x = x + (w / 2f);
this.y = y + (d / 2f);
this.z = z + (h / 2f);
this.r = (float) (Math.max(Math.max(w, d), h) / 2.);
}
}

View File

@ -34,12 +34,26 @@ public class Camera {
public final Quaternion rotation;
public Quaternion inverseRotation;
private final Matrix4 worldMatrix;
/**
* World -> View.
*/
private final Matrix4 viewMatrix;
/**
* View -> Clip.
*/
private final Matrix4 projectionMatrix;
public final Matrix4 worldProjectionMatrix;
private final Matrix4 inverseWorldMatrix;
private final Matrix4 inverseRotationMatrix;
private final Matrix4 inverseWorldProjectionMatrix;
/**
* World -> Clip.
*/
public final Matrix4 viewProjectionMatrix;
/**
* View -> World.
*/
private final Matrix4 inverseViewMatrix;
/**
* Clip -> World.
*/
private final Matrix4 inverseViewProjectionMatrix;
public final Vector3 directionX;
public final Vector3 directionY;
public final Vector3 directionZ;
@ -75,12 +89,11 @@ public class Camera {
// Derived values.
this.inverseRotation = new Quaternion();
this.worldMatrix = new Matrix4();
this.viewMatrix = new Matrix4();
this.projectionMatrix = new Matrix4();
this.worldProjectionMatrix = new Matrix4();
this.inverseWorldMatrix = new Matrix4();
this.inverseRotationMatrix = new Matrix4();
this.inverseWorldProjectionMatrix = new Matrix4();
this.viewProjectionMatrix = new Matrix4();
this.inverseViewMatrix = new Matrix4();
this.inverseViewProjectionMatrix = new Matrix4();
this.directionX = new Vector3();
this.directionY = new Vector3();
this.directionZ = new Vector3();
@ -222,9 +235,9 @@ public class Camera {
final Vector3 location = this.location;
final Quaternion rotation = this.rotation;
final Quaternion inverseRotation = this.inverseRotation;
final Matrix4 worldMatrix = this.worldMatrix;
final Matrix4 viewMatrix = this.viewMatrix;
final Matrix4 projectionMatrix = this.projectionMatrix;
final Matrix4 worldProjectionMatrix = this.worldProjectionMatrix;
final Matrix4 viewProjectionMatrix = this.viewProjectionMatrix;
final Vector3[] vectors = this.vectors;
final Vector3[] billboardedVectors = this.billboardedVectors;
@ -238,19 +251,19 @@ public class Camera {
}
rotation.toMatrix(projectionMatrix.val);
worldMatrix.translate(vectorHeap.set(location).scl(-1));
viewMatrix.translate(vectorHeap.set(location).scl(-1));
inverseRotation.set(rotation).conjugate();
// World projection matrix
// World space -> NDC space
worldProjectionMatrix.set(projectionMatrix).mul(worldMatrix);
viewProjectionMatrix.set(projectionMatrix).mul(viewMatrix);
// Recalculate the camera's frustum planes
RenderMathUtils.unpackPlanes(this.planes, worldProjectionMatrix);
RenderMathUtils.unpackPlanes(this.planes, viewProjectionMatrix);
// Inverse world matrix
// Camera space -> world space
this.inverseWorldMatrix.set(worldMatrix).inv();
this.inverseViewMatrix.set(viewMatrix).inv();
this.directionX.set(RenderMathUtils.VEC3_UNIT_X);
inverseRotation.transform(this.directionX);
@ -261,8 +274,8 @@ public class Camera {
// Inverse world projection matrix
// NDC space -> World space
this.inverseWorldProjectionMatrix.set(worldProjectionMatrix);
this.inverseWorldProjectionMatrix.inv();
this.inverseViewProjectionMatrix.set(viewProjectionMatrix);
this.inverseViewProjectionMatrix.inv();
for (int i = 0; i < 7; i++) {
billboardedVectors[i].set(vectors[i]);
@ -281,18 +294,18 @@ public class Camera {
}
public Vector3 cameraToWorld(final Vector3 out, final Vector3 v) {
return out.set(v).prj(this.inverseWorldMatrix);
return out.set(v).prj(this.inverseViewMatrix);
}
public Vector3 worldToCamera(final Vector3 out, final Vector3 v) {
return out.set(v).prj(this.worldMatrix);
return out.set(v).prj(this.viewMatrix);
}
public Vector2 worldToScreen(final Vector2 out, final Vector3 v) {
final Rectangle viewport = this.rect;
vectorHeap.set(v);
vectorHeap.prj(this.inverseWorldMatrix);
vectorHeap.prj(this.inverseViewMatrix);
out.x = Math.round(((vectorHeap.x + 1) / 2) * viewport.width);
out.y = Math.round(((vectorHeap.y + 1) / 2) * viewport.height);
@ -309,10 +322,10 @@ public class Camera {
final Rectangle viewport = this.rect;
// Intersection on the near-plane
RenderMathUtils.unproject(a, c.set(x, y, 0), this.inverseWorldProjectionMatrix, viewport);
RenderMathUtils.unproject(a, c.set(x, y, 0), this.inverseViewProjectionMatrix, viewport);
// Intersection on the far-plane
RenderMathUtils.unproject(b, c.set(x, y, 1), this.inverseWorldProjectionMatrix, viewport);
RenderMathUtils.unproject(b, c.set(x, y, 1), this.inverseViewProjectionMatrix, viewport);
out[0] = a.x;
out[1] = a.y;

View File

@ -3,7 +3,8 @@ package com.etheller.warsmash.viewer5;
import java.util.ArrayList;
import java.util.List;
public abstract class Emitter<MODEL_INSTANCE extends ModelInstance, EMITTED_OBJECT extends EmittedObject<MODEL_INSTANCE, ? extends Emitter<MODEL_INSTANCE, EMITTED_OBJECT>>> {
public abstract class Emitter<MODEL_INSTANCE extends ModelInstance, EMITTED_OBJECT extends EmittedObject<MODEL_INSTANCE, ? extends Emitter<MODEL_INSTANCE, EMITTED_OBJECT>>>
implements UpdatableObject {
public final MODEL_INSTANCE instance;
public final List<EMITTED_OBJECT> objects;
@ -36,6 +37,7 @@ public abstract class Emitter<MODEL_INSTANCE extends ModelInstance, EMITTED_OBJE
return object;
}
@Override
public void update(final float dt) {
this.updateEmission(dt);

View File

@ -8,26 +8,26 @@ import com.badlogic.gdx.math.Vector3;
public abstract class GenericNode {
protected Vector3 pivot;
protected Vector3 localLocation;
protected Quaternion localRotation;
protected Vector3 localScale;
protected Vector3 worldLocation;
protected Quaternion worldRotation;
protected Vector3 worldScale;
protected Vector3 inverseWorldLocation;
protected Quaternion inverseWorldRotation;
protected Vector3 inverseWorldScale;
protected Matrix4 localMatrix;
protected Matrix4 worldMatrix;
protected GenericNode parent;
protected List<GenericNode> children;
public Vector3 pivot;
public Vector3 localLocation;
public Quaternion localRotation;
public Vector3 localScale;
public Vector3 worldLocation;
public Quaternion worldRotation;
public Vector3 worldScale;
public Vector3 inverseWorldLocation;
public Quaternion inverseWorldRotation;
public Vector3 inverseWorldScale;
public Matrix4 localMatrix;
public Matrix4 worldMatrix;
public GenericNode parent;
public List<Node> children;
public boolean dontInheritTranslation;
public boolean dontInheritRotation;
public boolean dontInheritScaling;
protected boolean visible;
protected boolean wasDirty;
protected boolean dirty;
public boolean visible;
public boolean wasDirty;
public boolean dirty;
protected abstract void update(float dt, Scene scene);
}

View File

@ -29,7 +29,7 @@ public final class GenericResource extends Resource {
@Override
protected void error(final Exception e) {
e.printStackTrace();
}
}

View File

@ -37,6 +37,9 @@ public class GridCell {
}
public boolean isVisible(final Camera camera) {
if (true) {
return true;
}
this.plane = RenderMathUtils.testCell(camera.planes, this.left, this.right, this.bottom, this.top, this.plane);
return this.plane == -1;

View File

@ -4,7 +4,6 @@ import java.util.ArrayList;
import java.util.List;
import com.etheller.warsmash.viewer5.handlers.ModelHandler;
import com.etheller.warsmash.viewer5.handlers.ModelInstanceDescriptor;
public abstract class Model<HANDLER extends ModelHandler> extends HandlerResource<HANDLER> {
public Bounds bounds;
@ -17,13 +16,14 @@ public abstract class Model<HANDLER extends ModelHandler> extends HandlerResourc
this.preloadedInstances = new ArrayList<>();
}
protected abstract ModelInstance createInstance(int type);
public ModelInstance addInstance() {
return addInstance(0);
}
public ModelInstance addInstance(final int type) {
final ModelInstanceDescriptor instanceDescriptor = this.handler.instanceDescriptor;
final ModelInstance instance = instanceDescriptor.create(this);
final ModelInstance instance = createInstance(type);
if (this.ok) {
instance.load();

View File

@ -1,6 +1,5 @@
package com.etheller.warsmash.viewer5;
import com.badlogic.gdx.math.Vector3;
import com.etheller.warsmash.util.RenderMathUtils;
import com.etheller.warsmash.util.Vector4;
@ -19,8 +18,6 @@ public abstract class ModelInstance extends Node {
public boolean paused;
public boolean rendered;
public Vector3 worldLocation;
public Vector3 worldScale;
public Scene scene;
public ModelInstance(final Model model) {
@ -96,6 +93,9 @@ public abstract class ModelInstance extends Node {
}
public boolean isVisible(final Camera camera) {
if (true) {
return true;
}
final float x = this.worldLocation.x;
final float y = this.worldLocation.y;
final float z = this.worldLocation.z;
@ -122,4 +122,6 @@ public abstract class ModelInstance extends Node {
public abstract void renderTranslucent();
public abstract void load();
protected abstract RenderBatch getBatch(TextureMapper textureMapper2);
}

View File

@ -57,7 +57,7 @@ public class ModelViewer {
this.rectBuffer = this.gl.glGenBuffer();
this.buffer = new ClientBuffer(this.gl);
this.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.rectBuffer);
final ByteBuffer temp = ByteBuffer.allocate(6);
final ByteBuffer temp = ByteBuffer.allocateDirect(6);
temp.put((byte) 0);
temp.put((byte) 1);
temp.put((byte) 2);
@ -129,7 +129,6 @@ public class ModelViewer {
String finalSrc = src;
String extension = "";
boolean isFetch = false;
final boolean resolved = false;
// If a given path solver, resolve.
if (pathSolver != null) {
@ -138,12 +137,17 @@ public class ModelViewer {
finalSrc = solved.getFinalSrc();
extension = solved.getExtension();
isFetch = solved.isFetch();
}
// Built-in texture sources
// ---- TODO not using JS code here
if (!(extension instanceof String)) {
throw new IllegalStateException("The path solver did not return an extension!");
}
if (extension.charAt(0) != '.') {
extension = '.' + extension;
}
// Built-in texture sources
// ---- TODO not using JS code here
if (resolved) {
final Object[] handlerAndDataType = this.findHandler(extension.toLowerCase());
// Is there a handler for this file type?
@ -181,8 +185,10 @@ public class ModelViewer {
}
}
else {
throw new IllegalStateException("Load unresolved: " + finalSrc);
throw new IllegalStateException(
"Could not resolve " + finalSrc + ". Did you forget to pass a path solver?");
}
}
public boolean has(final String key) {
@ -276,6 +282,7 @@ public class ModelViewer {
public void startFrame() {
this.gl.glDepthMask(true);
this.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);
this.gl.glClearColor(0.5f, 0.5f, 0.5f, 1); // TODO remove white background
}
public void render() {

View File

@ -0,0 +1,40 @@
package com.etheller.warsmash.viewer5;
import java.util.ArrayList;
import java.util.List;
/**
* A render batch.
*/
public abstract class RenderBatch {
public Scene scene;
public Model<?> model;
public TextureMapper textureMapper;
public List<ModelInstance> instances = new ArrayList<>();
public int count = 0;
public abstract void render();
public RenderBatch(final Scene scene, final Model<?> model, final TextureMapper textureMapper) {
this.scene = scene;
this.model = model;
this.textureMapper = textureMapper;
}
public void clear() {
this.count = 0;
}
public void add(final ModelInstance instance) {
if (this.count == this.instances.size()) {
this.instances.add(instance);
}
else if (this.count > this.instances.size()) {
throw new IllegalStateException("count > size");
}
else {
this.instances.set(this.count, instance);
}
this.count++;
}
}

View File

@ -8,8 +8,6 @@ import java.util.List;
import java.util.Map;
import com.badlogic.gdx.math.Rectangle;
import com.etheller.warsmash.viewer5.handlers.Batch;
import com.etheller.warsmash.viewer5.handlers.BatchDescriptor;
/**
* A scene.
@ -29,22 +27,22 @@ import com.etheller.warsmash.viewer5.handlers.BatchDescriptor;
*/
public class Scene {
private final ModelViewer viewer;
public final ModelViewer viewer;
public final Camera camera;
final Grid grid;
public final Grid grid;
public int visibleCells;
public int visibleInstances;
public int updatedParticles;
public boolean audioEnabled;
public AudioContext audioContext;
private final List<ModelInstance> instances;
private final int currentInstance;
private final List<ModelInstance> batchedInstances;
private final int currentBatchedInstance;
public final List<ModelInstance> instances;
public final int currentInstance;
public final List<ModelInstance> batchedInstances;
public final int currentBatchedInstance;
public final EmittedObjectUpdater emitterObjectUpdater;
private final Map<TextureMapper, Batch> batches;
private final Comparator<ModelInstance> instanceDepthComparator;
public final Map<TextureMapper, RenderBatch> batches;
public final Comparator<ModelInstance> instanceDepthComparator;
public Scene(final ModelViewer viewer) {
final CanvasProvider canvas = viewer.canvas;
@ -144,12 +142,10 @@ public class Scene {
public void addToBatch(final ModelInstance instance) {
final TextureMapper textureMapper = instance.textureMapper;
Batch batch = this.batches.get(textureMapper);
RenderBatch batch = this.batches.get(textureMapper);
if (batch == null) {
final Model<?> model = instance.model;
final BatchDescriptor batchDescriptor = model.handler.batchDescriptor;
batch = batchDescriptor.create(this, model, textureMapper);
batch = instance.getBatch(textureMapper);
this.batches.put(textureMapper, batch);
}
@ -240,7 +236,7 @@ public class Scene {
this.viewer.gl.glViewport((int) viewport.x, (int) viewport.y, (int) viewport.width, (int) viewport.height);
// Clear all of the batches.
for (final Batch batch : this.batches.values()) {
for (final RenderBatch batch : this.batches.values()) {
batch.clear();
}
@ -250,7 +246,7 @@ public class Scene {
}
// Render all of the batches.
for (final Batch batch : this.batches.values()) {
for (final RenderBatch batch : this.batches.values()) {
batch.render();
}

View File

@ -54,5 +54,5 @@ public class Shaders {
" vec3 quat_transform(vec2 q, vec3 v) {\r\n" + //
" return vec3(quat_transform(q, v.xy), v.z);\r\n" + //
" }\r\n" + //
" `,";
" ";
}

View File

@ -1,7 +1,6 @@
package com.etheller.warsmash.viewer5;
import java.util.ArrayList;
import java.util.List;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Quaternion;
@ -13,30 +12,9 @@ public abstract class SkeletalNode extends GenericNode {
protected static final Quaternion rotationHeap = new Quaternion();
protected static final Vector3 scalingHeap = new Vector3();
public final Vector3 pivot;
public final Vector3 localLocation;
public final Quaternion localRotation;
public final Vector3 localScale;
public final Vector3 worldLocation;
public final Quaternion worldRotation;
public final Vector3 worldScale;
public final Vector3 inverseWorldLocation;
public final Quaternion inverseWorldRotation;
public final Vector3 inverseWorldScale;
public final Matrix4 localMatrix;
public final Matrix4 worldMatrix;
public SkeletalNode parent;
public final List<Node> children;
public final boolean dontInheritTranslation;
public final boolean dontInheritRotation;
public final boolean dontInheritScaling;
public boolean visible;
public boolean wasDirty;
public boolean dirty;
public UpdatableObject object;
public Object object;
public final boolean billboarded;
public boolean billboarded;
public final boolean billboardedX;
public final boolean billboardedY;
public final boolean billboardedZ;
@ -158,7 +136,7 @@ public abstract class SkeletalNode extends GenericNode {
this.inverseWorldLocation.z = -this.worldLocation.z;
}
protected void updateChildren(final float dt, final Scene scene) {
public void updateChildren(final float dt, final Scene scene) {
for (int i = 0, l = this.children.size(); i < l; i++) {
this.children.get(i).update(dt, scene);
}

View File

@ -1,5 +1,6 @@
package com.etheller.warsmash.viewer5;
import com.badlogic.gdx.graphics.Texture.TextureWrap;
import com.etheller.warsmash.viewer5.handlers.ResourceHandler;
public abstract class Texture extends HandlerResource<ResourceHandler> {
@ -16,7 +17,7 @@ public abstract class Texture extends HandlerResource<ResourceHandler> {
@Override
protected void error(final Exception e) {
throw new RuntimeException(e);
e.printStackTrace();
}
public void bind(final int unit) {
@ -39,4 +40,12 @@ public abstract class Texture extends HandlerResource<ResourceHandler> {
return this.gdxTexture.glTarget;
}
public void setWrapS(final boolean wrapS) {
this.gdxTexture.setWrap(wrapS ? TextureWrap.Repeat : TextureWrap.ClampToEdge, this.gdxTexture.getVWrap());
}
public void setWrapT(final boolean wrapT) {
this.gdxTexture.setWrap(this.gdxTexture.getUWrap(), wrapT ? TextureWrap.Repeat : TextureWrap.ClampToEdge);
}
}

View File

@ -0,0 +1,5 @@
package com.etheller.warsmash.viewer5;
public interface UpdatableObject {
void update(float dt);
}

View File

@ -0,0 +1,13 @@
package com.etheller.warsmash.viewer5.gl;
/**
* TODO what is this?
*/
public interface ANGLEInstancedArrays {
void glVertexAttribDivisorANGLE(int index, int divisor);
void glDrawArraysInstancedANGLE(int mode, int first, int count, int instanceCount);
void glDrawElementsInstancedANGLE(int mode, int count, int type, int indicesOffset, int instanceCount);
}

View File

@ -33,7 +33,7 @@ public class ClientBuffer {
this.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.buffer);
this.arrayBuffer = ByteBuffer.allocate(this.size);
this.arrayBuffer = ByteBuffer.allocateDirect(this.size);
this.gl.glBufferData(GL20.GL_ARRAY_BUFFER, this.size, this.arrayBuffer, GL20.GL_DYNAMIC_DRAW);
this.byteView = this.arrayBuffer;
this.floatView = this.arrayBuffer.asFloatBuffer();

View File

@ -0,0 +1,67 @@
package com.etheller.warsmash.viewer5.gl;
import java.nio.Buffer;
import com.badlogic.gdx.graphics.GL20;
public class DataTexture {
public GL20 gl;
public int texture;
public int format;
public int width = 0;
public int height = 0;
public DataTexture(final GL20 gl, final int channels, final int width, final int height) {
this.gl = gl;
this.texture = gl.glGenTexture();
this.format = (channels == 3 ? GL20.GL_RGB : GL20.GL_RGBA);
gl.glBindTexture(GL20.GL_TEXTURE_2D, this.texture);
gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_WRAP_S, GL20.GL_CLAMP_TO_EDGE);
gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_WRAP_T, GL20.GL_CLAMP_TO_EDGE);
gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MAG_FILTER, GL20.GL_NEAREST);
gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MAG_FILTER, GL20.GL_NEAREST);
this.reserve(width, height);
}
private void reserve(final int width, final int height) {
if ((this.width < width) || (this.height < height)) {
final GL20 gl = this.gl;
this.width = Math.max(this.width, width);
this.height = Math.max(this.height, height);
gl.glBindTexture(GL20.GL_TEXTURE_2D, this.texture);
gl.glTexImage2D(GL20.GL_TEXTURE_2D, 0, this.format, this.width, this.height, 0, this.format, GL20.GL_FLOAT,
null);
}
}
public void bindAndUpdate(final Buffer buffer) {
bindAndUpdate(buffer, this.width, this.height);
}
public void bindAndUpdate(final Buffer buffer, final int width, final int height) {
final GL20 gl = this.gl;
gl.glBindTexture(GL20.GL_TEXTURE_2D, this.texture);
gl.glTexSubImage2D(GL20.GL_TEXTURE_2D, 0, 0, 0, width, height, this.format, GL20.GL_FLOAT, buffer);
}
public void bind(final int unit) {
final GL20 gl = this.gl;
gl.glActiveTexture(GL20.GL_TEXTURE0 + unit);
gl.glBindTexture(GL20.GL_TEXTURE_2D, this.texture);
}
public int getWidth() {
return this.width;
}
public int getHeight() {
return this.height;
}
}

View File

@ -0,0 +1,5 @@
package com.etheller.warsmash.viewer5.gl;
public class Extensions {
public static ANGLEInstancedArrays angleInstancedArrays;
}

View File

@ -20,6 +20,7 @@ public class WebGL {
public ShaderProgram currentShaderProgram;
public String floatPrecision;
public final com.badlogic.gdx.graphics.Texture emptyTexture;
public ANGLEInstancedArrays instancedArrays;
public WebGL(final GL20 gl) {
gl.glDepthFunc(GL20.GL_LEQUAL);
@ -43,6 +44,7 @@ public class WebGL {
}
}
this.emptyTexture = new com.badlogic.gdx.graphics.Texture(imageData);
this.instancedArrays = Extensions.angleInstancedArrays;
}
public ShaderUnitDeprecated createShaderUnit(final String src, final int type) {
@ -53,10 +55,13 @@ public class WebGL {
return this.shaderUnits.get(hash);
}
public ShaderProgram createShaderProgram(final String vertexSrc, final String fragmentSrc) {
public ShaderProgram createShaderProgram(String vertexSrc, String fragmentSrc) {
vertexSrc = vertexSrc.replace("mediump", "");
fragmentSrc = fragmentSrc.replace("mediump", "");
final Map<Integer, ShaderProgram> shaderPrograms = this.shaderPrograms;
final int hash = stringHash(vertexSrc + fragmentSrc);
ShaderProgram.pedantic = false;
if (!shaderPrograms.containsKey(hash)) {
shaderPrograms.put(hash, new ShaderProgram(vertexSrc, fragmentSrc));
}
@ -66,6 +71,12 @@ public class WebGL {
if (shaderProgram.isCompiled()) {
return shaderProgram;
}
else {
System.err.println(shaderProgram.getLog());
if (true) {
throw new IllegalStateException("Bad shader");
}
}
return null;
}

View File

@ -1,22 +0,0 @@
package com.etheller.warsmash.viewer5.handlers;
import com.etheller.warsmash.viewer5.ModelInstance;
public class Batch {
public void add(final ModelInstance instance) {
// TODO Auto-generated method stub
}
public void clear() {
// TODO Auto-generated method stub
}
public void render() {
// TODO Auto-generated method stub
}
}

View File

@ -1,9 +0,0 @@
package com.etheller.warsmash.viewer5.handlers;
import com.etheller.warsmash.viewer5.Model;
import com.etheller.warsmash.viewer5.Scene;
import com.etheller.warsmash.viewer5.TextureMapper;
public interface BatchDescriptor {
Batch create(Scene scene, Model model, TextureMapper textureMapper);
}

View File

@ -1,6 +1,5 @@
package com.etheller.warsmash.viewer5.handlers;
public abstract class ModelHandler extends ResourceHandler {
public BatchDescriptor batchDescriptor;
public ModelInstanceDescriptor instanceDescriptor;
}

View File

@ -1,11 +1,13 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
public class AttachmentInstance {
import com.etheller.warsmash.viewer5.UpdatableObject;
public class AttachmentInstance implements UpdatableObject {
private static final float[] visbilityHeap = new float[1];
private final MdxComplexInstance instance;
private final Attachment attachment;
private final MdxComplexInstance internalInstance;
public final MdxComplexInstance internalInstance;
public AttachmentInstance(final MdxComplexInstance instance, final Attachment attachment) {
final MdxModel internalModel = attachment.internalModel;
@ -21,7 +23,8 @@ public class AttachmentInstance {
this.internalInstance = internalInstance;
}
public void update() {
@Override
public void update(final float dt) {
final MdxComplexInstance internalInstance = this.internalInstance;
if (internalInstance.model.ok) {

View File

@ -7,6 +7,7 @@ import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.etheller.warsmash.viewer5.ModelViewer;
import com.etheller.warsmash.viewer5.Scene;
import com.etheller.warsmash.viewer5.Texture;
import com.etheller.warsmash.viewer5.gl.DataTexture;
public class BatchGroup extends GenericGroup {
@ -18,6 +19,7 @@ public class BatchGroup extends GenericGroup {
this.isExtended = isExtended;
}
@Override
public void render(final MdxComplexInstance instance) {
final Scene scene = instance.scene;
final MdxModel model = this.model;
@ -41,9 +43,9 @@ public class BatchGroup extends GenericGroup {
shader.begin();
shader.setUniformMatrix("u_mvp", scene.camera.worldProjectionMatrix);
shader.setUniformMatrix("u_mvp", scene.camera.viewProjectionMatrix);
final Texture boneTexture = instance.boneTexture;
final DataTexture boneTexture = instance.boneTexture;
// Instances of models with no bones don't have a bone texture.
if (boneTexture != null) {

View File

@ -7,7 +7,13 @@ public class Bone extends GenericObject {
public Bone(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.Bone bone, final int index) {
super(model, bone, index);
this.geosetAnimation = model.getGeosetAnimations().get(bone.getGeosetAnimationId());
final int geosetAnimationId = bone.getGeosetAnimationId();
if (geosetAnimationId != -1) {
this.geosetAnimation = model.getGeosetAnimations().get(geosetAnimationId);
}
else {
this.geosetAnimation = null;
}
}
@Override

View File

@ -1,12 +1,12 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.etheller.warsmash.viewer5.Model;
import com.etheller.warsmash.viewer5.ModelViewer;
import com.etheller.warsmash.viewer5.Scene;
import com.etheller.warsmash.viewer5.SkeletalNode;
import com.etheller.warsmash.viewer5.gl.ANGLEInstancedArrays;
public class EmitterGroup extends GenericGroup {
private final MdxModel model;
@ -15,12 +15,14 @@ public class EmitterGroup extends GenericGroup {
this.model = model;
}
@Override
public void render(final MdxComplexInstance instance) {
final Scene scene = instance.scene;
final SkeletalNode[] nodes = instance.nodes;
final Model<?> model = instance.model;
final ModelViewer viewer = model.viewer;
final GL20 gl = viewer.gl;
final ANGLEInstancedArrays instancedArrays = viewer.webGL.instancedArrays;
final ShaderProgram shader = MdxHandler.Shaders.particles;
gl.glDepthMask(false);
@ -30,36 +32,36 @@ public class EmitterGroup extends GenericGroup {
shader.begin();
shader.setUniformMatrix("u_mvp", scene.camera.worldProjectionMatrix);
shader.setUniformMatrix("u_mvp", scene.camera.viewProjectionMatrix);
shader.setUniformf("u_texture", 0);
final int a_position = shader.getAttributeLocation("a_position");
Gdx.gl30.glVertexAttribDivisor(a_position, 0);
instancedArrays.glVertexAttribDivisorANGLE(a_position, 0);
gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, viewer.rectBuffer);
gl.glVertexAttribPointer(a_position, 1, GL20.GL_UNSIGNED_BYTE, false, 0, 0);
Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_p0"), 1);
Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_p1"), 1);
Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_p2"), 1);
Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_p3"), 1);
Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_health"), 1);
Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_color"), 1);
Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_tail"), 1);
Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_leftRightTop"), 1);
instancedArrays.glVertexAttribDivisorANGLE(shader.getAttributeLocation("a_p0"), 1);
instancedArrays.glVertexAttribDivisorANGLE(shader.getAttributeLocation("a_p1"), 1);
instancedArrays.glVertexAttribDivisorANGLE(shader.getAttributeLocation("a_p2"), 1);
instancedArrays.glVertexAttribDivisorANGLE(shader.getAttributeLocation("a_p3"), 1);
instancedArrays.glVertexAttribDivisorANGLE(shader.getAttributeLocation("a_health"), 1);
instancedArrays.glVertexAttribDivisorANGLE(shader.getAttributeLocation("a_color"), 1);
instancedArrays.glVertexAttribDivisorANGLE(shader.getAttributeLocation("a_tail"), 1);
instancedArrays.glVertexAttribDivisorANGLE(shader.getAttributeLocation("a_leftRightTop"), 1);
for (final int index : this.objects) {
GeometryEmitterFuncs.renderEmitter((MdxEmitter<?, ?, ?>) nodes[index].object, shader);
}
Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_leftRightTop"), 0);
Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_tail"), 0);
Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_color"), 0);
Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_health"), 0);
Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_p3"), 0);
Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_p2"), 0);
Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_p1"), 0);
Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_p0"), 0);
instancedArrays.glVertexAttribDivisorANGLE(shader.getAttributeLocation("a_leftRightTop"), 0);
instancedArrays.glVertexAttribDivisorANGLE(shader.getAttributeLocation("a_tail"), 0);
instancedArrays.glVertexAttribDivisorANGLE(shader.getAttributeLocation("a_color"), 0);
instancedArrays.glVertexAttribDivisorANGLE(shader.getAttributeLocation("a_health"), 0);
instancedArrays.glVertexAttribDivisorANGLE(shader.getAttributeLocation("a_p3"), 0);
instancedArrays.glVertexAttribDivisorANGLE(shader.getAttributeLocation("a_p2"), 0);
instancedArrays.glVertexAttribDivisorANGLE(shader.getAttributeLocation("a_p1"), 0);
instancedArrays.glVertexAttribDivisorANGLE(shader.getAttributeLocation("a_p0"), 0);
}
}

View File

@ -32,6 +32,10 @@ public abstract class EventObjectEmitter<EMITTER_OBJECT extends EventObjectEmitt
}
public void reset() {
this.lastValue = 0;
}
@Override
protected void emit() {
this.emitObject(0);

View File

@ -21,7 +21,7 @@ import com.etheller.warsmash.viewer5.PathSolver;
import com.etheller.warsmash.viewer5.Texture;
import com.etheller.warsmash.viewer5.handlers.EmitterObject;
public abstract class EventObjectEmitterObject extends GenericObject implements EmitterObject {
public class EventObjectEmitterObject extends GenericObject implements EmitterObject {
private static final LoadGenericCallback mappedDataCallback = new LoadGenericCallback() {
@Override
@ -57,10 +57,10 @@ public abstract class EventObjectEmitterObject extends GenericObject implements
};
private int geometryEmitterType = -1;
private final String type;
public final String type;
private final String id;
private final long[] keyFrames;
private long globalSequence;
private long globalSequence = -1;
private final long[] defval = { 1 };
public MdxModel internalModel;
public Texture internalTexture;
@ -130,7 +130,7 @@ public abstract class EventObjectEmitterObject extends GenericObject implements
FetchDataTypeName.SLK, mappedDataCallback));
}
else if ("SPL".equals(type)) {
tables.add(viewer.loadGeneric(pathSolver.solve("Splats\\SpawnData.slk", solverParams).finalSrc,
tables.add(viewer.loadGeneric(pathSolver.solve("Splats\\SplatData.slk", solverParams).finalSrc,
FetchDataTypeName.SLK, mappedDataCallback));
}
else if ("UBR".equals(type)) {
@ -156,6 +156,30 @@ public abstract class EventObjectEmitterObject extends GenericObject implements
this.load(tables);
}
private float getFloat(final MappedDataRow row, final String name) {
final Float x = (Float) row.get(name);
if (x == null) {
return Float.NaN;
}
else {
return x.floatValue();
}
}
private int getInt(final MappedDataRow row, final String name) {
return getInt(row, name, Integer.MIN_VALUE);
}
private int getInt(final MappedDataRow row, final String name, final int defaultValue) {
final Number x = (Number) row.get(name);
if (x == null) {
return defaultValue;
}
else {
return x.intValue();
}
}
private void load(final List<GenericResource> tables) {
final MappedData firstTable = (MappedData) tables.get(0).data;
final MappedDataRow row = firstTable.getRow(this.id);
@ -182,40 +206,36 @@ public abstract class EventObjectEmitterObject extends GenericObject implements
"ReplaceableTextures\\Splats\\" + row.get("file") + texturesExt, pathSolver,
model.solverParams);
this.scale = (Float) row.get("Scale");
this.scale = getFloat(row, "Scale");
this.colors = new float[][] {
{ ((Float) row.get("StartR")).floatValue(), ((Float) row.get("StartG")).floatValue(),
((Float) row.get("StartB")).floatValue(), ((Float) row.get("StartA")).floatValue() },
{ ((Float) row.get("MiddleR")).floatValue(), ((Float) row.get("MiddleG")).floatValue(),
((Float) row.get("MiddleB")).floatValue(), ((Float) row.get("MiddleA")).floatValue() },
{ ((Float) row.get("EndR")).floatValue(), ((Float) row.get("EndG")).floatValue(),
((Float) row.get("EndB")).floatValue(), ((Float) row.get("EndA")).floatValue() } };
{ getFloat(row, "StartR"), getFloat(row, "StartG"), getFloat(row, "StartB"),
getFloat(row, "StartA") },
{ getFloat(row, "MiddleR"), getFloat(row, "MiddleG"), getFloat(row, "MiddleB"),
getFloat(row, "MiddleA") },
{ getFloat(row, "EndR"), getFloat(row, "EndG"), getFloat(row, "EndB"),
getFloat(row, "EndA") } };
if ("SPL".equals(this.type)) {
this.columns = ((Number) row.get("Columns")).intValue();
this.rows = ((Number) row.get("Rows")).intValue();
this.lifeSpan = ((Number) row.get("Lifespan")).floatValue()
+ ((Number) row.get("Decay")).floatValue();
this.columns = getInt(row, "Columns");
this.rows = getInt(row, "Rows");
this.lifeSpan = getFloat(row, "Lifespan") + getFloat(row, "Decay");
this.intervals = new float[][] {
{ ((Float) row.get("UVLifespanStart")).floatValue(),
((Float) row.get("UVLifespanEnd")).floatValue(),
((Float) row.get("LifespanRepeat")).floatValue() },
{ ((Float) row.get("UVDecayStart")).floatValue(),
((Float) row.get("UVDecayEnd")).floatValue(),
((Float) row.get("DecayRepeat")).floatValue() }, };
{ getFloat(row, "UVLifespanStart"), getFloat(row, "UVLifespanEnd"),
getFloat(row, "LifespanRepeat") },
{ getFloat(row, "UVDecayStart"), getFloat(row, "UVDecayEnd"),
getFloat(row, "DecayRepeat") }, };
}
else {
this.columns = 1;
this.rows = 1;
this.lifeSpan = ((Number) row.get("BirthTime")).floatValue()
+ ((Number) row.get("PauseTime")).floatValue() + ((Number) row.get("Decay")).floatValue();
this.intervalTimes = new float[] { ((Number) row.get("BirthTime")).floatValue(),
((Number) row.get("PauseTime")).floatValue(), ((Number) row.get("Decay")).floatValue() };
this.lifeSpan = getFloat(row, "BirthTime") + getFloat(row, "PauseTime") + getFloat(row, "Decay");
this.intervalTimes = new float[] { getFloat(row, "BirthTime"), getFloat(row, "PauseTime"),
getFloat(row, "Decay") };
}
final int[] blendModes = FilterMode
.emitterFilterMode(com.etheller.warsmash.parsers.mdlx.ParticleEmitter2.FilterMode
.fromId(((Number) row.get("BlendMode")).intValue()));
.fromId(getInt(row, "BlendMode")));
this.blendSrc = blendModes[0];
this.blendDst = blendModes[1];
@ -232,12 +252,12 @@ public abstract class EventObjectEmitterObject extends GenericObject implements
final MappedDataRow animSoundsRow = animSounds.getRow((String) row.get("SoundLabel"));
if (animSoundsRow != null) {
this.distanceCutoff = ((Number) animSoundsRow.get("DistanceCutoff")).floatValue();
this.maxDistance = ((Number) animSoundsRow.get("MaxDistance")).floatValue();
this.minDistance = ((Number) animSoundsRow.get("MinDistance")).floatValue();
this.pitch = ((Number) animSoundsRow.get("Pitch")).floatValue();
this.pitchVariance = ((Number) animSoundsRow.get("PitchVariance")).floatValue();
this.volume = ((Number) animSoundsRow.get("Volume")).floatValue();
this.distanceCutoff = getFloat(animSoundsRow, "DistanceCutoff");
this.maxDistance = getFloat(animSoundsRow, "MaxDistance");
this.minDistance = getFloat(animSoundsRow, "MinDistance");
this.pitch = getFloat(animSoundsRow, "Pitch");
this.pitchVariance = getFloat(animSoundsRow, "PitchVariance");
this.volume = getFloat(animSoundsRow, "Volume");
final String[] fileNames = ((String) animSoundsRow.get("FileNames")).split(",");
final GenericResource[] resources = new GenericResource[fileNames.length];
@ -300,4 +320,13 @@ public abstract class EventObjectEmitterObject extends GenericObject implements
return -1;
}
@Override
public boolean ok() {
return this.ok;
}
@Override
public int getGeometryEmitterType() {
return this.geometryEmitterType;
}
}

View File

@ -3,9 +3,11 @@ package com.etheller.warsmash.viewer5.handlers.mdx;
import java.util.ArrayList;
import java.util.List;
public class GenericGroup {
public abstract class GenericGroup {
public final List<Integer> objects;
public abstract void render(MdxComplexInstance instance);
public GenericGroup() {
this.objects = new ArrayList<>(); // TODO IntArrayList
}

View File

@ -145,7 +145,7 @@ public class GenericObject extends AnimatedObject implements GenericIndexed {
return this.isVariant(AnimationMap.KGSC.getWar3id(), sequence);
}
private static final class Variants {
public static final class Variants {
boolean[] translation;
boolean[] rotation;
boolean[] scale;

View File

@ -4,9 +4,7 @@ import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.util.List;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.GL30;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.badlogic.gdx.math.Vector3;
import com.etheller.warsmash.viewer5.Camera;
@ -14,6 +12,7 @@ import com.etheller.warsmash.viewer5.ModelViewer;
import com.etheller.warsmash.viewer5.Scene;
import com.etheller.warsmash.viewer5.Texture;
import com.etheller.warsmash.viewer5.TextureMapper;
import com.etheller.warsmash.viewer5.gl.ANGLEInstancedArrays;
import com.etheller.warsmash.viewer5.gl.ClientBuffer;
import com.etheller.warsmash.viewer5.handlers.EmitterObject;
@ -378,6 +377,9 @@ public class GeometryEmitterFuncs {
}
public static void renderEmitter(final MdxEmitter<?, ?, ?> emitter, final ShaderProgram shader) {
if (emitter == null) {
System.err.println("NULL EMITTER");
}
int alive = emitter.alive;
final EmitterObject emitterObject = emitter.emitterObject;
final int emitterType = emitterObject.getGeometryEmitterType();
@ -388,6 +390,7 @@ public class GeometryEmitterFuncs {
if (alive > 0) {
final ModelViewer viewer = emitter.instance.model.viewer;
final ANGLEInstancedArrays instancedArrays = viewer.webGL.instancedArrays;
final ClientBuffer buffer = viewer.buffer;
final GL20 gl = viewer.gl;
final int size = alive * BYTES_PER_OBJECT;
@ -425,7 +428,7 @@ public class GeometryEmitterFuncs {
shader.setVertexAttribute("a_leftRightTop", 3, GL20.GL_UNSIGNED_BYTE, false, BYTES_PER_OBJECT,
BYTE_OFFSET_LEFT_RIGHT_TOP);
Gdx.gl30.glDrawArraysInstanced(GL30.GL_TRIANGLES, 0, 6, alive);
instancedArrays.glDrawArraysInstancedANGLE(GL20.GL_TRIANGLES, 0, 6, alive);
}
}

View File

@ -2,10 +2,9 @@ package com.etheller.warsmash.viewer5.handlers.mdx;
import java.util.Arrays;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.GL30;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.etheller.warsmash.viewer5.gl.ANGLEInstancedArrays;
public class Geoset {
public MdxModel model;
@ -123,8 +122,9 @@ public class Geoset {
}
public void renderSimple(final int instances) {
Gdx.gl30.glDrawElementsInstanced(GL30.GL_TRIANGLES, this.elements, GL30.GL_UNSIGNED_SHORT, this.faceOffset,
instances);
final ANGLEInstancedArrays instancedArrays = this.model.viewer.webGL.instancedArrays;
instancedArrays.glDrawElementsInstancedANGLE(GL20.GL_TRIANGLES, this.elements, GL20.GL_UNSIGNED_SHORT,
this.faceOffset, instances);
}
public void bindHd(final ShaderProgram shader, final int coordId) {

View File

@ -15,8 +15,12 @@ public class GeosetAnimation extends AnimatedObject {
final float[] color = geosetAnimation.getColor();
this.alpha = geosetAnimation.getAlpha();
this.color = new float[] { color[2], color[1], color[0] };
this.color = new float[] { color[2], color[1], color[0] }; // Stored as RGB, but animated colors are stored as
// BGR, so sizzle.
this.geosetId = geosetAnimation.getGeosetId();
this.addVariants(AnimationMap.KGAO.getWar3id(), "alpha");
this.addVariants(AnimationMap.KGAC.getWar3id(), "color");
}
public int getAlpha(final float[] out, final int sequence, final int frame, final int counter) {

View File

@ -1,70 +1,654 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.List;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Quaternion;
import com.badlogic.gdx.math.Vector3;
import com.etheller.warsmash.parsers.mdlx.Sequence;
import com.etheller.warsmash.viewer5.GenericNode;
import com.etheller.warsmash.viewer5.ModelInstance;
import com.etheller.warsmash.viewer5.Node;
import com.etheller.warsmash.viewer5.RenderBatch;
import com.etheller.warsmash.viewer5.Scene;
import com.etheller.warsmash.viewer5.SkeletalNode;
import com.etheller.warsmash.viewer5.Texture;
import com.etheller.warsmash.viewer5.TextureMapper;
import com.etheller.warsmash.viewer5.UpdatableObject;
import com.etheller.warsmash.viewer5.gl.DataTexture;
public class MdxComplexInstance extends ModelInstance {
private static final float[] visibilityHeap = new float[1];
private static final float[] translationHeap = new float[3];
private static final float[] rotationHeap = new float[4];
private static final float[] scaleHeap = new float[3];
private static final float[] colorHeap = new float[3];
private static final float[] alphaHeap = new float[1];
private static final long[] textureIdHeap = new long[1];
public List<AttachmentInstance> attachments = new ArrayList<>();
public List<ParticleEmitter> particleEmitters = new ArrayList<>();
public List<ParticleEmitter2> particleEmitters2 = new ArrayList<>();
public List<RibbonEmitter> ribbonEmitters = new ArrayList<>();
public List<EventObjectEmitter<?, ?>> eventObjectEmitters = new ArrayList<>();
public MdxNode[] nodes;
public SkeletalNode[] sortedNodes;
public int frame;
public int counter;
public int sequence;
public int sequenceLoopMode;
public boolean sequenceEnded;
public int teamColor;
public Texture boneTexture;
// TODO more fields, these few are to make related classes compile
public float[] vertexColor;
public int frame = 0;
// Global sequences
public int counter = 0;
public int sequence = -1;
public int sequenceLoopMode = 0;
public boolean sequenceEnded = false;
public int teamColor = 0;
public float[] vertexColor = { 1, 1, 1, 1 };
// Particles do not spawn when the sequence is -1, or when the sequence finished
// and it's not repeating
public boolean allowParticleSpawn = false;
// If forced is true, everything will update regardless of variancy.
// Any later non-forced update can then use variancy to skip updating things.
// It is set to true every time the sequence is set with setSequence().
public boolean forced = true;
public float[][] geosetColors;
public float[] layerAlphas;
public int[] layerTextures;
public float[][] uvAnims;
public boolean allowParticleSpawn;
public Matrix4[] worldMatrices;
public FloatBuffer worldMatricesCopyHeap;
public DataTexture boneTexture;
public MdxComplexInstance(final MdxModel model) {
super(model);
}
@Override
public void updateAnimations(final float dt) {
// TODO Auto-generated method stub
public void load() {
final MdxModel model = (MdxModel) this.model;
this.geosetColors = new float[model.geosets.size()][];
for (int i = 0, l = model.geosets.size(); i < l; i++) {
this.geosetColors[i] = new float[4];
}
this.layerAlphas = new float[model.layers.size()];
this.layerTextures = new int[model.layers.size()];
this.uvAnims = new float[model.layers.size()][];
for (int i = 0, l = model.layers.size(); i < l; i++) {
this.layerAlphas[i] = 0;
this.layerTextures[i] = 0;
this.uvAnims[i] = new float[5];
}
// Create the needed amount of shared nodes.
final Object[] sharedNodeData = Node.createSkeletalNodes(model.genericObjects.size(),
MdxNodeDescriptor.INSTANCE);
final List<MdxNode> nodes = (List<MdxNode>) sharedNodeData[0];
int nodeIndex = 0;
this.nodes = nodes.toArray(new MdxNode[nodes.size()]);
// A shared typed array for all world matrices of the internal nodes.
this.worldMatrices = ((List<Matrix4>) sharedNodeData[1]).toArray(new Matrix4[0]);
this.worldMatricesCopyHeap = ByteBuffer.allocateDirect(16 * this.worldMatrices.length * 4).asFloatBuffer();
// And now initialize all of the nodes and objects
for (final Bone bone : model.bones) {
this.initNode(this.nodes, this.nodes[nodeIndex++], bone);
}
for (final Light light : model.lights) {
this.initNode(this.nodes, this.nodes[nodeIndex++], light);
}
for (final Helper helper : model.helpers) {
this.initNode(this.nodes, this.nodes[nodeIndex++], helper);
}
for (final Attachment attachment : model.attachments) {
AttachmentInstance attachmentInstance = null;
// Attachments may have game models attached to them, such as Undead and
// Nightelf building animations.
if (attachment.internalModel != null) {
attachmentInstance = new AttachmentInstance(this, attachment);
this.attachments.add(attachmentInstance);
}
this.initNode(this.nodes, this.nodes[nodeIndex++], attachment, attachmentInstance);
}
for (final ParticleEmitterObject emitterObject : model.particleEmitters) {
final ParticleEmitter emitter = new ParticleEmitter(this, emitterObject);
this.particleEmitters.add(emitter);
this.initNode(this.nodes, this.nodes[nodeIndex++], emitterObject, emitter);
}
for (final ParticleEmitter2Object emitterObject : model.particleEmitters2) {
final ParticleEmitter2 emitter = new ParticleEmitter2(this, emitterObject);
this.particleEmitters2.add(emitter);
this.initNode(this.nodes, this.nodes[nodeIndex++], emitterObject, emitter);
}
for (final RibbonEmitterObject emitterObject : model.ribbonEmitters) {
final RibbonEmitter emitter = new RibbonEmitter(this, emitterObject);
this.ribbonEmitters.add(emitter);
this.initNode(this.nodes, this.nodes[nodeIndex++], emitterObject, emitter);
}
for (final EventObjectEmitterObject emitterObject : model.eventObjects) {
final String type = emitterObject.type;
EventObjectEmitter<?, ?> emitter;
if ("SPN".equals(type)) {
emitter = new EventObjectSpnEmitter(this, emitterObject);
}
else if ("SPL".equals(type)) {
emitter = new EventObjectSplEmitter(this, emitterObject);
}
else if ("UBR".equals(type)) {
emitter = new EventObjectUbrEmitter(this, emitterObject);
}
else {
emitter = new EventObjectSndEmitter(this, emitterObject);
}
this.eventObjectEmitters.add(emitter);
this.initNode(this.nodes, this.nodes[nodeIndex++], emitterObject, emitter);
}
for (final CollisionShape collisionShape : model.collisionShapes) {
this.initNode(this.nodes, this.nodes[nodeIndex++], collisionShape);
}
// Save a sorted array of all of the nodes, such that every child node comes
// after its parent.
// This allows for flat iteration when updating.
final List<Integer> hierarchy = model.hierarchy;
this.sortedNodes = new SkeletalNode[nodes.size()];
for (int i = 0, l = nodes.size(); i < l; i++) {
this.sortedNodes[i] = this.nodes[hierarchy.get(i)];
}
// If the sequence was changed before the model was loaded, reset it now that
// the model loaded.
this.setSequence(this.sequence);
if (model.bones.size() != 0) {
this.boneTexture = new DataTexture(model.viewer.gl, 4, model.bones.size() * 4, 1);
}
}
/*
* Clear all of the emitted objects that belong to this instance.
*/
@Override
public void clearEmittedObjects() {
for (final ParticleEmitter emitter : this.particleEmitters) {
emitter.clear();
}
for (final ParticleEmitter2 emitter : this.particleEmitters2) {
emitter.clear();
}
for (final RibbonEmitter emitter : this.ribbonEmitters) {
emitter.clear();
}
for (final EventObjectEmitter<?, ?> emitter : this.eventObjectEmitters) {
emitter.clear();
}
}
private void initNode(final MdxNode[] nodes, final SkeletalNode node, final GenericObject genericObject) {
initNode(nodes, node, genericObject, null);
}
/**
* Initialize a skeletal node.
*/
private void initNode(final MdxNode[] nodes, final SkeletalNode node, final GenericObject genericObject,
final UpdatableObject object) {
node.pivot.set(genericObject.pivot);
if (genericObject.parentId == -1) {
node.parent = this;
}
else {
node.parent = nodes[genericObject.parentId];
}
/// TODO: single-axis billboarding
if (genericObject.billboarded != 0) {
node.billboarded = true;
} // else if (genericObject.billboardedX) {
// node.billboardedX = true;
// } else if (genericObject.billboardedY) {
// node.billboardedY = true;
// } else if (genericObject.billboardedZ) {
// node.billboardedZ = true;
// }
if (object != null) {
node.object = object;
}
}
/*
* Overriden to hide also attachment models.
*/
@Override
public void clearEmittedObjects() {
// TODO Auto-generated method stub
public void hide() {
super.hide();
for (final AttachmentInstance attachment : this.attachments) {
attachment.internalInstance.hide();
}
}
/**
* Updates all of this instance internal nodes and objects. Nodes that are
* determined to not be visible will not be updated, nor will any of their
* children down the hierarchy.
*/
public void updateNodes(final float dt, final boolean forced) {
final int sequence = this.sequence;
final int frame = this.frame;
final int counter = this.counter;
final SkeletalNode[] sortedNodes = this.sortedNodes;
final MdxModel model = (MdxModel) this.model;
final List<GenericObject> sortedGenericObjects = model.sortedGenericObjects;
final Scene scene = this.scene;
// Update the nodes
for (int i = 0, l = sortedNodes.length; i < l; i++) {
final GenericObject genericObject = sortedGenericObjects.get(i);
final SkeletalNode node = sortedNodes[i];
final GenericNode parent = node.parent;
genericObject.getVisibility(visibilityHeap, sequence, frame, counter);
final boolean objectVisible = visibilityHeap[0] > 0;
final boolean nodeVisible = forced || (parent.visible && objectVisible);
node.visible = nodeVisible;
// Every node only needs to be updated if this is a forced update, or if both
// the parent node and the generic object corresponding to this node are
// visible.
// Incoming messy code for optimizations!
if (nodeVisible) {
boolean wasDirty = false;
final GenericObject.Variants variants = genericObject.variants;
final Vector3 localLocation = node.localLocation;
final Quaternion localRotation = node.localRotation;
final Vector3 localScale = node.localScale;
// Only update the local node data if there is a need to
if (forced || variants.generic[sequence]) {
wasDirty = true;
// Translation
if (forced || variants.translation[sequence]) {
genericObject.getTranslation(translationHeap, sequence, frame, counter);
localLocation.x = translationHeap[0];
localLocation.y = translationHeap[1];
localLocation.z = translationHeap[2];
}
// Rotation
if (forced || variants.rotation[sequence]) {
genericObject.getRotation(rotationHeap, sequence, frame, counter);
localRotation.x = rotationHeap[0];
localRotation.y = rotationHeap[1];
localRotation.z = rotationHeap[2];
localRotation.w = rotationHeap[3];
}
// Scale
if (forced || variants.scale[sequence]) {
genericObject.getScale(scaleHeap, sequence, frame, counter);
localScale.x = scaleHeap[0];
localScale.y = scaleHeap[1];
localScale.z = scaleHeap[2];
}
}
final boolean wasReallyDirty = forced || wasDirty || parent.wasDirty || genericObject.anyBillboarding;
node.wasDirty = wasReallyDirty;
// If this is a forced update, or this node's local data was updated, or the
// parent node was updated, do a full world update.
if (wasReallyDirty) {
node.recalculateTransformation(scene);
}
// If there is an instance object associated with this node, and the node is
// visible (which might not be the case for a forced update!), update the
// object.
// This includes attachments and emitters.
final UpdatableObject object = node.object;
if ((object != null) && objectVisible) {
object.update(dt);
}
// Update all of the node's non-skeletal children, which will update their
// children, and so on.
node.updateChildren(dt, scene);
}
}
}
/**
* Update the batch data.
*/
public void updateBatches(final boolean forced) {
final int sequence = this.sequence;
final int frame = this.frame;
final int counter = this.counter;
final MdxModel model = (MdxModel) this.model;
final List<Geoset> geosets = model.geosets;
final List<Layer> layers = model.layers;
final float[][] geosetColors = this.geosetColors;
final float[] layerAlphas = this.layerAlphas;
final int[] layerTextures = this.layerTextures;
final float[][] uvAnims = this.uvAnims;
// Geoset
for (int i = 0, l = geosets.size(); i < l; i++) {
final Geoset geoset = geosets.get(i);
final GeosetAnimation geosetAnimation = geoset.geosetAnimation;
final float[] geosetColor = geosetColors[i];
if (geosetAnimation != null) {
// Color
if (forced || (geosetAnimation.variants.get("color")[sequence] != 0)) {
geosetAnimation.getColor(colorHeap, sequence, frame, counter);
geosetColor[0] = colorHeap[0];
geosetColor[1] = colorHeap[1];
geosetColor[2] = colorHeap[2];
}
// Alpha
if (forced || (geosetAnimation.variants.get("alpha")[sequence] != 0)) {
geosetAnimation.getAlpha(alphaHeap, sequence, frame, counter);
geosetColor[3] = alphaHeap[0];
}
}
else if (forced) {
geosetColor[0] = 1;
geosetColor[1] = 1;
geosetColor[2] = 1;
geosetColor[3] = 1;
}
}
// Layers
for (int i = 0, l = layers.size(); i < l; i++) {
final Layer layer = layers.get(i);
final TextureAnimation textureAnimation = layer.textureAnimation;
final float[] uvAnim = uvAnims[i];
// Alpha
if (forced || (layer.variants.get("alpha")[sequence] != 0)) {
layer.getAlpha(alphaHeap, sequence, frame, counter);
layerAlphas[i] = alphaHeap[0];
}
// Sprite animation
if (forced || (layer.variants.get("textureId")[sequence] != 0)) {
layer.getTextureId(textureIdHeap, sequence, frame, counter);
layerTextures[i] = (int) textureIdHeap[0];
}
if (textureAnimation != null) {
// UV translation animation
if (forced || (textureAnimation.variants.get("translation")[sequence] != 0)) {
textureAnimation.getTranslation(translationHeap, sequence, frame, counter);
uvAnim[0] = translationHeap[0];
uvAnim[1] = translationHeap[1];
}
// UV rotation animation
if (forced || (textureAnimation.variants.get("rotation")[sequence] != 0)) {
textureAnimation.getRotation(rotationHeap, sequence, frame, counter);
uvAnim[2] = rotationHeap[2];
uvAnim[3] = rotationHeap[3];
}
// UV scale animation
if (forced || (textureAnimation.variants.get("scale")[sequence] != 0)) {
textureAnimation.getScale(scaleHeap, sequence, frame, counter);
uvAnim[4] = scaleHeap[0];
}
}
else if (forced) {
uvAnim[0] = 0;
uvAnim[1] = 0;
uvAnim[2] = 0;
uvAnim[3] = 1;
uvAnim[4] = 1;
}
}
}
public void updateBoneTexture() {
if (this.boneTexture != null) {
this.worldMatricesCopyHeap.clear();
for (int i = 0, l = this.worldMatrices.length; i < l; i++) {
final Matrix4 worldMatrix = this.worldMatrices[i];
this.worldMatricesCopyHeap.put((i * 16) + 0, worldMatrix.val[Matrix4.M00]);
this.worldMatricesCopyHeap.put((i * 16) + 1, worldMatrix.val[Matrix4.M01]);
this.worldMatricesCopyHeap.put((i * 16) + 2, worldMatrix.val[Matrix4.M02]);
this.worldMatricesCopyHeap.put((i * 16) + 3, worldMatrix.val[Matrix4.M03]);
this.worldMatricesCopyHeap.put((i * 16) + 4, worldMatrix.val[Matrix4.M10]);
this.worldMatricesCopyHeap.put((i * 16) + 5, worldMatrix.val[Matrix4.M11]);
this.worldMatricesCopyHeap.put((i * 16) + 6, worldMatrix.val[Matrix4.M12]);
this.worldMatricesCopyHeap.put((i * 16) + 7, worldMatrix.val[Matrix4.M13]);
this.worldMatricesCopyHeap.put((i * 16) + 8, worldMatrix.val[Matrix4.M20]);
this.worldMatricesCopyHeap.put((i * 16) + 9, worldMatrix.val[Matrix4.M21]);
this.worldMatricesCopyHeap.put((i * 16) + 10, worldMatrix.val[Matrix4.M22]);
this.worldMatricesCopyHeap.put((i * 16) + 11, worldMatrix.val[Matrix4.M23]);
this.worldMatricesCopyHeap.put((i * 16) + 12, worldMatrix.val[Matrix4.M30]);
this.worldMatricesCopyHeap.put((i * 16) + 13, worldMatrix.val[Matrix4.M31]);
this.worldMatricesCopyHeap.put((i * 16) + 14, worldMatrix.val[Matrix4.M32]);
this.worldMatricesCopyHeap.put((i * 16) + 15, worldMatrix.val[Matrix4.M33]);
}
this.boneTexture.bindAndUpdate(this.worldMatricesCopyHeap);
}
}
@Override
public void renderOpaque() {
// TODO Auto-generated method stub
final MdxModel model = (MdxModel) this.model;
for (final GenericGroup group : model.opaqueGroups) {
group.render(this);
}
}
@Override
public void renderTranslucent() {
// TODO Auto-generated method stub
final MdxModel model = (MdxModel) this.model;
for (final GenericGroup group : model.translucentGroups) {
group.render(this);
}
}
@Override
public void load() {
// TODO Auto-generated method stub
public void updateAnimations(final float dt) {
final MdxModel model = (MdxModel) this.model;
final int sequenceId = this.sequence;
if (sequenceId != -1) {
final Sequence sequence = model.sequences.get(sequenceId);
final long[] interval = sequence.getInterval();
final int frameTime = model.viewer.frameTime;
this.frame += frameTime;
this.counter += frameTime;
this.allowParticleSpawn = true;
if (this.frame >= interval[1]) {
if ((this.sequenceLoopMode == 2) || ((this.sequenceLoopMode == 0) && (sequence.getFlags() == 0))) {
this.frame = (int) interval[0]; // TODO not cast
this.resetEventEmitters();
}
else {
this.frame = (int) interval[1]; // TODO not cast
this.counter -= frameTime;
this.allowParticleSpawn = false;
}
this.sequenceEnded = true;
}
else {
this.sequenceEnded = false;
}
}
final boolean forced = this.forced;
if (sequenceId == -1) {
if (forced) {
// Update the nodes
this.updateNodes(dt, forced);
this.updateBoneTexture();
// Update the batches
this.updateBatches(forced);
}
}
else {
// let variants = model.variants;
// if (forced || variants.nodes[sequenceId]) {
// Update the nodes
this.updateNodes(dt, forced);
this.updateBoneTexture();
// }
// if (forced || variants.batches[sequenceId]) {
// Update the batches
this.updateBatches(forced);
// }
}
this.forced = false;
}
public MdxComplexInstance setSequenceLoopMode(final int mode) {
this.sequenceLoopMode = mode;
/**
* Set the team color of this instance.
*/
public MdxComplexInstance setTeamColor(final int id) {
this.teamColor = id;
return this;
}
public void setSequence(final int sequence) {
this.sequence = sequence;
throw new UnsupportedOperationException("Not yet implemented");
/**
* Set the vertex color of this instance.
*/
public MdxComplexInstance setVertexColor(final float[] color) {
System.arraycopy(color, 0, this.vertexColor, 0, color.length);
return this;
}
/**
* Set the sequence of this instance.
*/
public MdxComplexInstance setSequence(final int id) {
final MdxModel model = (MdxModel) this.model;
this.sequence = id;
if (model.ok) {
final List<Sequence> sequences = model.sequences;
if ((id < 0) || (id > (sequences.size() - 1))) {
this.sequence = -1;
this.frame = 0;
this.allowParticleSpawn = false;
}
else {
this.frame = (int) sequences.get(id).getInterval()[0]; // TODO not cast
}
this.resetEventEmitters();
this.forced = true;
}
return this;
}
/**
* Set the seuqnece loop mode. 0 to never loop, 1 to loop based on the model,
* and 2 to always loop.
*/
public MdxComplexInstance setSequenceLoopMode(final int mode) {
this.sequenceLoopMode = mode;
return this;
}
/**
* Get an attachment node.
*/
public MdxNode getAttachment(final int id) {
final MdxModel model = (MdxModel) this.model;
final Attachment attachment = model.attachments.get(id);
if (attachment != null) {
return this.nodes[attachment.index];
}
return null;
}
/**
* Event emitters depend on keyframe index changes to emit, rather than only
* values. To work, they need to check what the last keyframe was, and only if
* it's a different one, do something. When changing sequences, these states
* need to be reset, so they can immediately emit things if needed.
*/
private void resetEventEmitters() {
/// TODO: Update this. Said Ghostwolf.
for (final EventObjectEmitter<?, ?> eventObjectEmitter : this.eventObjectEmitters) {
eventObjectEmitter.reset();
}
}
@Override
protected RenderBatch getBatch(final TextureMapper textureMapper2) {
throw new UnsupportedOperationException("NOT API");
}
}

View File

@ -25,6 +25,7 @@ public class MdxHandler extends ModelHandler {
this.extensions = new ArrayList<>();
this.extensions.add(new String[] { ".mdx", "arrayBuffer" });
this.extensions.add(new String[] { ".mdl", "text" });
this.load = true;
}
@Override
@ -36,12 +37,13 @@ public class MdxHandler extends ModelHandler {
MdxShaders.fsComplex);
Shaders.particles = viewer.webGL.createShaderProgram(MdxShaders.vsParticles, MdxShaders.fsParticles);
Shaders.simple = viewer.webGL.createShaderProgram(MdxShaders.vsSimple, MdxShaders.fsSimple);
Shaders.hd = viewer.webGL.createShaderProgram(MdxShaders.vsHd, MdxShaders.fsHd);
// Shaders.hd = viewer.webGL.createShaderProgram(MdxShaders.vsHd, MdxShaders.fsHd);
// TODO HD reforged
// If a shader failed to compile, don't allow the handler to be registered, and
// send an error instead.
return Shaders.complex.isCompiled() && Shaders.extended.isCompiled() && Shaders.particles.isCompiled()
&& Shaders.simple.isCompiled() && Shaders.hd.isCompiled();
&& Shaders.simple.isCompiled() /* && Shaders.hd.isCompiled() */;
}
@Override

View File

@ -1,11 +1,15 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import com.badlogic.gdx.graphics.GL20;
import com.etheller.warsmash.parsers.mdlx.Extent;
import com.etheller.warsmash.parsers.mdlx.MdlxModel;
import com.etheller.warsmash.parsers.mdlx.Sequence;
import com.etheller.warsmash.viewer5.ModelInstance;
import com.etheller.warsmash.viewer5.ModelViewer;
import com.etheller.warsmash.viewer5.PathSolver;
import com.etheller.warsmash.viewer5.Texture;
@ -16,7 +20,7 @@ public class MdxModel extends com.etheller.warsmash.viewer5.Model<MdxHandler> {
public SolverParams solverParams = new SolverParams();
public String name = "";
public List<Sequence> sequences = new ArrayList<>();
public List<Integer> globalSequences = new ArrayList<>();
public List<Long> globalSequences = new ArrayList<>();
public List<Material> materials = new ArrayList<>();
public List<Layer> layers = new ArrayList<>();
public List<Integer> replaceables = new ArrayList<>();
@ -34,72 +38,305 @@ public class MdxModel extends com.etheller.warsmash.viewer5.Model<MdxHandler> {
public List<RibbonEmitterObject> ribbonEmitters = new ArrayList<>();
public List<Camera> cameras = new ArrayList<>();
public List<EventObjectEmitterObject> eventObjects = new ArrayList<>();
public
private MdlxModel model;
public List<CollisionShape> collisionShapes = new ArrayList<>();
public boolean hasLayerAnims = false;
public boolean hasGeosetAnims = false;
public List<Batch> batches = new ArrayList<>();
public List<GenericObject> genericObjects = new ArrayList<>();
public List<GenericObject> sortedGenericObjects = new ArrayList<>();
public List<Integer> hierarchy = new ArrayList<>();
public List<GenericGroup> opaqueGroups = new ArrayList<>();
public List<GenericGroup> translucentGroups = new ArrayList<>();
public List<GenericGroup> simpleGroups = new ArrayList<>();
public int arrayBuffer;
public int elementBuffer;
public List<Batch> batches = new ArrayList<>(); // TODO??
public List<Object> opaqueGroups;
public List<Object> translucentGroups;
public MdxModel(final MdxHandler handler, final ModelViewer viewer, final String extension,
final PathSolver pathSolver, final String fetchUrl) {
super(handler, viewer, extension, pathSolver, fetchUrl);
}
public ModelInstance createInstance(final int type) {
if (type == 1) {
return new MdxSimpleInstance(this);
}
else {
return new MdxComplexInstance(this);
}
}
public void load(final Object bufferOrParser) throws IOException {
MdlxModel parser;
if (bufferOrParser instanceof MdlxModel) {
parser = (MdlxModel) bufferOrParser;
}
else {
parser = new MdlxModel((InputStream) bufferOrParser);
}
final ModelViewer viewer = this.viewer;
final PathSolver pathSolver = this.pathSolver;
final SolverParams solverParams = this.solverParams;
final boolean reforged = parser.getVersion() > 800;
final String texturesExt = reforged ? ".dds" : ".blp";
this.reforged = reforged;
this.name = parser.getName();
// Initialize the bounds.
final Extent extent = parser.getExtent();
this.bounds.fromExtents(extent.getMin(), extent.getMax());
// Sequences
this.sequences.addAll(parser.getSequences());
// Global sequences
this.globalSequences.addAll(parser.getGlobalSequences());
// Texture animations
for (final com.etheller.warsmash.parsers.mdlx.TextureAnimation textureAnimation : parser
.getTextureAnimations()) {
this.textureAnimations.add(new TextureAnimation(this, textureAnimation));
}
// Materials
int layerId = 0;
for (final com.etheller.warsmash.parsers.mdlx.Material material : parser.getMaterials()) {
final List<Layer> layers = new ArrayList<>();
for (final com.etheller.warsmash.parsers.mdlx.Layer layer : material.getLayers()) {
final Layer vLayer = new Layer(this, layer, layerId++, material.getPriorityPlane());
layers.add(vLayer);
this.layers.add(vLayer);
}
this.materials.add(new Material(this, "" /* material.shader */, layers));
if (false /* !"".equals(material.shader) */) {
this.hd = true;
}
}
if (reforged) {
solverParams.reforged = true;
}
if (this.hd) {
solverParams.hd = true;
}
final GL20 gl = viewer.gl;
boolean usingTeamTextures = false;
// Textures.
for (final com.etheller.warsmash.parsers.mdlx.Texture texture : parser.getTextures()) {
String path = texture.getPath();
final int replaceableId = texture.getReplaceableId();
final int flags = texture.getFlags();
if (replaceableId != 0) {
path = "ReplaceableTextures\\" + ReplaceableIds.getPathString(replaceableId) + ".blp";
if ((replaceableId == 1) || (replaceableId == 2)) {
usingTeamTextures = true;
}
}
if (reforged && !path.endsWith(".dds")) {
path = path.substring(0, path.length() - 4) + ".dds";
}
final Texture viewerTexture = (Texture) viewer.load(path, pathSolver, solverParams);
// When the texture will load, it will apply its wrap modes.
if (!viewerTexture.loaded) {
if ((flags & 0x1) != 0) {
viewerTexture.setWrapS(true);
}
if ((flags & 0x2) != 0) {
viewerTexture.setWrapT(true);
}
}
this.replaceables.add(replaceableId);
this.textures.add(viewerTexture);
}
// Start loading the team color and glow textures if this model uses them and
// they weren't loaded previously.
if (usingTeamTextures) {
final List<Texture> teamColors = reforged ? MdxHandler.reforgedTeamColors : MdxHandler.teamColors;
final List<Texture> teamGlows = reforged ? MdxHandler.reforgedTeamGlows : MdxHandler.teamGlows;
if (teamColors.isEmpty()) {
for (int i = 0; i < 28; i++) {
final String id = ReplaceableIds.getIdString(i);
teamColors.add((Texture) viewer.load("ReplaceableTextures\\TeamColor\\TeamColor" + id + texturesExt,
pathSolver, solverParams));
teamGlows.add((Texture) viewer.load("ReplaceableTextures\\TeamGlow\\TeamGlow" + id + texturesExt,
pathSolver, solverParams));
}
}
}
// Geoset animations
for (final com.etheller.warsmash.parsers.mdlx.GeosetAnimation geosetAnimation : parser.getGeosetAnimations()) {
this.geosetAnimations.add(new GeosetAnimation(this, geosetAnimation));
}
// Geosets
SetupGeosets.setupGeosets(this, parser.getGeosets());
this.pivotPoints = parser.getPivotPoints();
// Tracks the IDs of all generic objects
int objectId = 0;
// Bones
for (final com.etheller.warsmash.parsers.mdlx.Bone bone : parser.getBones()) {
this.bones.add(new Bone(this, bone, objectId++));
}
// Lights
for (final com.etheller.warsmash.parsers.mdlx.Light light : parser.getLights()) {
this.lights.add(new Light(this, light, objectId++));
}
// Helpers
for (final com.etheller.warsmash.parsers.mdlx.Helper helper : parser.getHelpers()) {
this.helpers.add(new Helper(this, helper, objectId++));
}
// Attachments
for (final com.etheller.warsmash.parsers.mdlx.Attachment attachment : parser.getAttachments()) {
this.attachments.add(new Attachment(this, attachment, objectId++));
}
// Particle Emitters
for (final com.etheller.warsmash.parsers.mdlx.ParticleEmitter particleEmitter : parser.getParticleEmitters()) {
this.particleEmitters.add(new ParticleEmitterObject(this, particleEmitter, objectId++));
}
// Particle Emitters 2
for (final com.etheller.warsmash.parsers.mdlx.ParticleEmitter2 particleEmitter2 : parser
.getParticleEmitters2()) {
this.particleEmitters2.add(new ParticleEmitter2Object(this, particleEmitter2, objectId++));
}
// Ribbon emitters
for (final com.etheller.warsmash.parsers.mdlx.RibbonEmitter ribbonEmitter : parser.getRibbonEmitters()) {
this.ribbonEmitters.add(new RibbonEmitterObject(this, ribbonEmitter, objectId++));
}
// Camera
for (final com.etheller.warsmash.parsers.mdlx.Camera camera : parser.getCameras()) {
this.cameras.add(new Camera(this, camera));
}
// Event objects
for (final com.etheller.warsmash.parsers.mdlx.EventObject eventObject : parser.getEventObjects()) {
this.eventObjects.add(new EventObjectEmitterObject(this, eventObject, objectId++));
}
// Collision shapes
for (final com.etheller.warsmash.parsers.mdlx.CollisionShape collisionShape : parser.getCollisionShapes()) {
this.collisionShapes.add(new CollisionShape(this, collisionShape, objectId++));
}
// One array for all generic objects.
this.genericObjects.addAll(this.bones);
this.genericObjects.addAll(this.lights);
this.genericObjects.addAll(this.helpers);
this.genericObjects.addAll(this.attachments);
this.genericObjects.addAll(this.particleEmitters);
this.genericObjects.addAll(this.particleEmitters2);
this.genericObjects.addAll(this.ribbonEmitters);
this.genericObjects.addAll(this.eventObjects);
this.genericObjects.addAll(this.collisionShapes);
// Render groups.
SetupGroups.setupGroups(this);
// SimpleInstance render group.
SetupSimpleGroups.setupSimpleGroups(this);
// Creates the sorted indices array of the generic objects
this.setupHierarchy(-1);
// Keep a sorted array.
for (int i = 0, l = this.genericObjects.size(); i < l; i++) {
this.sortedGenericObjects.add(this.genericObjects.get(this.hierarchy.get(i)));
}
}
private void setupHierarchy(final int parent) {
for (int i = 0, l = this.genericObjects.size(); i < l; i++) {
final GenericObject object = this.genericObjects.get(i);
if (object.parentId == parent) {
this.hierarchy.add(i);
this.setupHierarchy(object.objectId);
}
}
}
@Override
protected void lateLoad() {
// TODO Auto-generated method stub
}
@Override
protected void load(final InputStream src, final Object options) {
// TODO Auto-generated method stub
try {
this.load(src);
}
catch (final IOException e) {
throw new RuntimeException(e);
}
}
@Override
protected void error(final Exception e) {
// TODO Auto-generated method stub
e.printStackTrace();
}
// TODO typing
public List<Long> getGlobalSequences() {
return this.model.getGlobalSequences();
return this.globalSequences;
}
public List<Sequence> getSequences() {
return this.model.getSequences();
return this.sequences;
}
public List<float[]> getPivotPoints() {
return this.model.getPivotPoints();
return this.pivotPoints;
}
public List<GeosetAnimation> getGeosetAnimations() {
throw new UnsupportedOperationException("NYI");
return this.geosetAnimations;
}
public List<Texture> getTextures() {
throw new UnsupportedOperationException("NYI");
return this.textures;
}
public List<Material> getMaterials() {
throw new UnsupportedOperationException("NYI");
return this.materials;
}
public List<TextureAnimation> getTextureAnimations() {
throw new UnsupportedOperationException("NYI");
return this.textureAnimations;
}
public List<Geoset> getGeosets() {
throw new UnsupportedOperationException("NYI");
return this.geosets;
}
private static final class SolverParams {

View File

@ -0,0 +1,13 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.etheller.warsmash.util.Descriptor;
public class MdxNodeDescriptor implements Descriptor<MdxNode> {
public static final MdxNodeDescriptor INSTANCE = new MdxNodeDescriptor();
@Override
public MdxNode create() {
return new MdxNode();
}
}

View File

@ -0,0 +1,128 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import java.nio.FloatBuffer;
import java.util.List;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.badlogic.gdx.math.Matrix4;
import com.etheller.warsmash.viewer5.Model;
import com.etheller.warsmash.viewer5.ModelInstance;
import com.etheller.warsmash.viewer5.ModelViewer;
import com.etheller.warsmash.viewer5.RenderBatch;
import com.etheller.warsmash.viewer5.Scene;
import com.etheller.warsmash.viewer5.Texture;
import com.etheller.warsmash.viewer5.TextureMapper;
import com.etheller.warsmash.viewer5.gl.ANGLEInstancedArrays;
import com.etheller.warsmash.viewer5.gl.ClientBuffer;
import com.etheller.warsmash.viewer5.gl.WebGL;
public class MdxRenderBatch extends RenderBatch {
public MdxRenderBatch(final Scene scene, final Model<?> model, final TextureMapper textureMapper) {
super(scene, model, textureMapper);
}
private void bindAndUpdateBuffer(final ClientBuffer buffer) {
final int count = this.count;
final List<ModelInstance> instances = this.instances;
// Ensure there is enough memory for all of the instances data.
buffer.reserve(count * 48);
final FloatBuffer floatView = buffer.floatView;
// "Copy" the instances into the buffer
for (int i = 0; i < count; i++) {
final ModelInstance instance = instances.get(i);
final Matrix4 worldMatrix = instance.worldMatrix;
final int offset = i * 12;
floatView.put(offset + 0, worldMatrix.val[Matrix4.M00]);
floatView.put(offset + 1, worldMatrix.val[Matrix4.M01]);
floatView.put(offset + 2, worldMatrix.val[Matrix4.M02]);
floatView.put(offset + 3, worldMatrix.val[Matrix4.M03]);
floatView.put(offset + 4, worldMatrix.val[Matrix4.M10]);
floatView.put(offset + 5, worldMatrix.val[Matrix4.M11]);
floatView.put(offset + 6, worldMatrix.val[Matrix4.M12]);
floatView.put(offset + 7, worldMatrix.val[Matrix4.M13]);
floatView.put(offset + 8, worldMatrix.val[Matrix4.M20]);
floatView.put(offset + 9, worldMatrix.val[Matrix4.M21]);
floatView.put(offset + 10, worldMatrix.val[Matrix4.M22]);
floatView.put(offset + 11, worldMatrix.val[Matrix4.M23]);
}
// Update the buffer.
buffer.bindAndUpdate(count * 48);
}
@Override
public void render() {
final int count = this.count;
if (count != 0) {
final MdxModel model = (MdxModel) this.model;
final List<Batch> batches = model.batches;
final List<Texture> textures = model.textures;
final ModelViewer viewer = model.viewer;
final GL20 gl = viewer.gl;
final WebGL webGL = viewer.webGL;
final ANGLEInstancedArrays instancedArrays = webGL.instancedArrays;
final ShaderProgram shader = MdxHandler.Shaders.simple;
final int m0 = shader.getAttributeLocation("a_m0");
final int m1 = shader.getAttributeLocation("a_m1");
final int m2 = shader.getAttributeLocation("a_m2");
final int m3 = shader.getAttributeLocation("a_m3");
final ClientBuffer buffer = viewer.buffer;
final TextureMapper textureMapper = this.textureMapper;
webGL.useShaderProgram(shader);
this.bindAndUpdateBuffer(buffer);
shader.setVertexAttribute(m0, 3, GL20.GL_FLOAT, false, 48, 0);
shader.setVertexAttribute(m1, 3, GL20.GL_FLOAT, false, 48, 12);
shader.setVertexAttribute(m2, 3, GL20.GL_FLOAT, false, 48, 24);
shader.setVertexAttribute(m3, 3, GL20.GL_FLOAT, false, 48, 36);
shader.setUniformMatrix4fv("u_VP", this.scene.camera.viewProjectionMatrix.val, 0,
this.scene.camera.viewProjectionMatrix.val.length);
gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, model.arrayBuffer);
gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, model.elementBuffer);
instancedArrays.glVertexAttribDivisorANGLE(m0, 1);
instancedArrays.glVertexAttribDivisorANGLE(m1, 1);
instancedArrays.glVertexAttribDivisorANGLE(m2, 1);
instancedArrays.glVertexAttribDivisorANGLE(m3, 1);
for (final GenericGroup group : model.simpleGroups) {
for (final Integer object : group.objects) {
final Batch batch = batches.get(object);
final Geoset geoset = batch.geoset;
final Layer layer = batch.layer;
final Texture texture = textures.get(layer.textureId);
shader.setUniformi("u_texture", 0);
Texture mappedTexture = textureMapper.get(texture);
if (mappedTexture == null) {
mappedTexture = texture;
}
viewer.webGL.bindTexture(mappedTexture, 0);
layer.bind(shader);
geoset.bindSimple(shader);
geoset.renderSimple(count);
}
}
instancedArrays.glVertexAttribDivisorANGLE(m3, 0);
instancedArrays.glVertexAttribDivisorANGLE(m2, 0);
instancedArrays.glVertexAttribDivisorANGLE(m1, 0);
instancedArrays.glVertexAttribDivisorANGLE(m0, 0);
}
}
}

View File

@ -56,7 +56,7 @@ public class MdxShaders {
" }";
public static final String vsSimple = "\r\n" + //
" uniform mat4 u_mvp;\r\n" + //
" uniform mat4 u_VP;\r\n" + //
" attribute vec3 a_m0;\r\n" + //
" attribute vec3 a_m1;\r\n" + //
" attribute vec3 a_m2;\r\n" + //
@ -66,22 +66,22 @@ public class MdxShaders {
" varying vec2 v_uv;\r\n" + //
" void main() {\r\n" + //
" v_uv = a_uv;\r\n" + //
" gl_Position = u_mvp * mat4(a_m0, 0.0, a_m1, 0.0, a_m2, 0.0, a_m3, 1.0) * vec4(a_position, 1.0);\r\n"
+ //
" }";
" gl_Position = u_VP * mat4(a_m0, 0.0, a_m1, 0.0, a_m2, 0.0, a_m3, 1.0) * vec4(a_position, 1.0);\r\n" + //
" }\r\n";
public static final String fsSimple = "\r\n" + //
" precision mediump float;\r\n" + //
" uniform sampler2D u_texture;\r\n" + //
" uniform float u_filterMode;\r\n" + //
" varying vec2 v_uv;\r\n" + //
" void main() {\r\n" + //
" vec4 color = texture2D(u_texture, v_uv);\r\n" + //
" // 1bit Alpha\r\n" + //
" if (u_filterMode == 1.0 && color.a < 0.75) {\r\n" + //
" discard;\r\n" + //
" }\r\n" + //
" //if (u_filterMode == 1.0 && color.a < 0.75) {\r\n" + //
" //discard;\r\n" + //
" //}\r\n" + //
" gl_FragColor = color;\r\n" + //
" }";
" }\r\n";
public static final String vsComplex = Shaders.boneTexture + "\r\n" + //
" uniform mat4 u_mvp;\r\n" + //

View File

@ -1,19 +1,16 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.etheller.warsmash.viewer5.BatchedInstance;
import com.etheller.warsmash.viewer5.Model;
import com.etheller.warsmash.viewer5.ModelInstance;
import com.etheller.warsmash.viewer5.RenderBatch;
import com.etheller.warsmash.viewer5.TextureMapper;
public class MdxSimpleInstance extends ModelInstance {
public class MdxSimpleInstance extends BatchedInstance {
public MdxSimpleInstance(final Model model) {
super(model);
}
@Override
public boolean isBatched() {
return true;
}
@Override
public void updateAnimations(final float dt) {
}
@ -34,4 +31,9 @@ public class MdxSimpleInstance extends ModelInstance {
public void load() {
}
@Override
public RenderBatch getBatch(final TextureMapper textureMapper) {
return new MdxRenderBatch(this.scene, this.model, textureMapper);
}
}

View File

@ -61,7 +61,7 @@ public class ParticleEmitter2Object extends GenericObject implements EmitterObje
}
else {
this.internalTexture = (Texture) model.viewer.load(
"ReplaceableTextures\\" + ReplaceableIds.get(replaceableId) + ".blp", model.pathSolver,
"ReplaceableTextures\\" + ReplaceableIds.getPathString(replaceableId) + ".blp", model.pathSolver,
model.solverParams);
}

View File

@ -6,7 +6,7 @@ import com.etheller.warsmash.util.Interpolator;
public class QuaternionSd extends Sd<float[]> {
public QuaternionSd(final MdxModel model, final Timeline<float[]> timeline) {
super(model, timeline);
super(model, timeline, SdArrayDescriptor.FLOAT_ARRAY);
}
@Override

View File

@ -5,18 +5,35 @@ import java.util.Map;
public class ReplaceableIds {
private static final Map<Long, String> ID_TO_STR = new HashMap<>();
private static final Map<Long, String> REPLACEABLE_ID_TO_STR = new HashMap<>();
static {
for (int i = 0; i < 28; i++) {
ID_TO_STR.put(Long.valueOf(i), String.format("%2d", i).replace(' ', '0'));
}
REPLACEABLE_ID_TO_STR.put(Long.valueOf(1), "TeamColor\\TeamColor00");
REPLACEABLE_ID_TO_STR.put(Long.valueOf(2), "TeamGlow\\TeamGlow00");
REPLACEABLE_ID_TO_STR.put(Long.valueOf(11), "Cliff\\Cliff0");
REPLACEABLE_ID_TO_STR.put(Long.valueOf(21), ""); // Used by all cursor models (HumanCursor, OrcCursor,
// UndeadCursor, NightElfCursor)
REPLACEABLE_ID_TO_STR.put(Long.valueOf(31), "LordaeronTree\\LordaeronSummerTree");
REPLACEABLE_ID_TO_STR.put(Long.valueOf(32), "AshenvaleTree\\AshenTree");
REPLACEABLE_ID_TO_STR.put(Long.valueOf(33), "BarrensTree\\BarrensTree");
REPLACEABLE_ID_TO_STR.put(Long.valueOf(34), "NorthrendTree\\NorthTree");
REPLACEABLE_ID_TO_STR.put(Long.valueOf(35), "Mushroom\\MushroomTree");
REPLACEABLE_ID_TO_STR.put(Long.valueOf(36), "RuinsTree\\RuinsTree");
REPLACEABLE_ID_TO_STR.put(Long.valueOf(37), "OutlandMushroomTree\\MushroomTree");
}
public static void main(final String[] args) {
System.out.println(ID_TO_STR);
}
public static String get(final long replaceableId) {
public static String getIdString(final long replaceableId) {
return ID_TO_STR.get(replaceableId);
}
public static String getPathString(final long replaceableId) {
return REPLACEABLE_ID_TO_STR.get(replaceableId);
}
}

View File

@ -6,7 +6,7 @@ import com.etheller.warsmash.util.RenderMathUtils;
public class ScalarSd extends Sd<float[]> {
public ScalarSd(final MdxModel model, final Timeline<float[]> timeline) {
super(model, timeline);
super(model, timeline, SdArrayDescriptor.FLOAT_ARRAY);
}
@Override

View File

@ -85,7 +85,7 @@ public abstract class Sd<TYPE> {
}
public Sd(final MdxModel model, final Timeline<TYPE> timeline) {
public Sd(final MdxModel model, final Timeline<TYPE> timeline, final SdArrayDescriptor<TYPE> arrayDescriptor) {
final List<Long> globalSequences = model.getGlobalSequences();
final int globalSequenceId = timeline.getGlobalSequenceId();
final Integer forcedInterp = forcedInterpMap.get(timeline.getName());
@ -105,13 +105,14 @@ public abstract class Sd<TYPE> {
if ((globalSequenceId != -1) && (globalSequences.size() > 0)) {
this.globalSequence = new SdSequence<TYPE>(this, 0, globalSequences.get(globalSequenceId).longValue(),
timeline, true);
timeline, true, arrayDescriptor);
}
else {
for (final Sequence sequence : model.getSequences()) {
final long[] interval = sequence.getInterval();
this.sequences.add(new SdSequence<TYPE>(this, interval[0], interval[1], timeline, false));
this.sequences
.add(new SdSequence<TYPE>(this, interval[0], interval[1], timeline, false, arrayDescriptor));
}
}
}

View File

@ -0,0 +1,24 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
public interface SdArrayDescriptor<TYPE> {
SdArrayDescriptor<Object> GENERIC = new SdArrayDescriptor<Object>() {
@Override
public Object[] create(final int size) {
return new Object[size];
}
};
SdArrayDescriptor<float[]> FLOAT_ARRAY = new SdArrayDescriptor<float[]>() {
@Override
public float[][] create(final int size) {
return new float[size][];
}
};
SdArrayDescriptor<long[]> LONG_ARRAY = new SdArrayDescriptor<long[]>() {
@Override
public long[][] create(final int size) {
return new long[size][];
}
};
TYPE[] create(int size);
}

View File

@ -18,7 +18,7 @@ public final class SdSequence<TYPE> {
public boolean constant;
public SdSequence(final Sd<TYPE> sd, final long start, final long end, final Timeline<TYPE> timeline,
final boolean isGlobalSequence) {
final boolean isGlobalSequence, final SdArrayDescriptor<TYPE> arrayDescriptor) {
this.sd = sd;
this.start = start;
this.end = end;
@ -120,13 +120,17 @@ public final class SdSequence<TYPE> {
}
this.frames = new long[framesBuilder.size()];
for (int i = 0; i < framesBuilder.size(); i++) {
frames[i] = framesBuilder.get(i);
this.frames[i] = framesBuilder.get(i);
}
this.values = valuesBuilder.toArray((TYPE[]) new Object[valuesBuilder.size()]);
this.inTans = inTansBuilder.toArray((TYPE[]) new Object[inTansBuilder.size()]);
this.outTans = outTansBuilder.toArray((TYPE[]) new Object[outTansBuilder.size()]);
this.values = valuesBuilder.toArray(arrayDescriptor.create(valuesBuilder.size()));
this.inTans = inTansBuilder.toArray(arrayDescriptor.create(inTansBuilder.size()));
this.outTans = outTansBuilder.toArray(arrayDescriptor.create(outTansBuilder.size()));
}
// private TYPE[] makeArray(final int size) {
// return (TYPE[]) new Object[size];
// }
public int getValue(final TYPE out, final long frame) {
final int l = this.frames.length;
@ -145,7 +149,8 @@ public final class SdSequence<TYPE> {
if (this.frames[i] > frame) {
final long start = this.frames[i = 1];
final long end = this.frames[i];
final float t = RenderMathUtils.clamp((frame - start) / (end - start), 0, 1);
final float t = RenderMathUtils.clamp(((end - start) == 0 ? 0 : ((frame - start) / (end - start))),
0, 1);
this.sd.interpolate(out, this.values, this.inTans, this.outTans, i - 1, i, t);

View File

@ -2,7 +2,7 @@ package com.etheller.warsmash.viewer5.handlers.mdx;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@ -11,8 +11,8 @@ import com.badlogic.gdx.graphics.GL20;
public class SetupGeosets {
private static final int NORMAL_BATCH = 0;
private static final int EXTENDED_BATCH = 0;
private static final int REFORGED_BATCH = 0;
private static final int EXTENDED_BATCH = 1;
private static final int REFORGED_BATCH = 2;
public static void setupGeosets(final MdxModel model,
final List<com.etheller.warsmash.parsers.mdlx.Geoset> geosets) {
@ -157,30 +157,52 @@ public class SetupGeosets {
}
// Positions.
gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, positionOffset, positions.length,
FloatBuffer.wrap(positions));
gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, positionOffset, positions.length, wrap(positions));
positionOffset += positions.length * 4;
// Normals.
gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, normalOffset, normals.length, FloatBuffer.wrap(normals));
gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, normalOffset, normals.length, wrap(normals));
normalOffset += normals.length * 4;
// Texture coordinates.
for (final float[] uvSet : uvSets) {
gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, uvOffset, uvSet.length, FloatBuffer.wrap(uvSet));
gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, uvOffset, uvSet.length, wrap(uvSet));
uvOffset += uvSet.length * 4;
}
// Skin.
gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, skinOffset, skin.length, ByteBuffer.wrap(skin));
gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, skinOffset, skin.length, wrap(skin));
skinOffset += skin.length * 1;
// Faces.
gl.glBufferSubData(GL20.GL_ELEMENT_ARRAY_BUFFER, faceOffset, faces.length, IntBuffer.wrap(faces));
gl.glBufferSubData(GL20.GL_ELEMENT_ARRAY_BUFFER, faceOffset, faces.length, wrapFaces(faces));
faceOffset += faces.length * 4;
}
}
}
}
private static ShortBuffer wrapFaces(final int[] faces) {
final ShortBuffer wrapper = ByteBuffer.allocateDirect(faces.length * 2).asShortBuffer();
for (final int face : faces) {
wrapper.put((short) face);
}
wrapper.clear();
return wrapper;
}
private static ByteBuffer wrap(final byte[] skin) {
final ByteBuffer wrapper = ByteBuffer.allocateDirect(skin.length);
wrapper.put(skin);
wrapper.clear();
return wrapper;
}
private static FloatBuffer wrap(final float[] positions) {
final FloatBuffer wrapper = ByteBuffer.allocateDirect(positions.length * 4).asFloatBuffer();
wrapper.put(positions);
wrapper.clear();
return wrapper;
}
}

View File

@ -71,8 +71,8 @@ public class SetupGroups {
}
}
final List<Object> opaqueGroups = model.opaqueGroups;
final List<Object> translucentGroups = model.translucentGroups;
final List<GenericGroup> opaqueGroups = model.opaqueGroups;
final List<GenericGroup> translucentGroups = model.translucentGroups;
GenericGroup currentGroup = null;
for (final Batch object : opaqueBatches) {
@ -82,7 +82,8 @@ public class SetupGroups {
opaqueGroups.add(currentGroup);
}
currentGroup.objects.add(object.index);
final int index = object.index;
currentGroup.objects.add(index);
}
// Sort between all of the translucent batches and emitters that have priority
@ -109,12 +110,13 @@ public class SetupGroups {
if ((object instanceof Batch /* || object instanceof ReforgedBatch */)
|| (object instanceof EmitterObject)) {
if ((currentGroup == null) || !matchingGroup(currentGroup, objects)) {
currentGroup = createMatchingGroup(model, objects);
currentGroup = createMatchingGroup(model, object);
translucentGroups.add(currentGroup);
}
currentGroup.objects.add(((GenericIndexed) object).getIndex());
final int index = ((GenericIndexed) object).getIndex();
currentGroup.objects.add(index);
}
}
}

View File

@ -0,0 +1,62 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import java.util.List;
public class SetupSimpleGroups {
private static final float[] alphaHeap = new float[1];
public static boolean isBatchSimple(final Batch batch) {
final GeosetAnimation geosetAnimation = batch.geoset.geosetAnimation;
if (geosetAnimation != null) {
geosetAnimation.getAlpha(alphaHeap, 0, 0, 0);
if (alphaHeap[0] <= 0.01) {
return false;
}
}
Layer layer;
if (batch instanceof Batch) {
layer = batch.layer;
}
else {
throw new IllegalStateException("reforged?"); // TODO
// layer = batch.material.layers[0];
}
layer.getAlpha(alphaHeap, 0, 0, 0);
if (alphaHeap[0] < 0.01) {
return false;
}
return true;
}
public static void setupSimpleGroups(final MdxModel model) {
final List<Batch> batches = model.batches;
final List<GenericGroup> simpleGroups = model.simpleGroups;
for (final GenericGroup group : model.opaqueGroups) {
GenericGroup simpleGroup;
if (group instanceof BatchGroup) {
simpleGroup = new BatchGroup(model, ((BatchGroup) group).isExtended);
}
else {
throw new IllegalStateException("reforged?"); // TODO
// simpleGroup = new ReforgedBatchGroup(model, group.shader);
}
for (final Integer object : group.objects) {
if (isBatchSimple(batches.get(object))) {
simpleGroup.objects.add(object);
}
}
simpleGroups.add(simpleGroup);
}
}
}

View File

@ -8,6 +8,10 @@ public class TextureAnimation extends AnimatedObject {
public TextureAnimation(final MdxModel model,
final com.etheller.warsmash.parsers.mdlx.TextureAnimation textureAnimation) {
super(model, textureAnimation);
this.addVariants(AnimationMap.KTAT.getWar3id(), "translation");
this.addVariants(AnimationMap.KTAR.getWar3id(), "rotation");
this.addVariants(AnimationMap.KTAS.getWar3id(), "scale");
}
public int getTranslation(final float[] out, final int sequence, final int frame, final int counter) {

View File

@ -6,7 +6,7 @@ import com.etheller.warsmash.util.RenderMathUtils;
public class UInt32Sd extends Sd<long[]> {
public UInt32Sd(final MdxModel model, final Timeline<long[]> timeline) {
super(model, timeline);
super(model, timeline, SdArrayDescriptor.LONG_ARRAY);
}
@Override

View File

@ -6,7 +6,7 @@ import com.etheller.warsmash.util.Interpolator;
public class VectorSd extends Sd<float[]> {
public VectorSd(final MdxModel model, final Timeline<float[]> timeline) {
super(model, timeline);
super(model, timeline, SdArrayDescriptor.FLOAT_ARRAY);
}
@Override

View File

@ -1,12 +1,37 @@
package com.etheller.warsmash.desktop;
import org.lwjgl.opengl.GL31;
import org.lwjgl.opengl.GL33;
import com.badlogic.gdx.backends.lwjgl.LwjglApplication;
import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration;
import com.etheller.warsmash.WarsmashGdxGame;
import com.etheller.warsmash.viewer5.gl.ANGLEInstancedArrays;
import com.etheller.warsmash.viewer5.gl.Extensions;
public class DesktopLauncher {
public static void main (String[] arg) {
LwjglApplicationConfiguration config = new LwjglApplicationConfiguration();
public static void main(final String[] arg) {
Extensions.angleInstancedArrays = new ANGLEInstancedArrays() {
@Override
public void glVertexAttribDivisorANGLE(final int index, final int divisor) {
GL33.glVertexAttribDivisor(index, divisor);
}
@Override
public void glDrawElementsInstancedANGLE(final int mode, final int count, final int type,
final int indicesOffset, final int instanceCount) {
GL31.glDrawElementsInstanced(mode, count, type, indicesOffset, instanceCount);
}
@Override
public void glDrawArraysInstancedANGLE(final int mode, final int first, final int count,
final int instanceCount) {
GL31.glDrawArraysInstanced(mode, first, count, instanceCount);
}
};
final LwjglApplicationConfiguration config = new LwjglApplicationConfiguration();
config.useGL30 = true;
config.gles30ContextMinorVersion = 3;
new LwjglApplication(new WarsmashGdxGame(), config);
}
}