mirror of
https://github.com/Retera/WarsmashModEngine.git
synced 2022-07-31 17:38:59 +02:00
More progress, working on getting viewer
This commit is contained in:
parent
f0da595d20
commit
7175a28753
46
core/src/com/etheller/warsmash/datasources/DataSource.java
Normal file
46
core/src/com/etheller/warsmash/datasources/DataSource.java
Normal file
@ -0,0 +1,46 @@
|
||||
package com.etheller.warsmash.datasources;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Collection;
|
||||
|
||||
public interface DataSource {
|
||||
/**
|
||||
* Efficiently return a stream instance that will read the data source file's
|
||||
* contents directly from the data source. For example, this will read a file
|
||||
* within an MPQ or CASC storage without extracting it.
|
||||
*
|
||||
* @param filepath
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
InputStream getResourceAsStream(String filepath) throws IOException;
|
||||
|
||||
/**
|
||||
* Inefficiently copy a file from the data source onto the Hard Drive of the
|
||||
* computer, and then return a java File instance pointed at the file.
|
||||
*
|
||||
* @param filepath
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
File getFile(String filepath) throws IOException;
|
||||
|
||||
/**
|
||||
* Returns true if the data source contains a valid entry for a particular file.
|
||||
* Some data sources (MPQs) may contain files for which this returns true, even
|
||||
* though they cannot list the file in their Listfile.
|
||||
*
|
||||
* @param filepath
|
||||
* @return
|
||||
*/
|
||||
boolean has(String filepath);
|
||||
|
||||
/**
|
||||
* @return a list of data source contents, or null if no list is provided
|
||||
*/
|
||||
Collection<String> getListfile();
|
||||
|
||||
void close() throws IOException;
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package com.etheller.warsmash.datasources;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public interface DataSourceDescriptor extends Serializable {
|
||||
DataSource createDataSource();
|
||||
|
||||
String getDisplayName();
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
package com.etheller.warsmash.datasources;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class FolderDataSource implements DataSource {
|
||||
|
||||
private final Path folderPath;
|
||||
private final Set<String> listfile;
|
||||
|
||||
public FolderDataSource(final Path folderPath) {
|
||||
this.folderPath = folderPath;
|
||||
this.listfile = new HashSet<>();
|
||||
try {
|
||||
Files.walk(folderPath).filter(Files::isRegularFile).forEach(new Consumer<Path>() {
|
||||
@Override
|
||||
public void accept(final Path t) {
|
||||
FolderDataSource.this.listfile.add(folderPath.relativize(t).toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (final IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getResourceAsStream(final String filepath) throws IOException {
|
||||
if (!has(filepath)) {
|
||||
return null;
|
||||
}
|
||||
return Files.newInputStream(this.folderPath.resolve(filepath), StandardOpenOption.READ);
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getFile(final String filepath) throws IOException {
|
||||
if (!has(filepath)) {
|
||||
return null;
|
||||
}
|
||||
return new File(this.folderPath.toString() + File.separatorChar + filepath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean has(final String filepath) {
|
||||
if ("".equals(filepath)) {
|
||||
return false; // special case for folder data source, dont do this
|
||||
}
|
||||
return Files.exists(this.folderPath.resolve(filepath));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getListfile() {
|
||||
return this.listfile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
package com.etheller.warsmash.datasources;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
|
||||
public class FolderDataSourceDescriptor implements DataSourceDescriptor {
|
||||
/**
|
||||
* Generated serial id
|
||||
*/
|
||||
private static final long serialVersionUID = -476724730967709309L;
|
||||
private final String folderPath;
|
||||
|
||||
public FolderDataSourceDescriptor(final String folderPath) {
|
||||
this.folderPath = folderPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataSource createDataSource() {
|
||||
return new FolderDataSource(Paths.get(this.folderPath));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return "Folder: " + this.folderPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = (prime * result) + ((this.folderPath == null) ? 0 : this.folderPath.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final FolderDataSourceDescriptor other = (FolderDataSourceDescriptor) obj;
|
||||
if (this.folderPath == null) {
|
||||
if (other.folderPath != null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (!this.folderPath.equals(other.folderPath)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
package com.etheller.warsmash.util;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.badlogic.gdx.math.Matrix4;
|
||||
import com.badlogic.gdx.math.Quaternion;
|
||||
import com.badlogic.gdx.math.Rectangle;
|
||||
@ -287,4 +289,46 @@ public enum RenderMathUtils {
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
public static int testCell(final List<Vector4> planes, final int left, final int right, final int bottom,
|
||||
final int top, int first) {
|
||||
if (first == -1) {
|
||||
first = 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
final int index = (first + i) % 6;
|
||||
final Vector4 plane = planes.get(index);
|
||||
|
||||
if ((distance2Plane2(plane, left, bottom) < 0) && (distance2Plane2(plane, left, top) < 0)
|
||||
&& (distance2Plane2(plane, right, top) < 0) && (distance2Plane2(plane, right, bottom) < 0)) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static int testCell(final Vector4[] planes, final int left, final int right, final int bottom, final int top,
|
||||
int first) {
|
||||
if (first == -1) {
|
||||
first = 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
final int index = (first + i) % 6;
|
||||
final Vector4 plane = planes[index];
|
||||
|
||||
if ((distance2Plane2(plane, left, bottom) < 0) && (distance2Plane2(plane, left, top) < 0)
|
||||
&& (distance2Plane2(plane, right, top) < 0) && (distance2Plane2(plane, right, bottom) < 0)) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static float distance2Plane2(final Vector4 plane, final int px, final int py) {
|
||||
return (plane.x * px) + (plane.y * py) + plane.w;
|
||||
}
|
||||
}
|
||||
|
47
core/src/com/etheller/warsmash/viewer5/AudioContext.java
Normal file
47
core/src/com/etheller/warsmash/viewer5/AudioContext.java
Normal file
@ -0,0 +1,47 @@
|
||||
package com.etheller.warsmash.viewer5;
|
||||
|
||||
public class AudioContext {
|
||||
private boolean running = false;
|
||||
public Listener listener = new Listener();
|
||||
|
||||
public void suspend() {
|
||||
this.running = false;
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return this.running;
|
||||
}
|
||||
|
||||
public void resume() {
|
||||
this.running = true;
|
||||
}
|
||||
|
||||
public static class Listener {
|
||||
private float x;
|
||||
private float y;
|
||||
private float z;
|
||||
private float forwardX;
|
||||
private float forwardY;
|
||||
private float forwardZ;
|
||||
private float upX;
|
||||
private float upY;
|
||||
private float upZ;
|
||||
|
||||
public void setPosition(final float x, final float y, final float z) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
public void setOrientation(final float forwardX, final float forwardY, final float forwardZ, final float upX,
|
||||
final float upY, final float upZ) {
|
||||
this.forwardX = forwardX;
|
||||
this.forwardY = forwardY;
|
||||
this.forwardZ = forwardZ;
|
||||
this.upX = upX;
|
||||
this.upY = upY;
|
||||
this.upZ = upZ;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
5
core/src/com/etheller/warsmash/viewer5/Bounds.java
Normal file
5
core/src/com/etheller/warsmash/viewer5/Bounds.java
Normal file
@ -0,0 +1,5 @@
|
||||
package com.etheller.warsmash.viewer5;
|
||||
|
||||
public class Bounds {
|
||||
public int x, y, r;
|
||||
}
|
326
core/src/com/etheller/warsmash/viewer5/Camera.java
Normal file
326
core/src/com/etheller/warsmash/viewer5/Camera.java
Normal file
@ -0,0 +1,326 @@
|
||||
package com.etheller.warsmash.viewer5;
|
||||
|
||||
import com.badlogic.gdx.math.Matrix4;
|
||||
import com.badlogic.gdx.math.Quaternion;
|
||||
import com.badlogic.gdx.math.Rectangle;
|
||||
import com.badlogic.gdx.math.Vector2;
|
||||
import com.badlogic.gdx.math.Vector3;
|
||||
import com.etheller.warsmash.util.RenderMathUtils;
|
||||
import com.etheller.warsmash.util.Vector4;
|
||||
|
||||
public class Camera {
|
||||
private static final Vector3 vectorHeap = new Vector3();
|
||||
private static final Vector3 vectorHeap2 = new Vector3();
|
||||
private static final Vector3 vectorHeap3 = new Vector3();
|
||||
private static final Quaternion quatHeap = new Quaternion();
|
||||
private static final Matrix4 matHeap = new Matrix4();
|
||||
|
||||
public final Rectangle rect;
|
||||
|
||||
private boolean isPerspective;
|
||||
private float fov;
|
||||
private float aspect;
|
||||
|
||||
private boolean isOrtho;
|
||||
private float leftClipPlane;
|
||||
private float rightClipPlane;
|
||||
private float bottomClipPlane;
|
||||
private float topClipPlane;
|
||||
|
||||
private float nearClipPlane;
|
||||
private float farClipPlane;
|
||||
|
||||
public final Vector3 location;
|
||||
public final Quaternion rotation;
|
||||
|
||||
public Quaternion inverseRotation;
|
||||
private final Matrix4 worldMatrix;
|
||||
private final Matrix4 projectionMatrix;
|
||||
private final Matrix4 worldProjectionMatrix;
|
||||
private final Matrix4 inverseWorldMatrix;
|
||||
private final Matrix4 inverseRotationMatrix;
|
||||
private final Matrix4 inverseWorldProjectionMatrix;
|
||||
public final Vector3 directionX;
|
||||
public final Vector3 directionY;
|
||||
public final Vector3 directionZ;
|
||||
private final Vector3[] vectors;
|
||||
private final Vector3[] billboardedVectors;
|
||||
|
||||
public final Vector4[] planes;
|
||||
private boolean dirty;
|
||||
|
||||
public Camera() {
|
||||
// rencered viewport
|
||||
this.rect = new Rectangle();
|
||||
|
||||
// perspective values
|
||||
this.isPerspective = true;
|
||||
this.fov = 0;
|
||||
this.aspect = 0;
|
||||
|
||||
// Orthogonal values
|
||||
this.isOrtho = false;
|
||||
this.leftClipPlane = 0f;
|
||||
this.rightClipPlane = 0f;
|
||||
this.bottomClipPlane = 0f;
|
||||
this.topClipPlane = 0f;
|
||||
|
||||
// Shared values
|
||||
this.nearClipPlane = 0f;
|
||||
this.farClipPlane = 0f;
|
||||
|
||||
// World values
|
||||
this.location = new Vector3();
|
||||
this.rotation = new Quaternion();
|
||||
|
||||
// Derived values.
|
||||
this.inverseRotation = new Quaternion();
|
||||
this.worldMatrix = new Matrix4();
|
||||
this.projectionMatrix = new Matrix4();
|
||||
this.worldProjectionMatrix = new Matrix4();
|
||||
this.inverseWorldMatrix = new Matrix4();
|
||||
this.inverseRotationMatrix = new Matrix4();
|
||||
this.inverseWorldProjectionMatrix = new Matrix4();
|
||||
this.directionX = new Vector3();
|
||||
this.directionY = new Vector3();
|
||||
this.directionZ = new Vector3();
|
||||
|
||||
// First four vectors are the corners of a 2x2 rectangle, the last three vectors
|
||||
// are the unit axes
|
||||
this.vectors = new Vector3[] { new Vector3(-1, -1, 0), new Vector3(-1, 1, 0), new Vector3(1, 1, 0),
|
||||
new Vector3(1, -1, 0), new Vector3(1, 0, 0), new Vector3(0, 1, 0), new Vector3(0, 0, 1) };
|
||||
|
||||
// First four vectors are the corners of a 2x2 rectangle billboarded to the
|
||||
// camera, the last three vectors are the unit axes billboarded
|
||||
this.billboardedVectors = new Vector3[] { new Vector3(), new Vector3(), new Vector3(), new Vector3(),
|
||||
new Vector3(), new Vector3(), new Vector3() };
|
||||
|
||||
// Left, right, top, bottom, near, far
|
||||
this.planes = new Vector4[] { new Vector4(), new Vector4(), new Vector4(), new Vector4(), new Vector4(),
|
||||
new Vector4() };
|
||||
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
public void perspective(final float fov, final float aspect, final float near, final float far) {
|
||||
this.isPerspective = true;
|
||||
this.isOrtho = false;
|
||||
this.fov = fov;
|
||||
this.aspect = aspect;
|
||||
this.nearClipPlane = near;
|
||||
this.farClipPlane = far;
|
||||
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
public void ortho(final float left, final float right, final float bottom, final float top, final float near,
|
||||
final float far) {
|
||||
this.isPerspective = false;
|
||||
this.isOrtho = true;
|
||||
this.leftClipPlane = left;
|
||||
this.rightClipPlane = right;
|
||||
this.bottomClipPlane = bottom;
|
||||
this.topClipPlane = top;
|
||||
this.nearClipPlane = near;
|
||||
this.farClipPlane = far;
|
||||
}
|
||||
|
||||
public void viewport(final Rectangle viewport) {
|
||||
this.rect.set(viewport);
|
||||
|
||||
this.aspect = viewport.width / viewport.height;
|
||||
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
public void setLocation(final Vector3 location) {
|
||||
this.location.set(location);
|
||||
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
public void move(final Vector3 offset) {
|
||||
this.location.add(offset);
|
||||
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
public void setRotation(final Quaternion rotation) {
|
||||
this.rotation.set(rotation);
|
||||
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
public void rotate(final Quaternion rotation) {
|
||||
this.rotation.mul(rotation);
|
||||
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
public void setRotationAngles(final float horizontalAngle, final float verticalAngle) {
|
||||
this.rotation.idt();
|
||||
// this.rotateAngles(horizontalAngle, verticalAngle);
|
||||
throw new UnsupportedOperationException(
|
||||
"Ghostwolf called a function that does not exist, so I did not know what to do here");
|
||||
}
|
||||
|
||||
public void rotateAround(final Quaternion rotation, final Vector3 point) {
|
||||
this.rotate(rotation);
|
||||
|
||||
quatHeap.conjugate(); // TODO ?????????
|
||||
vectorHeap.set(this.location);
|
||||
vectorHeap.sub(point);
|
||||
rotation.transform(vectorHeap);
|
||||
vectorHeap.add(point);
|
||||
this.location.set(vectorHeap);
|
||||
}
|
||||
|
||||
public void setRotationAround(final Quaternion rotation, final Vector3 point) {
|
||||
this.setRotation(rotation);
|
||||
;
|
||||
|
||||
final float length = vectorHeap.set(this.location).sub(point).len();
|
||||
|
||||
quatHeap.conjugate(); // TODO ?????????
|
||||
vectorHeap.set(RenderMathUtils.VEC3_UNIT_Z);
|
||||
quatHeap.transform(vectorHeap);
|
||||
vectorHeap.scl(length);
|
||||
this.location.set(vectorHeap.add(point));
|
||||
}
|
||||
|
||||
public void setRotationAroundAngles(final float horizontalAngle, final float verticalAngle, final Vector3 point) {
|
||||
quatHeap.idt();
|
||||
RenderMathUtils.rotateX(quatHeap, quatHeap, verticalAngle);
|
||||
RenderMathUtils.rotateY(quatHeap, quatHeap, horizontalAngle);
|
||||
|
||||
this.setRotationAround(quatHeap, point);
|
||||
}
|
||||
|
||||
public void face(final Vector3 point, final Vector3 worldUp) {
|
||||
matHeap.setToLookAt(this.location, point, worldUp);
|
||||
matHeap.getRotation(this.rotation);
|
||||
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
public void moveToAndFace(final Vector3 location, final Vector3 target, final Vector3 worldUp) {
|
||||
this.location.set(location);
|
||||
this.face(target, worldUp);
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
this.location.set(0, 0, 0);
|
||||
this.rotation.idt();
|
||||
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
public void update() {
|
||||
if (this.dirty) {
|
||||
this.dirty = true;
|
||||
|
||||
final Vector3 location = this.location;
|
||||
final Quaternion rotation = this.rotation;
|
||||
final Quaternion inverseRotation = this.inverseRotation;
|
||||
final Matrix4 worldMatrix = this.worldMatrix;
|
||||
final Matrix4 projectionMatrix = this.projectionMatrix;
|
||||
final Matrix4 worldProjectionMatrix = this.worldProjectionMatrix;
|
||||
final Vector3[] vectors = this.vectors;
|
||||
final Vector3[] billboardedVectors = this.billboardedVectors;
|
||||
|
||||
if (this.isPerspective) {
|
||||
RenderMathUtils.perspective(projectionMatrix, this.fov, this.aspect, this.nearClipPlane,
|
||||
this.farClipPlane);
|
||||
}
|
||||
else {
|
||||
RenderMathUtils.ortho(projectionMatrix, this.leftClipPlane, this.rightClipPlane, this.bottomClipPlane,
|
||||
this.topClipPlane, this.nearClipPlane, this.farClipPlane);
|
||||
}
|
||||
|
||||
rotation.toMatrix(projectionMatrix.val);
|
||||
worldMatrix.translate(vectorHeap.set(location).scl(-1));
|
||||
inverseRotation.set(rotation).conjugate();
|
||||
|
||||
// World projection matrix
|
||||
// World space -> NDC space
|
||||
worldProjectionMatrix.set(projectionMatrix).mul(worldMatrix);
|
||||
|
||||
// Recalculate the camera's frustum planes
|
||||
RenderMathUtils.unpackPlanes(this.planes, worldProjectionMatrix);
|
||||
|
||||
// Inverse world matrix
|
||||
// Camera space -> world space
|
||||
this.inverseWorldMatrix.set(worldMatrix).inv();
|
||||
|
||||
this.directionX.set(RenderMathUtils.VEC3_UNIT_X);
|
||||
inverseRotation.transform(this.directionX);
|
||||
this.directionY.set(RenderMathUtils.VEC3_UNIT_Y);
|
||||
inverseRotation.transform(this.directionY);
|
||||
this.directionZ.set(RenderMathUtils.VEC3_UNIT_Z);
|
||||
inverseRotation.transform(this.directionZ);
|
||||
|
||||
// Inverse world projection matrix
|
||||
// NDC space -> World space
|
||||
this.inverseWorldProjectionMatrix.set(worldProjectionMatrix);
|
||||
this.inverseWorldProjectionMatrix.inv();
|
||||
|
||||
for (int i = 0; i < 7; i++) {
|
||||
billboardedVectors[i].set(vectors[i]);
|
||||
inverseRotation.transform(billboardedVectors[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean testSphere(final Vector3 center, final float radius) {
|
||||
for (final Vector4 plane : this.planes) {
|
||||
if (RenderMathUtils.distanceToPlane(plane, center) <= -radius) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public Vector3 cameraToWorld(final Vector3 out, final Vector3 v) {
|
||||
return out.set(v).prj(this.inverseWorldMatrix);
|
||||
}
|
||||
|
||||
public Vector3 worldToCamera(final Vector3 out, final Vector3 v) {
|
||||
return out.set(v).prj(this.worldMatrix);
|
||||
}
|
||||
|
||||
public Vector2 worldToScreen(final Vector2 out, final Vector3 v) {
|
||||
final Rectangle viewport = this.rect;
|
||||
|
||||
vectorHeap.set(v);
|
||||
vectorHeap.prj(this.inverseWorldMatrix);
|
||||
|
||||
out.x = Math.round(((vectorHeap.x + 1) / 2) * viewport.width);
|
||||
out.y = Math.round(((vectorHeap.y + 1) / 2) * viewport.height);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
public float[] screenToWorldRay(final float[] out, final Vector2 v) {
|
||||
final Vector3 a = vectorHeap;
|
||||
final Vector3 b = vectorHeap2;
|
||||
final Vector3 c = vectorHeap3;
|
||||
final float x = v.x;
|
||||
final float y = v.y;
|
||||
final Rectangle viewport = this.rect;
|
||||
|
||||
// Intersection on the near-plane
|
||||
RenderMathUtils.unproject(a, c.set(x, y, 0), this.inverseWorldProjectionMatrix, viewport);
|
||||
|
||||
// Intersection on the far-plane
|
||||
RenderMathUtils.unproject(b, c.set(x, y, 1), this.inverseWorldProjectionMatrix, viewport);
|
||||
|
||||
out[0] = a.x;
|
||||
out[1] = a.y;
|
||||
out[2] = a.z;
|
||||
out[3] = b.x;
|
||||
out[4] = b.y;
|
||||
out[5] = b.z;
|
||||
|
||||
return out;
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package com.etheller.warsmash.viewer5;
|
||||
|
||||
public interface CanvasProvider {
|
||||
float getWidth();
|
||||
|
||||
float getHeight();
|
||||
}
|
11
core/src/com/etheller/warsmash/viewer5/EmittedObject.java
Normal file
11
core/src/com/etheller/warsmash/viewer5/EmittedObject.java
Normal file
@ -0,0 +1,11 @@
|
||||
package com.etheller.warsmash.viewer5;
|
||||
|
||||
public abstract class EmittedObject {
|
||||
abstract void update(float dt);
|
||||
|
||||
public float health;
|
||||
public Emitter emitter;
|
||||
public int index;
|
||||
|
||||
protected abstract void bind(int flags);
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package com.etheller.warsmash.viewer5;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class EmittedObjectUpdater {
|
||||
final List<EmittedObject> objects;
|
||||
private int alive;
|
||||
|
||||
public EmittedObjectUpdater() {
|
||||
this.objects = new ArrayList<>();
|
||||
this.alive = 0;
|
||||
}
|
||||
|
||||
public void add(final EmittedObject object) {
|
||||
this.objects.add(object);
|
||||
this.alive++;
|
||||
}
|
||||
|
||||
public void update(final float dt) {
|
||||
for (int i = 0; i < this.alive; i++) {
|
||||
final EmittedObject object = this.objects.get(i);
|
||||
|
||||
object.update(dt);
|
||||
|
||||
if (object.health <= 0) {
|
||||
this.alive -= 1;
|
||||
|
||||
object.emitter.kill(object);
|
||||
|
||||
// Swap between this object and the last living object.
|
||||
// Decrement the iterator so the swapped object is updated this frame.
|
||||
if (i != this.alive) {
|
||||
this.objects.set(i, this.objects.remove(this.alive));
|
||||
i -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
75
core/src/com/etheller/warsmash/viewer5/Emitter.java
Normal file
75
core/src/com/etheller/warsmash/viewer5/Emitter.java
Normal file
@ -0,0 +1,75 @@
|
||||
package com.etheller.warsmash.viewer5;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.etheller.warsmash.viewer5.handlers.EmitterObject;
|
||||
|
||||
public abstract class Emitter {
|
||||
|
||||
private final ModelInstance instance;
|
||||
private final EmitterObject emitterObject;
|
||||
private final List<EmittedObject> objects;
|
||||
private int alive;
|
||||
private int currentEmission;
|
||||
|
||||
public Emitter(final ModelInstance instance, final EmitterObject emitterObject) {
|
||||
this.instance = instance;
|
||||
this.emitterObject = emitterObject;
|
||||
this.objects = new ArrayList<>();
|
||||
this.alive = 0;
|
||||
this.currentEmission = 0;
|
||||
}
|
||||
|
||||
public final EmittedObject emitObject(final int flags) {
|
||||
if (this.alive == this.objects.size()) {
|
||||
this.objects.add(this.createObject());
|
||||
}
|
||||
|
||||
final EmittedObject object = this.objects.get(this.alive);
|
||||
object.index = this.alive;
|
||||
object.bind(flags);
|
||||
|
||||
this.alive += 1;
|
||||
this.currentEmission -= 1;
|
||||
|
||||
this.instance.scene.emitterObjectUpdater.add(object);
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
public final void update(final float dt) {
|
||||
this.updateEmission(dt);
|
||||
|
||||
final int currentEmission = this.currentEmission;
|
||||
if (currentEmission >= 1) {
|
||||
for (int i = 0; i < currentEmission; i += 1) {
|
||||
this.emit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final void kill(final EmittedObject object) {
|
||||
this.alive -= 1;
|
||||
|
||||
final EmittedObject otherObject = this.objects.get(this.alive);
|
||||
this.objects.set(object.index, otherObject);
|
||||
this.objects.set(this.alive, object);
|
||||
|
||||
otherObject.index = object.index;
|
||||
object.index = -1;
|
||||
}
|
||||
|
||||
public final void clear() {
|
||||
for (int i = 0; i < this.alive; i++) {
|
||||
this.objects.get(i).health = 0;
|
||||
}
|
||||
this.currentEmission = 0;
|
||||
}
|
||||
|
||||
protected abstract void updateEmission(float dt);
|
||||
|
||||
protected abstract void emit();
|
||||
|
||||
protected abstract EmittedObject createObject();
|
||||
}
|
119
core/src/com/etheller/warsmash/viewer5/Grid.java
Normal file
119
core/src/com/etheller/warsmash/viewer5/Grid.java
Normal file
@ -0,0 +1,119 @@
|
||||
package com.etheller.warsmash.viewer5;
|
||||
|
||||
import com.badlogic.gdx.math.Vector3;
|
||||
|
||||
public class Grid {
|
||||
private final int x;
|
||||
private final int y;
|
||||
private final int width;
|
||||
private final int depth;
|
||||
private final int cellWidth;
|
||||
private final int cellDepth;
|
||||
private final int columns;
|
||||
private final int rows;
|
||||
final GridCell[] cells;
|
||||
|
||||
public Grid(final int x, int y, final int width, final int depth, final int cellWidth, final int cellDepth) {
|
||||
final int columns = width / cellWidth;
|
||||
final int rows = depth / cellDepth;
|
||||
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.width = width;
|
||||
this.depth = depth;
|
||||
this.cellWidth = cellWidth;
|
||||
this.cellDepth = cellDepth;
|
||||
this.columns = columns;
|
||||
this.rows = rows;
|
||||
this.cells = new GridCell[rows * columns];
|
||||
|
||||
for (int row = 0; row < rows; row++) {
|
||||
for (int column = 0; column < columns; column++) {
|
||||
final int left = x + (columns * cellWidth);
|
||||
final int right = left + cellWidth;
|
||||
final int bottom = y = row * cellDepth;
|
||||
final int top = bottom + cellDepth;
|
||||
|
||||
this.cells[(row * columns) + column] = new GridCell(left, right, bottom, top);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void add(final ModelInstance instance) {
|
||||
final int left = instance.left;
|
||||
final int right = instance.right + 1;
|
||||
final int bottom = instance.bottom;
|
||||
final int top = instance.top + 1;
|
||||
|
||||
if (left != -1) {
|
||||
for (int y = bottom; y < top; y++) {
|
||||
for (int x = left; x < right; x++) {
|
||||
this.cells[(y * this.columns) + x].add(instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void remove(final ModelInstance instance) {
|
||||
final int left = instance.left;
|
||||
final int right = instance.right + 1;
|
||||
final int bottom = instance.bottom;
|
||||
final int top = instance.top + 1;
|
||||
|
||||
if (left != -1) {
|
||||
instance.left = -1;
|
||||
|
||||
for (int y = bottom; y < top; y++) {
|
||||
for (int x = left; x < right; x++) {
|
||||
this.cells[(y * this.columns) + x].remove(instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void moved(final ModelInstance instance) {
|
||||
final Bounds bounds = instance.model.bounds;
|
||||
final float x = (instance.worldLocation.x + bounds.x) - this.x;
|
||||
final float y = (instance.worldLocation.y + bounds.y) - this.y;
|
||||
final float r = bounds.r;
|
||||
final Vector3 s = instance.worldScale;
|
||||
int left = (int) (Math.floor((x - (r * s.x)) / this.cellWidth));
|
||||
int right = (int) (Math.floor((x + (r * s.x)) / this.cellWidth));
|
||||
int bottom = (int) (Math.floor((y - (r * s.y)) / this.cellDepth));
|
||||
int top = (int) (Math.floor((y + (r * s.y)) / this.cellDepth));
|
||||
|
||||
if ((right < 0) || (left > (this.columns - 1)) || (top < 0) || (bottom > (this.rows - 1))) {
|
||||
// The instance is outside of the grid, so remove it.
|
||||
this.remove(instance);
|
||||
}
|
||||
else {
|
||||
// Clamp the values so they are in the grid.
|
||||
left = Math.max(left, 0);
|
||||
right = Math.min(right, this.columns - 1);
|
||||
bottom = Math.max(bottom, 0);
|
||||
top = Math.min(top, this.rows - 1);
|
||||
|
||||
// If the values actually changed, update the cells.
|
||||
if ((left != instance.left) || (right != instance.right) || (bottom != instance.bottom)
|
||||
|| (top != instance.top)) {
|
||||
/// TODO: This can be optimized by checking if there are shared cells.
|
||||
/// That can be done in precisely the same way as done a few lines above, i.e.
|
||||
/// simple rectangle intersection.
|
||||
this.remove(instance);
|
||||
|
||||
instance.left = left;
|
||||
instance.right = right;
|
||||
instance.bottom = bottom;
|
||||
instance.top = top;
|
||||
|
||||
this.add(instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
for (final GridCell cell : this.cells) {
|
||||
cell.clear();
|
||||
}
|
||||
}
|
||||
}
|
44
core/src/com/etheller/warsmash/viewer5/GridCell.java
Normal file
44
core/src/com/etheller/warsmash/viewer5/GridCell.java
Normal file
@ -0,0 +1,44 @@
|
||||
package com.etheller.warsmash.viewer5;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.etheller.warsmash.util.RenderMathUtils;
|
||||
|
||||
public class GridCell {
|
||||
public final int left;
|
||||
public final int right;
|
||||
public final int bottom;
|
||||
public final int top;
|
||||
public int plane;
|
||||
final List<ModelInstance> instances;
|
||||
public final boolean visible;
|
||||
|
||||
public GridCell(final int left, final int right, final int bottom, final int top) {
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
this.bottom = bottom;
|
||||
this.top = top;
|
||||
this.plane = -1;
|
||||
this.instances = new ArrayList<ModelInstance>();
|
||||
this.visible = false;
|
||||
}
|
||||
|
||||
public void add(final ModelInstance instance) {
|
||||
this.instances.add(instance);
|
||||
}
|
||||
|
||||
public void remove(final ModelInstance instance) {
|
||||
this.instances.remove(instance);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
this.instances.clear();
|
||||
}
|
||||
|
||||
public boolean isVisible(final Camera camera) {
|
||||
this.plane = RenderMathUtils.testCell(camera.planes, this.left, this.right, this.bottom, this.top, this.plane);
|
||||
|
||||
return this.plane == -1;
|
||||
}
|
||||
}
|
9
core/src/com/etheller/warsmash/viewer5/Model.java
Normal file
9
core/src/com/etheller/warsmash/viewer5/Model.java
Normal file
@ -0,0 +1,9 @@
|
||||
package com.etheller.warsmash.viewer5;
|
||||
|
||||
import com.etheller.warsmash.viewer5.handlers.Handler;
|
||||
|
||||
public class Model {
|
||||
public Bounds bounds;
|
||||
public boolean ok;
|
||||
public Handler handler;
|
||||
}
|
47
core/src/com/etheller/warsmash/viewer5/ModelInstance.java
Normal file
47
core/src/com/etheller/warsmash/viewer5/ModelInstance.java
Normal file
@ -0,0 +1,47 @@
|
||||
package com.etheller.warsmash.viewer5;
|
||||
|
||||
import com.badlogic.gdx.math.Vector3;
|
||||
|
||||
public class ModelInstance {
|
||||
public Model model;
|
||||
public TextureMapper textureMapper;
|
||||
|
||||
public int left = -1;
|
||||
public int right = -1;
|
||||
public int bottom = -1;
|
||||
public int top = -1;
|
||||
public int plane = -1;
|
||||
public int depth = 0;
|
||||
|
||||
public Vector3 worldLocation;
|
||||
public Vector3 worldScale;
|
||||
public Scene scene;
|
||||
public boolean rendered;
|
||||
public int cullFrame;
|
||||
public int updateFrame;
|
||||
|
||||
public boolean isVisible(final Camera camera) {
|
||||
// TODO Auto-generated method stub
|
||||
return false;
|
||||
}
|
||||
|
||||
public void update(final float dt, final Scene scene) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
public boolean isBatched() {
|
||||
// TODO Auto-generated method stub
|
||||
return false;
|
||||
}
|
||||
|
||||
public void renderOpaque() {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
public void renderTranslucent() {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
}
|
169
core/src/com/etheller/warsmash/viewer5/ModelViewer.java
Normal file
169
core/src/com/etheller/warsmash/viewer5/ModelViewer.java
Normal file
@ -0,0 +1,169 @@
|
||||
package com.etheller.warsmash.viewer5;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.badlogic.gdx.Gdx;
|
||||
import com.badlogic.gdx.graphics.GL20;
|
||||
import com.badlogic.gdx.graphics.Texture;
|
||||
import com.etheller.warsmash.datasources.DataSource;
|
||||
import com.etheller.warsmash.viewer5.gl.WebGL;
|
||||
|
||||
public class ModelViewer {
|
||||
private final DataSource dataSource;
|
||||
public final CanvasProvider canvas;
|
||||
public List<Resource> resources;
|
||||
public Map<String, Resource> fetchCache;
|
||||
public int frameTime;
|
||||
public GL20 gl;
|
||||
public WebGL webGL;
|
||||
public List<Scene> scenes;
|
||||
private int visibleCells;
|
||||
private int visibleInstances;
|
||||
private int updatedParticles;
|
||||
public int frame;
|
||||
private final int rectBuffer;
|
||||
private final boolean enableAudio;
|
||||
private final Map<Model, List<TextureMapper>> textureMappers;
|
||||
|
||||
public ModelViewer(final DataSource dataSource, final CanvasProvider canvas) {
|
||||
this.dataSource = dataSource;
|
||||
this.canvas = canvas;
|
||||
this.resources = new ArrayList<>();
|
||||
this.fetchCache = new HashMap<>();
|
||||
this.frameTime = 1000 / 60;
|
||||
this.gl = Gdx.gl;
|
||||
this.webGL = new WebGL(this.gl);
|
||||
this.scenes = new ArrayList<>();
|
||||
this.visibleCells = 0;
|
||||
this.visibleInstances = 0;
|
||||
this.updatedParticles = 0;
|
||||
|
||||
this.frame = 0;
|
||||
|
||||
this.rectBuffer = this.gl.glGenBuffer();
|
||||
this.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.rectBuffer);
|
||||
final ByteBuffer temp = ByteBuffer.allocate(6);
|
||||
temp.put((byte) 0);
|
||||
temp.put((byte) 1);
|
||||
temp.put((byte) 2);
|
||||
temp.put((byte) 0);
|
||||
temp.put((byte) 2);
|
||||
temp.put((byte) 3);
|
||||
temp.clear();
|
||||
this.gl.glBufferData(GL20.GL_ARRAY_BUFFER, temp.capacity(), temp, GL20.GL_STATIC_DRAW);
|
||||
this.enableAudio = false;
|
||||
this.textureMappers = new HashMap<Model, List<TextureMapper>>();
|
||||
}
|
||||
|
||||
public Scene addScene() {
|
||||
final Scene scene = new Scene(this);
|
||||
|
||||
this.scenes.add(scene);
|
||||
|
||||
return scene;
|
||||
}
|
||||
|
||||
public boolean removeScene(final Scene scene) {
|
||||
return this.scenes.remove(scene);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
this.scenes.clear();
|
||||
}
|
||||
|
||||
public boolean has(final String key) {
|
||||
return this.fetchCache.containsKey(key);
|
||||
}
|
||||
|
||||
public Resource get(final String key) {
|
||||
return this.fetchCache.get(key);
|
||||
}
|
||||
|
||||
public void updateAndRender() {
|
||||
update();
|
||||
}
|
||||
|
||||
// public Resource loadGeneric(String path, String dataType, )
|
||||
public void update() {
|
||||
final float dt = this.frameTime * 0.001f;
|
||||
|
||||
this.frame += 1;
|
||||
|
||||
this.visibleCells = 0;
|
||||
this.visibleInstances = 0;
|
||||
this.updatedParticles = 0;
|
||||
|
||||
for (final Scene scene : this.scenes) {
|
||||
scene.update(dt);
|
||||
|
||||
this.visibleCells += scene.visibleCells;
|
||||
this.visibleInstances += scene.visibleInstances;
|
||||
this.updatedParticles += scene.updatedParticles;
|
||||
}
|
||||
}
|
||||
|
||||
public void startFrame() {
|
||||
this.gl.glDepthMask(true);
|
||||
this.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);
|
||||
}
|
||||
|
||||
public void render() {
|
||||
this.renderOpaque();
|
||||
this.renderTranslucent();
|
||||
}
|
||||
|
||||
private void renderOpaque() {
|
||||
for (final Scene scene : this.scenes) {
|
||||
scene.renderOpaque();
|
||||
}
|
||||
}
|
||||
|
||||
private void renderTranslucent() {
|
||||
for (final Scene scene : this.scenes) {
|
||||
scene.renderTranslucent();
|
||||
}
|
||||
}
|
||||
|
||||
public TextureMapper baseTextureMapper(final ModelInstance instance) {
|
||||
final Model model = instance.model;
|
||||
List<TextureMapper> mappers = this.textureMappers.get(model);
|
||||
if (mappers == null) {
|
||||
mappers = new ArrayList<>();
|
||||
this.textureMappers.put(model, mappers);
|
||||
}
|
||||
if (mappers.isEmpty()) {
|
||||
mappers.add(new TextureMapper(model));
|
||||
}
|
||||
return mappers.get(0);
|
||||
}
|
||||
|
||||
public TextureMapper changeTextureMapper(final ModelInstance instance, final Object key, final Texture texture) {
|
||||
final Map<Object, Texture> map = new HashMap<>(instance.textureMapper.textures);
|
||||
|
||||
if (texture instanceof Texture) { // not null?
|
||||
map.put(key, texture);
|
||||
}
|
||||
else {
|
||||
map.remove(key);
|
||||
}
|
||||
|
||||
final Model model = instance.model;
|
||||
final List<TextureMapper> mappers = this.textureMappers.get(model);
|
||||
|
||||
for (final TextureMapper mapper : mappers) {
|
||||
if (mapper.textures.equals(map)) {
|
||||
return mapper;
|
||||
}
|
||||
}
|
||||
|
||||
final TextureMapper mapper = new TextureMapper(model, map);
|
||||
|
||||
mappers.add(mapper);
|
||||
|
||||
return mapper;
|
||||
}
|
||||
}
|
12
core/src/com/etheller/warsmash/viewer5/Resource.java
Normal file
12
core/src/com/etheller/warsmash/viewer5/Resource.java
Normal file
@ -0,0 +1,12 @@
|
||||
package com.etheller.warsmash.viewer5;
|
||||
|
||||
public abstract class Resource {
|
||||
public ModelViewer viewer;
|
||||
|
||||
public Resource(final ModelViewer viewer) {
|
||||
this.viewer = viewer;
|
||||
}
|
||||
|
||||
public abstract void bind(int unit);
|
||||
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
package com.etheller.warsmash.viewer5;
|
||||
|
||||
public interface ResourceLoader {
|
||||
}
|
290
core/src/com/etheller/warsmash/viewer5/Scene.java
Normal file
290
core/src/com/etheller/warsmash/viewer5/Scene.java
Normal file
@ -0,0 +1,290 @@
|
||||
package com.etheller.warsmash.viewer5;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
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.
|
||||
*
|
||||
* Every scene has its own list of model instances, and its own camera and
|
||||
* viewport.
|
||||
*
|
||||
* In addition, in Ghostwolf's original code every scene may have its own
|
||||
* AudioContext if enableAudio() is called. If audo is enabled, the
|
||||
* AudioContext's listener's location will be updated automatically. Note that
|
||||
* due to browser policies, this may be done only after user interaction with
|
||||
* the web page.
|
||||
*
|
||||
* In "Warsmash", we are starting from an attempt to replicate Ghostwolf, but
|
||||
* audio is always on in LibGDX generally. So we will probably simplify or skip
|
||||
* over those behaviors other than a boolean on/off toggle for audio.
|
||||
*/
|
||||
public class Scene {
|
||||
|
||||
private final ModelViewer viewer;
|
||||
private final Camera camera;
|
||||
private final Grid grid;
|
||||
public int visibleCells;
|
||||
public int visibleInstances;
|
||||
public int updatedParticles;
|
||||
private boolean audioEnabled;
|
||||
private AudioContext audioContext;
|
||||
|
||||
private final List<ModelInstance> instances;
|
||||
private final int currentInstance;
|
||||
private final List<ModelInstance> batchedInstances;
|
||||
private final int currentBatchedInstance;
|
||||
public final EmittedObjectUpdater emitterObjectUpdater;
|
||||
private final Map<TextureMapper, Batch> batches;
|
||||
private final Comparator<ModelInstance> instanceDepthComparator;
|
||||
|
||||
public Scene(final ModelViewer viewer) {
|
||||
final CanvasProvider canvas = viewer.canvas;
|
||||
this.viewer = viewer;
|
||||
this.camera = new Camera();
|
||||
this.grid = new Grid(-100000, -100000, 200000, 200000, 200000, 200000);
|
||||
|
||||
this.visibleCells = 0;
|
||||
this.visibleInstances = 0;
|
||||
this.updatedParticles = 0;
|
||||
|
||||
this.audioEnabled = false;
|
||||
this.audioContext = null;
|
||||
|
||||
// Use the whole canvas, and standard perspective projection values.
|
||||
this.camera.viewport(new Rectangle(0, 0, canvas.getWidth(), canvas.getHeight()));
|
||||
this.camera.perspective((float) (Math.PI / 4), canvas.getWidth() / canvas.getHeight(), 8, 10000);
|
||||
|
||||
this.instances = new ArrayList<>();
|
||||
this.currentInstance = 0;
|
||||
|
||||
this.batchedInstances = new ArrayList<>();
|
||||
this.currentBatchedInstance = 0;
|
||||
|
||||
this.emitterObjectUpdater = new EmittedObjectUpdater();
|
||||
|
||||
this.batches = new HashMap<>();
|
||||
this.instanceDepthComparator = new InstanceDepthComparator();
|
||||
}
|
||||
|
||||
public boolean enableAudio() {
|
||||
if (this.audioContext == null) {
|
||||
this.audioContext = new AudioContext();
|
||||
}
|
||||
if (!this.audioContext.isRunning()) {
|
||||
this.audioContext.resume();
|
||||
}
|
||||
this.audioEnabled = this.audioContext.isRunning();
|
||||
return this.audioEnabled;
|
||||
}
|
||||
|
||||
public void disableAudio() {
|
||||
if (this.audioContext != null) {
|
||||
this.audioContext.suspend();
|
||||
}
|
||||
this.audioEnabled = false;
|
||||
}
|
||||
|
||||
public boolean addInstance(final ModelInstance instance) {
|
||||
if (instance.scene != this) {
|
||||
if (instance.scene != null) {
|
||||
instance.scene.removeInstance(instance);
|
||||
}
|
||||
|
||||
instance.scene = this;
|
||||
|
||||
// Only allow instances that are actually ok to be added the scene.
|
||||
if (instance.model.ok) {
|
||||
this.grid.moved(instance);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean removeInstance(final ModelInstance instance) {
|
||||
if (instance.scene == this) {
|
||||
this.grid.remove(instance);
|
||||
|
||||
instance.scene = null;
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
// First remove references to this scene stored in the instances.
|
||||
for (final GridCell cell : this.grid.cells) {
|
||||
for (final ModelInstance instance : cell.instances) {
|
||||
instance.scene = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Then remove references to the instances.
|
||||
this.grid.clear();
|
||||
}
|
||||
|
||||
public boolean detach() {
|
||||
if (this.viewer != null) {
|
||||
return this.viewer.removeScene(this);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void addToBatch(final ModelInstance instance) {
|
||||
final TextureMapper textureMapper = instance.textureMapper;
|
||||
Batch 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);
|
||||
|
||||
this.batches.put(textureMapper, batch);
|
||||
}
|
||||
|
||||
batch.add(instance);
|
||||
}
|
||||
|
||||
public void update(final float dt) {
|
||||
this.camera.update();
|
||||
|
||||
if (this.audioEnabled) {
|
||||
final float x = this.camera.location.x;
|
||||
final float y = this.camera.location.y;
|
||||
final float z = this.camera.location.z;
|
||||
final float forwardX = this.camera.directionY.x;
|
||||
final float forwardY = this.camera.directionY.y;
|
||||
final float forwardZ = this.camera.directionY.z;
|
||||
final float upX = this.camera.directionZ.x;
|
||||
final float upY = this.camera.directionZ.y;
|
||||
final float upZ = this.camera.directionZ.z;
|
||||
final AudioContext.Listener listener = this.audioContext.listener;
|
||||
|
||||
listener.setPosition(-x, -y, -z);
|
||||
listener.setOrientation(forwardX, forwardY, forwardZ, upX, upY, upZ);
|
||||
}
|
||||
|
||||
final int frame = this.viewer.frame;
|
||||
|
||||
int currentInstance = 0;
|
||||
int currentBatchedInstance = 0;
|
||||
|
||||
this.visibleCells = 0;
|
||||
this.visibleInstances = 0;
|
||||
|
||||
// Update and collect all of the visible instances.
|
||||
for (final GridCell cell : this.grid.cells) {
|
||||
if (cell.isVisible(this.camera)) {
|
||||
this.visibleCells += 1;
|
||||
|
||||
for (final ModelInstance instance : cell.instances) {
|
||||
if (instance.rendered && (instance.cullFrame < frame) && instance.isVisible(this.camera)) {
|
||||
instance.cullFrame = frame;
|
||||
|
||||
if (instance.updateFrame < frame) {
|
||||
instance.update(dt, this);
|
||||
}
|
||||
|
||||
if (instance.isBatched()) {
|
||||
if (currentBatchedInstance < this.batchedInstances.size()) {
|
||||
this.batchedInstances.set(currentBatchedInstance++, instance);
|
||||
}
|
||||
else {
|
||||
this.batchedInstances.add(instance);
|
||||
currentBatchedInstance++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (currentInstance < this.instances.size()) {
|
||||
this.instances.set(currentInstance++, instance);
|
||||
}
|
||||
else {
|
||||
this.instances.add(instance);
|
||||
currentInstance++;
|
||||
}
|
||||
}
|
||||
|
||||
this.visibleInstances += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = this.batchedInstances.size() - 1; i >= currentBatchedInstance; i--) {
|
||||
this.batchedInstances.remove(i);
|
||||
}
|
||||
|
||||
for (int i = this.instances.size() - 1; i >= currentInstance; i--) {
|
||||
this.instances.remove(i);
|
||||
}
|
||||
Collections.sort(this.instances, this.instanceDepthComparator);
|
||||
|
||||
this.emitterObjectUpdater.update(dt);
|
||||
this.updatedParticles = this.emitterObjectUpdater.objects.size();
|
||||
}
|
||||
|
||||
public void renderOpaque() {
|
||||
final Rectangle viewport = this.camera.rect;
|
||||
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()) {
|
||||
batch.clear();
|
||||
}
|
||||
|
||||
// Add all of the batched instances to batches.
|
||||
for (final ModelInstance instance : this.batchedInstances) {
|
||||
this.addToBatch(instance);
|
||||
}
|
||||
|
||||
// Render all of the batches.
|
||||
for (final Batch batch : this.batches.values()) {
|
||||
batch.render();
|
||||
}
|
||||
|
||||
// Render all of the opaque things of non-batched instances.
|
||||
for (final ModelInstance instance : this.instances) {
|
||||
instance.renderOpaque();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders all translucent things in this scene. Automatically applies the
|
||||
* camera's viewport.
|
||||
*/
|
||||
public void renderTranslucent() {
|
||||
final Rectangle viewport = this.camera.rect;
|
||||
|
||||
this.viewer.gl.glViewport((int) viewport.x, (int) viewport.y, (int) viewport.width, (int) viewport.height);
|
||||
|
||||
for (final ModelInstance instance : this.instances) {
|
||||
instance.renderTranslucent();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void clearEmitterObjects() {
|
||||
for (final EmittedObject object : this.emitterObjectUpdater.objects) {
|
||||
object.health = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class InstanceDepthComparator implements Comparator<ModelInstance> {
|
||||
@Override
|
||||
public int compare(final ModelInstance o1, final ModelInstance o2) {
|
||||
return o2.depth - o1.depth;
|
||||
}
|
||||
}
|
||||
}
|
25
core/src/com/etheller/warsmash/viewer5/TextureMapper.java
Normal file
25
core/src/com/etheller/warsmash/viewer5/TextureMapper.java
Normal file
@ -0,0 +1,25 @@
|
||||
package com.etheller.warsmash.viewer5;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.badlogic.gdx.graphics.Texture;
|
||||
|
||||
public class TextureMapper {
|
||||
public final Model model;
|
||||
public final Map<Object, Texture> textures;
|
||||
|
||||
public TextureMapper(final Model model) {
|
||||
this.model = model;
|
||||
this.textures = new HashMap<>();
|
||||
}
|
||||
|
||||
public TextureMapper(final Model model, final Map<Object, Texture> textures) {
|
||||
this.model = model;
|
||||
this.textures = new HashMap<>(textures);
|
||||
}
|
||||
|
||||
public Texture get(final Object key) {
|
||||
return this.textures.get(key);
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package com.etheller.warsmash.viewer5.deprecated;
|
||||
|
||||
import com.etheller.warsmash.viewer5.gl.WebGL;
|
||||
|
||||
public class ShaderProgram {
|
||||
|
||||
public boolean ok;
|
||||
public int attribsCount;
|
||||
public int webglResource;
|
||||
|
||||
public ShaderProgram(final WebGL webGL, final ShaderUnitDeprecated vertexShader, final ShaderUnitDeprecated fragmentShader) {
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package com.etheller.warsmash.viewer5.deprecated;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
import com.badlogic.gdx.graphics.GL20;
|
||||
|
||||
public class ShaderUnitDeprecated {
|
||||
|
||||
public boolean ok;
|
||||
private final int webglResource;
|
||||
private final String src;
|
||||
private final int shaderType;
|
||||
|
||||
public ShaderUnitDeprecated(final GL20 gl, final String src, final int type) {
|
||||
final int id = gl.glCreateShader(type);
|
||||
this.ok = false;
|
||||
this.webglResource = id;
|
||||
this.src = src;
|
||||
this.shaderType = type;
|
||||
|
||||
gl.glShaderSource(id, src);
|
||||
gl.glCompileShader(id);
|
||||
|
||||
final IntBuffer success = ByteBuffer.allocateDirect(8).order(ByteOrder.nativeOrder()).asIntBuffer();
|
||||
gl.glGetShaderiv(id, GL20.GL_COMPILE_STATUS, success);
|
||||
throw new UnsupportedOperationException("Not yet implemented, probably using library instead");
|
||||
}
|
||||
|
||||
}
|
139
core/src/com/etheller/warsmash/viewer5/gl/WebGL.java
Normal file
139
core/src/com/etheller/warsmash/viewer5/gl/WebGL.java
Normal file
@ -0,0 +1,139 @@
|
||||
package com.etheller.warsmash.viewer5.gl;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.badlogic.gdx.graphics.GL20;
|
||||
import com.badlogic.gdx.graphics.Pixmap;
|
||||
import com.badlogic.gdx.graphics.Texture;
|
||||
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
|
||||
import com.etheller.warsmash.viewer5.deprecated.ShaderUnitDeprecated;
|
||||
|
||||
/**
|
||||
* This needs a rename. Just a ripoff of ghostwolf's wrapper utility class, it's
|
||||
* a utility, not a webgl
|
||||
*/
|
||||
public class WebGL {
|
||||
public GL20 gl;
|
||||
public Map<Integer, ShaderUnitDeprecated> shaderUnits;
|
||||
public Map<Integer, ShaderProgram> shaderPrograms;
|
||||
public ShaderProgram currentShaderProgram;
|
||||
public String floatPrecision;
|
||||
public final Texture emptyTexture;
|
||||
|
||||
public WebGL(final GL20 gl) {
|
||||
gl.glDepthFunc(GL20.GL_LEQUAL);
|
||||
gl.glEnable(GL20.GL_DEPTH_TEST);
|
||||
|
||||
// TODO here ghostwolf throws exceptions for unsupported versions of opengl
|
||||
|
||||
this.gl = gl;
|
||||
|
||||
this.shaderUnits = new HashMap<>();
|
||||
|
||||
this.shaderPrograms = new HashMap<>();
|
||||
|
||||
this.currentShaderProgram = null;
|
||||
this.floatPrecision = "precision mediump float;\n";
|
||||
|
||||
final Pixmap imageData = new Pixmap(2, 2, Pixmap.Format.RGBA8888);
|
||||
for (int i = 0; i < 2; i++) {
|
||||
for (int j = 0; j < 2; j++) {
|
||||
imageData.drawPixel(i, j, 0x000000FF);
|
||||
}
|
||||
}
|
||||
this.emptyTexture = new Texture(imageData);
|
||||
}
|
||||
|
||||
public ShaderUnitDeprecated createShaderUnit(final String src, final int type) {
|
||||
final int hash = stringHash(src); // TODO: why on earth are we doing this, what about hash collisions?
|
||||
if (!this.shaderUnits.containsKey(hash)) {
|
||||
this.shaderUnits.put(hash, new ShaderUnitDeprecated(this.gl, src, type));
|
||||
}
|
||||
return this.shaderUnits.get(hash);
|
||||
}
|
||||
|
||||
public ShaderProgram createShaderProgram(final String vertexSrc, final String fragmentSrc) {
|
||||
final Map<Integer, ShaderProgram> shaderPrograms = this.shaderPrograms;
|
||||
|
||||
final int hash = stringHash(vertexSrc + fragmentSrc);
|
||||
if (!shaderPrograms.containsKey(hash)) {
|
||||
shaderPrograms.put(hash, new ShaderProgram(vertexSrc, fragmentSrc));
|
||||
}
|
||||
|
||||
final ShaderProgram shaderProgram = shaderPrograms.get(hash);
|
||||
|
||||
if (shaderProgram.isCompiled()) {
|
||||
return shaderProgram;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void enableVertexAttribs(final int start, final int end) {
|
||||
final GL20 gl = this.gl;
|
||||
|
||||
for (int i = start; i < end; i++) {
|
||||
gl.glEnableVertexAttribArray(i);
|
||||
}
|
||||
}
|
||||
|
||||
public void disableVertexAttribs(final int start, final int end) {
|
||||
final GL20 gl = this.gl;
|
||||
|
||||
for (int i = start; i < end; i++) {
|
||||
gl.glDisableVertexAttribArray(i);
|
||||
}
|
||||
}
|
||||
|
||||
public void useShaderProgram(final ShaderProgram shaderProgram) {
|
||||
final ShaderProgram currentShaderProgram = this.currentShaderProgram;
|
||||
|
||||
if ((shaderProgram != null) && shaderProgram.isCompiled() && (shaderProgram != currentShaderProgram)) {
|
||||
int oldAttribs = 0;
|
||||
final int newAttribs = shaderProgram.getAttributes().length;
|
||||
|
||||
if (currentShaderProgram != null) {
|
||||
oldAttribs = currentShaderProgram.getAttributes().length;
|
||||
}
|
||||
|
||||
shaderProgram.begin();
|
||||
|
||||
if (newAttribs > oldAttribs) {
|
||||
this.enableVertexAttribs(oldAttribs, newAttribs);
|
||||
}
|
||||
else if (newAttribs < oldAttribs) {
|
||||
this.disableVertexAttribs(newAttribs, oldAttribs);
|
||||
}
|
||||
|
||||
this.currentShaderProgram = shaderProgram;
|
||||
}
|
||||
}
|
||||
|
||||
public void bindTexture(final Texture texture, final int unit) {
|
||||
final GL20 gl = this.gl;
|
||||
|
||||
gl.glActiveTexture(GL20.GL_TEXTURE0 + unit);
|
||||
|
||||
if (texture != null /* && texture.ok */) {
|
||||
texture.bind();
|
||||
}
|
||||
else {
|
||||
this.emptyTexture.bind();
|
||||
}
|
||||
}
|
||||
|
||||
public void setTextureMode(final int wrapS, final int wrapT, final int magFilter, final int minFilter) {
|
||||
final GL20 gl = this.gl;
|
||||
|
||||
// TODO make sure we dont assign this parameter doubly if we're already using
|
||||
// libgdx texture, which does do some wrapS and wrapT stuff already
|
||||
gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_WRAP_S, wrapS);
|
||||
gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_WRAP_T, wrapT);
|
||||
gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MAG_FILTER, magFilter);
|
||||
gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MIN_FILTER, minFilter);
|
||||
}
|
||||
|
||||
private int stringHash(final String src) {
|
||||
return src.hashCode();
|
||||
}
|
||||
}
|
22
core/src/com/etheller/warsmash/viewer5/handlers/Batch.java
Normal file
22
core/src/com/etheller/warsmash/viewer5/handlers/Batch.java
Normal file
@ -0,0 +1,22 @@
|
||||
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
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
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);
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package com.etheller.warsmash.viewer5.handlers;
|
||||
|
||||
public class EmitterObject {
|
||||
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package com.etheller.warsmash.viewer5.handlers;
|
||||
|
||||
public class Handler {
|
||||
public BatchDescriptor batchDescriptor;
|
||||
}
|
Loading…
Reference in New Issue
Block a user