Add game clock functionality and begin work on lighting system

This commit is contained in:
Retera 2020-09-26 16:02:20 -04:00
parent cfa5f952de
commit 59d350dd9e
31 changed files with 734 additions and 199 deletions

View File

@ -17,7 +17,8 @@ Path06="."
[Map]
//FilePath="CombatUnitTests.w3x"
FilePath="PitchRoll.w3x"
//FilePath="PitchRoll.w3x"
FilePath="PeonStartingBase.w3x"
//FilePath="PlayerPeasants.w3m"
//FilePath="FireLord.w3x"
//FilePath="Maps\Campaign\NightElf03.w3m"

View File

@ -27,6 +27,7 @@ import com.etheller.warsmash.viewer5.SolvedPath;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxHandler;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxViewer;
import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode;
public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvider {
@ -62,7 +63,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide
final FolderDataSourceDescriptor currentFolder = new FolderDataSourceDescriptor(".");
this.codebase = new CompoundDataSourceDescriptor(
Arrays.<DataSourceDescriptor>asList(war3mpq, testingFolder, currentFolder)).createDataSource();
this.viewer = new ModelViewer(this.codebase, this);
this.viewer = new MdxViewer(this.codebase, this);
this.viewer.addHandler(new MdxHandler());
this.viewer.enableAudio();

View File

@ -56,15 +56,13 @@ import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit;
import com.etheller.warsmash.viewer5.handlers.w3x.ui.MeleeUI;
public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProvider, InputProcessor {
private static final float HORIZONTAL_MAXIMUM = (float) Math.toRadians(56);
private static final float HORIZONTAL_MINIMUM = (float) -Math.toRadians(56);
private static final double HORIZONTAL_ANGLE_INCREMENT = Math.PI / 60;
private static final Vector3 clickLocationTemp = new Vector3();
private static final Vector2 clickLocationTemp2 = new Vector2();
private DataSource codebase;
private War3MapViewer viewer;
private CameraManager cameraManager;
private GameCameraManager cameraManager;
private final Rectangle tempRect = new Rectangle();
// libGDX stuff
@ -146,7 +144,20 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
throw new RuntimeException(e);
}
this.cameraManager = new CameraManager();
final Element cameraData = this.viewer.miscData.get("Camera");
final Element cameraListenerData = this.viewer.miscData.get("Listener");
final CameraPreset[] cameraPresets = new CameraPreset[6];
for (int i = 0; i < cameraPresets.length; i++) {
cameraPresets[i] = new CameraPreset(cameraData.getFieldFloatValue("AOA", i),
cameraData.getFieldFloatValue("FOV", i), cameraData.getFieldFloatValue("Rotation", i),
cameraData.getFieldFloatValue("Rotation", i + cameraPresets.length),
cameraData.getFieldFloatValue("Rotation", i + (cameraPresets.length * 2)),
cameraData.getFieldFloatValue("Distance", i), cameraData.getFieldFloatValue("FarZ", i),
cameraData.getFieldFloatValue("NearZ", i), cameraData.getFieldFloatValue("Height", i),
cameraListenerData.getFieldFloatValue("ListenerDistance", i),
cameraListenerData.getFieldFloatValue("ListenerAOA", i));
}
this.cameraManager = new GameCameraManager(cameraPresets);
this.cameraManager.setupCamera(this.viewer.worldScene);
@ -287,9 +298,10 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
final float deltaTime = Gdx.graphics.getDeltaTime();
Gdx.gl30.glBindVertexArray(WarsmashGdxGame.VAO);
this.cameraManager.target.add(this.cameraVelocity.x * deltaTime, this.cameraVelocity.y * deltaTime, 0);
this.cameraManager.target.z = Math.max(
this.cameraManager.target.z = (Math.max(
this.viewer.terrain.getGroundHeight(this.cameraManager.target.x, this.cameraManager.target.y),
this.viewer.terrain.getWaterHeight(this.cameraManager.target.x, this.cameraManager.target.y)) - 256;
this.viewer.terrain.getWaterHeight(this.cameraManager.target.x, this.cameraManager.target.y)) - 256)
+ this.cameraManager.presets[this.cameraManager.currentPreset].height + 256;
this.cameraManager.updateCamera();
this.meleeUI.updatePortrait();
this.viewer.updateAndRender();
@ -400,28 +412,26 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
this.meleeUI.resize();
}
public static class CameraManager {
private final float[] cameraPositionTemp = new float[3];
private final float[] cameraTargetTemp = new float[3];
public com.etheller.warsmash.viewer5.handlers.mdx.Camera modelCamera;
private MdxComplexInstance modelInstance;
private CanvasProvider canvas;
private Camera camera;
private float moveSpeed;
private float rotationSpeed;
private float zoomFactor;
private float horizontalAngle;
private float verticalAngle;
private float verticalAngleAcceleration;
private float distance;
private Vector3 position;
private Vector3 target;
private Vector3 worldUp;
private Vector3 vecHeap;
private Quaternion quatHeap;
private Quaternion quatHeap2;
private boolean insertDown;
private boolean deleteDown;
public static abstract class CameraManager {
protected final float[] cameraPositionTemp = new float[3];
protected final float[] cameraTargetTemp = new float[3];
protected CanvasProvider canvas;
protected Camera camera;
protected float moveSpeed;
protected float rotationSpeed;
protected float zoomFactor;
protected float horizontalAngle;
protected float verticalAngle;
protected float distance;
protected Vector3 position;
protected Vector3 target;
protected Vector3 worldUp;
protected Vector3 vecHeap;
protected Quaternion quatHeap;
protected Quaternion quatHeap2;
public CameraManager() {
}
// An orbit camera setup example.
// Left mouse button controls the orbit itself.
@ -449,45 +459,58 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
// cameraUpdate();
}
public abstract void updateCamera();
// private void cameraUpdate() {
//
// }
}
public static final class GameCameraManager extends CameraManager {
private final CameraPreset[] presets;
private int currentPreset = 0;
protected boolean insertDown;
protected boolean deleteDown;
public GameCameraManager(final CameraPreset[] presets) {
this.presets = presets;
}
@Override
public void updateCamera() {
this.quatHeap2.idt();
final CameraPreset cameraPreset = this.presets[this.currentPreset];
this.quatHeap.idt();
this.horizontalAngle = (float) Math
.toRadians(cameraPreset.getRotation(this.insertDown, this.deleteDown) - 90);
this.quatHeap.setFromAxisRad(0, 0, 1, this.horizontalAngle);
this.distance = Math.max(1200, cameraPreset.distance);
this.verticalAngle = (float) Math.toRadians(Math.min(335, cameraPreset.aoa) - 270);
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.nor();
this.position.scl(this.distance);
this.position = this.position.add(this.target);
this.camera.perspective((float) Math.toRadians(cameraPreset.fov / 2), this.camera.getAspect(),
cameraPreset.nearZ, cameraPreset.farZ);
this.camera.moveToAndFace(this.position, this.target, this.worldUp);
}
}
public static final class PortraitCameraManager extends CameraManager {
public com.etheller.warsmash.viewer5.handlers.mdx.Camera modelCamera;
protected MdxComplexInstance modelInstance;
@Override
public void updateCamera() {
this.quatHeap.idt();
this.quatHeap.setFromAxisRad(0, 0, 1, this.horizontalAngle);
if (this.insertDown && !this.deleteDown) {
this.horizontalAngle -= HORIZONTAL_ANGLE_INCREMENT;
if (this.horizontalAngle < HORIZONTAL_MINIMUM) {
this.horizontalAngle = HORIZONTAL_MINIMUM;
}
}
else if (this.deleteDown && !this.insertDown) {
this.horizontalAngle += HORIZONTAL_ANGLE_INCREMENT;
if (this.horizontalAngle > HORIZONTAL_MAXIMUM) {
this.horizontalAngle = HORIZONTAL_MAXIMUM;
}
}
else {
if (Math.abs(this.horizontalAngle) < HORIZONTAL_ANGLE_INCREMENT) {
this.horizontalAngle = 0;
}
else {
this.horizontalAngle -= HORIZONTAL_ANGLE_INCREMENT * Math.signum(this.horizontalAngle);
}
}
this.quatHeap2.idt();
this.verticalAngle += this.verticalAngleAcceleration;
this.verticalAngleAcceleration *= 0.975f;
if (this.verticalAngle > ((Math.PI / 2) - Math.toRadians(17))) {
this.verticalAngle = (float) ((Math.PI / 2) - Math.toRadians(17));
if (this.verticalAngleAcceleration > 0) {
this.verticalAngleAcceleration = 0;
}
}
if (this.verticalAngle < (float) Math.toRadians(34)) {
this.verticalAngle = (float) Math.toRadians(34);
if (this.verticalAngleAcceleration < 0) {
this.verticalAngleAcceleration = 0;
}
}
this.quatHeap2.setFromAxisRad(1, 0, 0, this.verticalAngle);
this.quatHeap.mul(this.quatHeap2);
@ -525,10 +548,6 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
this.modelCamera = portraitModel.getCameras().get(0);
}
}
// private void cameraUpdate() {
//
// }
}
private final float cameraSpeed = 4096.0f; // per second
@ -697,12 +716,12 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
@Override
public boolean scrolled(final int amount) {
this.cameraManager.verticalAngleAcceleration -= amount / 100.f;
if (this.cameraManager.verticalAngleAcceleration > (Math.PI / 128)) {
this.cameraManager.verticalAngleAcceleration = (float) (Math.PI / 128);
this.cameraManager.currentPreset -= amount;
if (this.cameraManager.currentPreset < 0) {
this.cameraManager.currentPreset = 0;
}
if (this.cameraManager.verticalAngleAcceleration < (-Math.PI / 128)) {
this.cameraManager.verticalAngleAcceleration = -(float) (Math.PI / 128);
if (this.cameraManager.currentPreset >= this.cameraManager.presets.length) {
this.cameraManager.currentPreset = this.cameraManager.presets.length - 1;
}
return true;
}
@ -716,4 +735,44 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
this.text = text;
}
}
private static class CameraPreset {
private final float aoa;
private final float fov;
private final float rotation;
private final float rotationInsert;
private final float rotationDelete;
private final float distance;
private final float farZ;
private final float nearZ;
private final float height;
private final float listenerDistance;
private final float listenerAOA;
public CameraPreset(final float aoa, final float fov, final float rotation, final float rotationInsert,
final float rotationDelete, final float distance, final float farZ, final float nearZ,
final float height, final float listenerDistance, final float listenerAOA) {
this.aoa = aoa;
this.fov = fov;
this.rotation = rotation;
this.rotationInsert = rotationInsert;
this.rotationDelete = rotationDelete;
this.distance = distance;
this.farZ = farZ;
this.nearZ = nearZ;
this.height = height;
this.listenerDistance = listenerDistance;
this.listenerAOA = listenerAOA;
}
public float getRotation(final boolean insertDown, final boolean deleteDown) {
if (insertDown && !deleteDown) {
return this.rotationInsert;
}
if (!insertDown && deleteDown) {
return this.rotationDelete;
}
return this.rotation;
}
}
}

View File

@ -209,7 +209,6 @@ public final class GameUI extends AbstractUIFrame implements UIFrame {
final MdxModel model = (MdxModel) this.modelViewer.load(backgroundArt, this.modelViewer.mapPathSolver,
this.modelViewer.solverParams);
spriteFrame.setModel(model);
spriteFrame.setSequence(0);
viewport2 = this.fdfCoordinateResolutionDummyViewport;
inflatedFrame = spriteFrame;
}

View File

@ -50,4 +50,22 @@ public class SpriteFrame extends AbstractRenderableFrame {
}
}
public void setAnimationSpeed(final float speedRatio) {
if (this.instance != null) {
this.instance.setAnimationSpeed(speedRatio);
}
}
public void setFrame(final int animationFrame) {
if (this.instance != null) {
this.instance.setFrame(animationFrame);
}
}
public void setFrameByRatio(final float ratioOfAnimationCompleted) {
if (this.instance != null) {
this.instance.setFrameByRatio(ratioOfAnimationCompleted);
}
}
}

View File

@ -20,6 +20,8 @@ public interface GameObject {
public float getFieldFloatValue(String field);
public float getFieldFloatValue(String field, int index);
public List<? extends GameObject> getFieldAsList(String field, ObjectData objectData);
public String getId();
@ -75,6 +77,11 @@ public interface GameObject {
return 0;
}
@Override
public float getFieldFloatValue(final String field, final int index) {
return 0;
}
@Override
public List<? extends GameObject> getFieldAsList(final String field, final ObjectData objectData) {
return Collections.emptyList();

View File

@ -79,6 +79,18 @@ public abstract class HashedGameObject implements GameObject {
return i;
}
@Override
public float getFieldFloatValue(final String field, final int index) {
float i = 0;
try {
i = Float.parseFloat(getField(field, index));
}
catch (final NumberFormatException e) {
}
return i;
}
@Override
public void setField(final String field, final String value, final int index) {
final StringKey key = new StringKey(field);

View File

@ -504,6 +504,17 @@ public class StandardObjectData {
return 0f;
}
@Override
public float getFieldFloatValue(final String field, final int index) {
for (final DataTable table : this.dataSource.getTables()) {
final Element element = table.get(this.id);
if ((element != null) && element.hasField(field)) {
return element.getFieldFloatValue(field, index);
}
}
return 0f;
}
/*
* (non-Javadoc) I'm not entirely sure this is still safe to use
*

View File

@ -8,10 +8,12 @@ import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.util.List;
import com.badlogic.gdx.math.Intersector;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Quaternion;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.math.collision.Ray;
public enum RenderMathUtils {
;
@ -451,6 +453,128 @@ public enum RenderMathUtils {
return out;
}
static Vector3 best = new Vector3();
static Vector3 tmp = new Vector3();
static Vector3 tmp1 = new Vector3();
static Vector3 tmp2 = new Vector3();
static Vector3 tmp3 = new Vector3();
/**
* Intersects the given ray with list of triangles. Returns the nearest
* intersection point in intersection
*
* @param ray The ray
* @param vertices the vertices
* @param indices the indices, each successive 3 shorts index the 3
* vertices of a triangle
* @param vertexSize the size of a vertex in floats
* @param intersection The nearest intersection point (optional)
* @return Whether the ray and the triangles intersect.
*/
public static boolean intersectRayTriangles(final Ray ray, final float[] vertices, final int[] indices,
final int vertexSize, final Vector3 intersection) {
float min_dist = Float.MAX_VALUE;
boolean hit = false;
if ((indices.length % 3) != 0) {
throw new RuntimeException("triangle list size is not a multiple of 3");
}
for (int i = 0; i < indices.length; i += 3) {
final int i1 = indices[i] * vertexSize;
final int i2 = indices[i + 1] * vertexSize;
final int i3 = indices[i + 2] * vertexSize;
final boolean result = Intersector.intersectRayTriangle(ray,
tmp1.set(vertices[i1], vertices[i1 + 1], vertices[i1 + 2]),
tmp2.set(vertices[i2], vertices[i2 + 1], vertices[i2 + 2]),
tmp3.set(vertices[i3], vertices[i3 + 1], vertices[i3 + 2]), tmp);
if (result == true) {
final float dist = ray.origin.dst2(tmp);
if (dist < min_dist) {
min_dist = dist;
best.set(tmp);
hit = true;
}
}
}
if (hit == false) {
return false;
}
else {
if (intersection != null) {
intersection.set(best);
}
return true;
}
}
/**
* Intersects the given ray with list of triangles. Returns the nearest
* intersection point in intersection
*
* @param ray The ray
* @param vertices the vertices
* @param indices the indices, each successive 3 shorts index the 3
* vertices of a triangle
* @param vertexSize the size of a vertex in floats
* @param intersection The nearest intersection point (optional)
* @return Whether the ray and the triangles intersect.
*/
public static boolean intersectRayTriangles(final Ray ray, final float[] vertices, final int[] indices,
final int vertexSize, final Vector3 worldLocation, final float facingRadians, final Vector3 intersection) {
float min_dist = Float.MAX_VALUE;
boolean hit = false;
if ((indices.length % 3) != 0) {
throw new RuntimeException("triangle list size is not a multiple of 3");
}
final float facingX_X = (float) Math.cos(facingRadians);
final float facingX_Y = (float) Math.sin(facingRadians);
final double halfPi = Math.PI / 2;
final float facingY_X = (float) Math.cos(facingRadians + halfPi);
final float facingY_Y = (float) Math.sin(facingRadians + halfPi);
for (int i = 0; i < indices.length; i += 3) {
final int i1 = indices[i] * vertexSize;
final int i2 = indices[i + 1] * vertexSize;
final int i3 = indices[i + 2] * vertexSize;
final boolean result = Intersector.intersectRayTriangle(ray,
tmp1.set((vertices[i1] * facingX_X) + (vertices[i1 + 1] * facingY_X) + worldLocation.x,
(vertices[i1] * facingX_Y) + (vertices[i1 + 1] * facingY_Y) + worldLocation.y,
vertices[i1 + 2] + worldLocation.z),
tmp2.set((vertices[i2] * facingX_X) + (vertices[i2 + 1] * facingY_X) + worldLocation.x,
(vertices[i2] * facingX_Y) + (vertices[i2 + 1] * facingY_Y) + worldLocation.y,
vertices[i2 + 2] + worldLocation.z),
tmp3.set((vertices[i3] * facingX_X) + (vertices[i3 + 1] * facingY_X) + worldLocation.x,
(vertices[i3] * facingX_Y) + (vertices[i3 + 1] * facingY_Y) + worldLocation.y,
vertices[i3 + 2] + worldLocation.z),
tmp);
if (result == true) {
final float dist = ray.origin.dst2(tmp);
if (dist < min_dist) {
min_dist = dist;
best.set(tmp);
hit = true;
}
}
}
if (hit == false) {
return false;
}
else {
if (intersection != null) {
intersection.set(best);
}
return true;
}
}
// ==== All of the following "wrap" calls are horribly inefficient. Eventually
// they should be removed entirely with better design.
// Until that happens, be sure to only call them during setup and not while the

View File

@ -77,9 +77,13 @@ public abstract class ModelInstance extends Node {
}
}
this.updateLights(scene);
this.updateFrame = this.model.viewer.frame;
}
protected abstract void updateLights(Scene scene2);
public boolean setScene(final Scene scene) {
return scene.addInstance(this);
}

View File

@ -20,7 +20,7 @@ import com.etheller.warsmash.viewer5.gl.WebGL;
import com.etheller.warsmash.viewer5.handlers.ResourceHandler;
import com.etheller.warsmash.viewer5.handlers.ResourceHandlerConstructionParams;
public class ModelViewer {
public abstract class ModelViewer {
public DataSource dataSource;
public final CanvasProvider canvas;
public List<Resource> resources;
@ -366,4 +366,6 @@ public class ModelViewer {
private void onResourceLoadError() {
System.err.println("error, this, InvalidHandler, FailedToLoad");
}
public abstract SceneLightManager createLightManager();
}

View File

@ -58,6 +58,7 @@ public abstract class Scene {
* If true, alpha works as usual.
*/
public boolean alpha = false;
private final SceneLightManager lightManager;
public Scene(final ModelViewer viewer) {
final CanvasProvider canvas = viewer.canvas;
@ -85,6 +86,8 @@ public abstract class Scene {
this.instanceDepthComparator = new InstanceDepthComparator();
this.visibleCells = 0;
this.visibleInstances = 0;
this.lightManager = this.viewer.createLightManager();
}
public boolean enableAudio() {
@ -300,6 +303,14 @@ public abstract class Scene {
}
}
public void addLight(final SceneLightInstance lightInstance) {
this.lightManager.add(lightInstance);
}
public void removeLight(final SceneLightInstance lightInstance) {
this.lightManager.remove(lightInstance);
}
private static final class InstanceDepthComparator implements Comparator<ModelInstance> {
@Override
public int compare(final ModelInstance o1, final ModelInstance o2) {

View File

@ -0,0 +1,5 @@
package com.etheller.warsmash.viewer5;
public interface SceneLightInstance {
}

View File

@ -0,0 +1,7 @@
package com.etheller.warsmash.viewer5;
public interface SceneLightManager {
public void add(final SceneLightInstance lightInstance);
public void remove(final SceneLightInstance lightInstance);
}

View File

@ -5,6 +5,8 @@ import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.math.collision.BoundingBox;
import com.badlogic.gdx.math.collision.Ray;
import com.etheller.warsmash.util.RenderMathUtils;
import com.etheller.warsmash.viewer5.GenericNode;
public class CollisionShape extends GenericObject {
private static Vector3 intersectHeap = new Vector3();
@ -87,4 +89,22 @@ public class CollisionShape extends GenericObject {
return Intersector.intersectRaySphere(ray, intersectHeap, this.radius, intersection);
}
}
public static boolean intersectRayTriangles(final Ray ray, final GenericNode mdxNode, final float[] vertices,
final int[] indices, final int vertexSize, final Vector3 intersection) {
intersectMatrixHeap.set(mdxNode.worldMatrix);
Matrix4.inv(intersectMatrixHeap.val);
intersectHeap.set(ray.origin);
intersectHeap2.set(ray.direction);
intersectHeap2.add(ray.origin);
intersectHeap.prj(intersectMatrixHeap);
intersectHeap2.prj(intersectMatrixHeap);
intersectHeap2.sub(intersectHeap);
intersectRayHeap.set(intersectHeap, intersectHeap2);
if (RenderMathUtils.intersectRayTriangles(intersectRayHeap, vertices, indices, vertexSize, intersection)) {
intersection.prj(mdxNode.worldMatrix);
return true;
}
return false;
}
}

View File

@ -24,10 +24,13 @@ public class Geoset {
private final int openGLSkinType;
private final int skinStride;
private final int boneCountOffsetBytes;
public final boolean unselectable;
public final com.etheller.warsmash.parsers.mdlx.Geoset mdlxGeoset;
public Geoset(final MdxModel model, final int index, final int positionOffset, final int normalOffset,
final int uvOffset, final int skinOffset, final int faceOffset, final int vertices, final int elements,
final int openGLSkinType, final int skinStride, final int boneCountOffsetBytes) {
final int openGLSkinType, final int skinStride, final int boneCountOffsetBytes, final boolean unselectable,
final com.etheller.warsmash.parsers.mdlx.Geoset mdlxGeoset) {
this.model = model;
this.index = index;
this.positionOffset = positionOffset;
@ -40,6 +43,8 @@ public class Geoset {
this.openGLSkinType = openGLSkinType;
this.skinStride = skinStride;
this.boneCountOffsetBytes = boneCountOffsetBytes;
this.unselectable = unselectable;
this.mdlxGeoset = mdlxGeoset;
for (final GeosetAnimation geosetAnimation : model.getGeosetAnimations()) {
if (geosetAnimation.geosetId == index) {

View File

@ -4,7 +4,7 @@ import com.etheller.warsmash.parsers.mdlx.AnimationMap;
public class Light extends GenericObject {
private final int type;
private final Type type;
private final float[] attenuation;
private final float[] color;
private final float intensity;
@ -14,7 +14,18 @@ public class Light extends GenericObject {
public Light(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.Light light, final int index) {
super(model, light, index);
this.type = light.getType();
switch (light.getType()) {
case 0:
this.type = Type.OMNIDIRECTIONAL;
break;
case 2:
this.type = Type.DIRECTIONAL;
break;
default:
case 1:
this.type = Type.AMBIENT;
break;
}
this.attenuation = light.getAttenuation();
this.color = light.getColor();
this.intensity = light.getIntensity();
@ -22,6 +33,10 @@ public class Light extends GenericObject {
this.ambientIntensity = light.getAmbientIntensity();
}
public Type getType() {
return this.type;
}
public int getAttenuationStart(final float[] out, final int sequence, final int frame, final int counter) {
return this.getScalarValue(out, AnimationMap.KLAS.getWar3id(), sequence, frame, counter, this.attenuation[0]);
}
@ -45,4 +60,15 @@ public class Light extends GenericObject {
public int getAmbientColor(final float[] out, final int sequence, final int frame, final int counter) {
return this.getVectorValue(out, AnimationMap.KLBC.getWar3id(), sequence, frame, counter, this.ambientColor);
}
public static enum Type {
// Omnidirectional light used for in-game sun
OMNIDIRECTIONAL,
// Directional light used for torches in the game world, and similar objects
// that "glow"
DIRECTIONAL,
// Directional ambient light used for torches in the game world, and similar
// objects that "glow"
AMBIENT;
}
}

View File

@ -0,0 +1,78 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import java.nio.FloatBuffer;
import com.etheller.warsmash.viewer5.Scene;
import com.etheller.warsmash.viewer5.SceneLightInstance;
import com.etheller.warsmash.viewer5.UpdatableObject;
public class LightInstance implements UpdatableObject, SceneLightInstance {
private static final float[] vectorHeap = new float[3];
private static final float[] scalarHeap = new float[1];
protected final MdxNode node;
protected final Light light;
private boolean visible;
private boolean loadedInScene;
public LightInstance(final MdxComplexInstance instance, final Light light) {
this.node = instance.nodes[light.index];
this.light = light;
}
public void bind(final int offset, final FloatBuffer floatBuffer, final int sequence, final int frame,
final int counter) {
this.light.getAttenuationStart(scalarHeap, sequence, frame, counter);
final float attenuationStart = scalarHeap[0];
this.light.getAttenuationEnd(scalarHeap, sequence, frame, counter);
final float attenuationEnd = scalarHeap[0];
this.light.getIntensity(scalarHeap, sequence, frame, counter);
final float intensity = scalarHeap[0];
this.light.getColor(vectorHeap, sequence, frame, counter);
final float colorRed = vectorHeap[0];
final float colorGreen = vectorHeap[1];
final float colorBlue = vectorHeap[2];
this.light.getAmbientIntensity(scalarHeap, sequence, frame, counter);
final float ambientIntensity = scalarHeap[0];
this.light.getAmbientColor(vectorHeap, sequence, frame, counter);
final float ambientColorRed = vectorHeap[0];
final float ambientColorGreen = vectorHeap[1];
final float ambientColorBlue = vectorHeap[2];
floatBuffer.put(offset, this.node.worldLocation.x);
floatBuffer.put(offset + 1, this.node.worldLocation.y);
floatBuffer.put(offset + 2, this.node.worldLocation.z);
// I use some padding to make the memory structure of the light be a 4x4 float
// grid, when somebody who actually has experience with this stuff comes along
// to change this to something smart, maybe they'll remove the padding if it's
// not necessary. I'm basing how I implement this on how Ghostwolf did
// BoneTexture
floatBuffer.put(offset + 3, 0);
floatBuffer.put(offset + 4, this.light.getType().ordinal());
floatBuffer.put(offset + 5, attenuationStart);
floatBuffer.put(offset + 6, attenuationEnd);
floatBuffer.put(offset + 7, 0);
floatBuffer.put(offset + 8, colorRed);
floatBuffer.put(offset + 9, colorGreen);
floatBuffer.put(offset + 10, colorBlue);
floatBuffer.put(offset + 11, intensity);
floatBuffer.put(offset + 12, ambientColorRed);
floatBuffer.put(offset + 13, ambientColorGreen);
floatBuffer.put(offset + 14, ambientColorBlue);
floatBuffer.put(offset + 15, ambientIntensity);
}
@Override
public void update(final float dt, final boolean visible) {
this.visible = visible;
}
public void update(final Scene scene) {
if (this.loadedInScene != this.visible) {
if (this.visible) {
scene.addLight(this);
}
else {
scene.removeLight(this);
}
}
}
}

View File

@ -34,6 +34,7 @@ public class MdxComplexInstance extends ModelInstance {
private static final float[] alphaHeap = new float[1];
private static final long[] textureIdHeap = new long[1];
public List<LightInstance> lights = new ArrayList<>();
public List<AttachmentInstance> attachments = new ArrayList<>();
public List<ParticleEmitter> particleEmitters = new ArrayList<>();
public List<ParticleEmitter2> particleEmitters2 = new ArrayList<>();
@ -106,7 +107,9 @@ public class MdxComplexInstance extends ModelInstance {
}
for (final Light light : model.lights) {
this.initNode(this.nodes, this.nodes[nodeIndex++], light);
final LightInstance lightInstance = new LightInstance(this, light);
this.lights.add(lightInstance);
this.initNode(this.nodes, this.nodes[nodeIndex++], light, lightInstance);
}
for (final Helper helper : model.helpers) {
@ -601,6 +604,13 @@ public class MdxComplexInstance extends ModelInstance {
}
@Override
protected void updateLights(final Scene scene) {
for (final LightInstance light : this.lights) {
light.update(scene);
}
}
/**
* Set the team color of this instance.
*/
@ -715,10 +725,28 @@ public class MdxComplexInstance extends ModelInstance {
* @param ray
*/
public boolean intersectRayWithCollision(final Ray ray, final Vector3 intersection) {
for (final CollisionShape collisionShape : ((MdxModel) this.model).collisionShapes) {
final MdxNode mdxNode = this.nodes[collisionShape.index];
if (collisionShape.checkIntersect(ray, mdxNode, intersection)) {
return true;
final MdxModel mdxModel = (MdxModel) this.model;
final List<CollisionShape> collisionShapes = mdxModel.collisionShapes;
if (collisionShapes.isEmpty()) {
for (final Geoset geoset : mdxModel.geosets) {
if (!geoset.unselectable) {
geoset.getAlpha(alphaHeap, this.sequence, this.frame, this.counter);
if (alphaHeap[0] > 0) {
final com.etheller.warsmash.parsers.mdlx.Geoset mdlxGeoset = geoset.mdlxGeoset;
if (CollisionShape.intersectRayTriangles(ray, this, mdlxGeoset.getVertices(),
mdlxGeoset.getFaces(), 3, intersection)) {
return true;
}
}
}
}
}
else {
for (final CollisionShape collisionShape : collisionShapes) {
final MdxNode mdxNode = this.nodes[collisionShape.index];
if (collisionShape.checkIntersect(ray, mdxNode, intersection)) {
return true;
}
}
}
return false;
@ -727,4 +755,17 @@ public class MdxComplexInstance extends ModelInstance {
public void setAnimationSpeed(final float speedRatio) {
this.animationSpeed = speedRatio;
}
public void setFrame(final int frame) {
this.frame = frame;
this.floatingFrame = frame;
}
public void setFrameByRatio(final float ratioOfAnimationCompleted) {
final Sequence currentlyPlayingSequence = ((MdxModel) this.model).sequences.get(this.sequence);
this.floatingFrame = currentlyPlayingSequence.getInterval()[0]
+ ((currentlyPlayingSequence.getInterval()[1] - currentlyPlayingSequence.getInterval()[0])
* ratioOfAnimationCompleted);
this.frame = (int) this.floatingFrame;
}
}

View File

@ -6,6 +6,7 @@ import com.etheller.warsmash.viewer5.BatchedInstance;
import com.etheller.warsmash.viewer5.Model;
import com.etheller.warsmash.viewer5.PathSolver;
import com.etheller.warsmash.viewer5.RenderBatch;
import com.etheller.warsmash.viewer5.Scene;
import com.etheller.warsmash.viewer5.Texture;
import com.etheller.warsmash.viewer5.TextureMapper;
@ -32,6 +33,10 @@ public class MdxSimpleInstance extends BatchedInstance {
public void renderTranslucent() {
}
@Override
protected void updateLights(final Scene scene2) {
}
@Override
public void load() {
}

View File

@ -0,0 +1,20 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.etheller.warsmash.datasources.DataSource;
import com.etheller.warsmash.viewer5.CanvasProvider;
import com.etheller.warsmash.viewer5.ModelViewer;
import com.etheller.warsmash.viewer5.SceneLightManager;
public class MdxViewer extends ModelViewer {
public MdxViewer(final DataSource dataSource, final CanvasProvider canvas) {
super(dataSource, canvas);
}
@Override
public SceneLightManager createLightManager() {
// TODO Auto-generated method stub
return null;
}
}

View File

@ -155,9 +155,10 @@ public class SetupGeosets {
}
}
final boolean unselectable = geoset.getSelectionFlags() == 4;
final Geoset vGeoset = new Geoset(model, model.getGeosets().size(), positionOffset, normalOffset,
uvOffset, skinOffset, faceOffset, vertices, faces.length, openGLSkinType, skinStride,
boneCountOffsetBytes);
boneCountOffsetBytes, unselectable, geoset);
model.getGeosets().add(vGeoset);

View File

@ -67,6 +67,7 @@ public class SequenceUtils {
filtered.sort(STAND_SEQUENCE_COMPARATOR);
int i = 0;
final double randomRoll = Math.random() * 100;
for (final int l = filtered.size(); i < l; i++) {
final Sequence sequence = filtered.get(i).sequence;
final float rarity = sequence.getRarity();
@ -75,7 +76,7 @@ public class SequenceUtils {
break;
}
if ((Math.random() * 10) > rarity) {
if (randomRoll < (10 - rarity)) {
return filtered.get(i);
}
}
@ -98,6 +99,7 @@ public class SequenceUtils {
filtered.sort(STAND_SEQUENCE_COMPARATOR);
int i = 0;
final double randomRoll = Math.random() * 100;
for (final int l = filtered.size(); i < l; i++) {
final Sequence sequence = filtered.get(i).sequence;
final float rarity = sequence.getRarity();
@ -106,7 +108,7 @@ public class SequenceUtils {
break;
}
if (((Math.random() * 10) > rarity) && allowRarityVariations) {
if ((randomRoll < (10 - rarity)) && allowRarityVariations) {
return filtered.get(i);
}
}

View File

@ -0,0 +1,9 @@
package com.etheller.warsmash.viewer5.handlers.w3x;
public class W3xSceneLight {
public static enum Type {
OMNIDIRECTIONAL,
DIRECTIONAL;
}
}

View File

@ -0,0 +1,35 @@
package com.etheller.warsmash.viewer5.handlers.w3x;
import java.util.ArrayList;
import java.util.List;
import com.etheller.warsmash.viewer5.SceneLightInstance;
import com.etheller.warsmash.viewer5.SceneLightManager;
import com.etheller.warsmash.viewer5.gl.DataTexture;
import com.etheller.warsmash.viewer5.handlers.mdx.LightInstance;
public class W3xSceneLightManager implements SceneLightManager {
public final List<LightInstance> lights;
private final DataTexture unitLightsTexture;
private final DataTexture terrainLightsTexture;
public W3xSceneLightManager(final War3MapViewer viewer) {
this.lights = new ArrayList<>();
this.unitLightsTexture = new DataTexture(viewer.gl, 4, 4, 1);
this.terrainLightsTexture = new DataTexture(viewer.gl, 4, 4, 1);
}
@Override
public void add(final SceneLightInstance lightInstance) {
// TODO redesign to avoid cast
final LightInstance mdxLight = (LightInstance) lightInstance;
this.lights.add(mdxLight);
}
@Override
public void remove(final SceneLightInstance lightInstance) {
// TODO redesign to avoid cast
final LightInstance mdxLight = (LightInstance) lightInstance;
this.lights.remove(mdxLight);
}
}

View File

@ -42,10 +42,12 @@ import com.etheller.warsmash.parsers.w3x.w3i.War3MapW3i;
import com.etheller.warsmash.parsers.w3x.wpm.War3MapWpm;
import com.etheller.warsmash.units.DataTable;
import com.etheller.warsmash.units.Element;
import com.etheller.warsmash.units.StandardObjectData;
import com.etheller.warsmash.units.manager.MutableObjectData;
import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject;
import com.etheller.warsmash.units.manager.MutableObjectData.WorldEditorDataType;
import com.etheller.warsmash.util.MappedData;
import com.etheller.warsmash.util.RenderMathUtils;
import com.etheller.warsmash.util.War3ID;
import com.etheller.warsmash.util.WarsmashConstants;
import com.etheller.warsmash.util.WorldEditStrings;
@ -56,6 +58,7 @@ import com.etheller.warsmash.viewer5.ModelInstance;
import com.etheller.warsmash.viewer5.ModelViewer;
import com.etheller.warsmash.viewer5.PathSolver;
import com.etheller.warsmash.viewer5.Scene;
import com.etheller.warsmash.viewer5.SceneLightManager;
import com.etheller.warsmash.viewer5.Texture;
import com.etheller.warsmash.viewer5.WorldScene;
import com.etheller.warsmash.viewer5.gl.WebGL;
@ -118,7 +121,6 @@ public class War3MapViewer extends ModelViewer {
public SolverParams solverParams = new SolverParams();
public WorldScene worldScene;
public boolean anyReady;
public boolean terrainCliffsAndWaterLoaded;
public MappedData terrainData = new MappedData();
public MappedData cliffTypesData = new MappedData();
public MappedData waterData = new MappedData();
@ -151,9 +153,11 @@ public class War3MapViewer extends ModelViewer {
public List<RenderUnit> selected = new ArrayList<>();
private DataTable unitAckSoundsTable;
private DataTable unitCombatSoundsTable;
private DataTable miscData;
public DataTable miscData;
private DataTable unitGlobalStrings;
private MdxComplexInstance confirmationInstance;
private MdxComplexInstance dncUnit;
private MdxComplexInstance dncTerrain;
public CSimulation simulation;
private float updateTime;
@ -195,7 +199,6 @@ public class War3MapViewer extends ModelViewer {
stringDataCallback);
// == when loaded, which is always in our system ==
this.terrainCliffsAndWaterLoaded = true;
this.terrainData.load(terrain.data.toString());
this.cliffTypesData.load(cliffTypes.data.toString());
this.waterData.load(water.data.toString());
@ -254,6 +257,9 @@ public class War3MapViewer extends ModelViewer {
try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\MiscGame.txt")) {
this.miscData.readTXT(miscDataTxtStream, true);
}
try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\MiscData.txt")) {
this.miscData.readTXT(miscDataTxtStream, true);
}
this.unitGlobalStrings = new DataTable(worldEditStrings);
try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\UnitGlobalStrings.txt")) {
this.unitGlobalStrings.readTXT(miscDataTxtStream, true);
@ -342,8 +348,11 @@ public class War3MapViewer extends ModelViewer {
final War3MapWpm terrainPathing = this.mapMpq.readPathing();
final StandardObjectData standardObjectData = new StandardObjectData(this.dataSource);
this.worldEditData = standardObjectData.getWorldEditData();
this.terrain = new Terrain(terrainData, terrainPathing, w3iFile, this.webGL, this.dataSource, worldEditStrings,
this);
this, this.worldEditData);
final float[] centerOffset = terrainData.getCenterOffset();
final int[] mapSize = terrainData.getMapSize();
@ -363,13 +372,6 @@ public class War3MapViewer extends ModelViewer {
this.confirmationInstance.setSequence(0);
this.confirmationInstance.setScene(this.worldScene);
if (this.terrainCliffsAndWaterLoaded) {
this.loadTerrainCliffsAndWater(terrainData);
}
else {
throw new IllegalStateException("transcription of JS has not loaded a map and has no JS async promises");
}
final Warcraft3MapObjectData modifications = this.mapMpq.readModifications();
this.simulation = new CSimulation(this.miscData, modifications.getUnits(), modifications.getAbilities(),
new SimulationRenderController() {
@ -388,12 +390,7 @@ public class War3MapViewer extends ModelViewer {
final float projectileLaunchY = simulation.getUnitData().getProjectileLaunchY(typeId);
final float projectileLaunchZ = simulation.getUnitData().getProjectileLaunchZ(typeId);
if (missileArt.toLowerCase().endsWith(".mdl")) {
missileArt = missileArt.substring(0, missileArt.length() - 4);
}
if (!missileArt.toLowerCase().endsWith(".mdx")) {
missileArt += ".mdx";
}
missileArt = mdx(missileArt);
final float facing = launchFacing;
final float sinFacing = (float) Math.sin(facing);
final float cosFacing = (float) Math.cos(facing);
@ -435,12 +432,7 @@ public class War3MapViewer extends ModelViewer {
.getProjectileLaunchX(typeId);
final float projectileLaunchY = War3MapViewer.this.simulation.getUnitData()
.getProjectileLaunchY(typeId);
if (missileArt.toLowerCase().endsWith(".mdl")) {
missileArt = missileArt.substring(0, missileArt.length() - 4);
}
if (!missileArt.toLowerCase().endsWith(".mdx")) {
missileArt += ".mdx";
}
missileArt = mdx(missileArt);
final float facing = (float) Math.toRadians(source.getFacing());
final float sinFacing = (float) Math.sin(facing);
final float cosFacing = (float) Math.cos(facing);
@ -507,9 +499,17 @@ public class War3MapViewer extends ModelViewer {
this.terrain.initShadows();
this.terrain.createWaves();
loadLightsAndShading(tileset);
}
private void loadTerrainCliffsAndWater(final War3MapW3e w3e) {
private void loadLightsAndShading(final char tileset) {
// TODO this should be set by the war3map.j actually, not by the tileset, so the
// call to set day night models is just for testing to make the test look pretty
final Element defaultTerrainLights = this.worldEditData.get("TerrainLights");
final Element defaultUnitLights = this.worldEditData.get("UnitLights");
setDayNightModels(defaultTerrainLights.getField(Character.toString(tileset)),
defaultUnitLights.getField(Character.toString(tileset)));
}
@ -902,6 +902,10 @@ public class War3MapViewer extends ModelViewer {
this.updateTime -= WarsmashConstants.SIMULATION_STEP_TIME;
this.simulation.update();
}
this.dncTerrain.setFrameByRatio(
this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours());
this.dncUnit.setFrameByRatio(
this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours());
}
}
@ -1007,7 +1011,7 @@ public class War3MapViewer extends ModelViewer {
this.worldScene.camera.screenToWorldRay(ray, mousePosHeap);
gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]);
gdxRayHeap.direction.nor();// needed for libgdx
Terrain.intersectRayTriangles(gdxRayHeap, this.terrain.softwareGroundMesh.vertices,
RenderMathUtils.intersectRayTriangles(gdxRayHeap, this.terrain.softwareGroundMesh.vertices,
this.terrain.softwareGroundMesh.indices, 3, out);
}
@ -1144,6 +1148,7 @@ public class War3MapViewer extends ModelViewer {
private static final int MAXIMUM_ACCEPTED = 1 << 30;
private float selectionCircleScaleFactor;
private DataTable worldEditData;
/**
* Returns a power of two size for the given target capacity.
@ -1240,4 +1245,30 @@ public class War3MapViewer extends ModelViewer {
this.textureDotted = textureDotted;
}
}
public void setDayNightModels(final String terrainDNCFile, final String unitDNCFile) {
final MdxModel terrainDNCModel = (MdxModel) load(mdx(terrainDNCFile), PathSolver.DEFAULT, null);
this.dncTerrain = (MdxComplexInstance) terrainDNCModel.addInstance();
this.dncTerrain.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP);
this.dncTerrain.setSequence(0);
final MdxModel unitDNCModel = (MdxModel) load(mdx(unitDNCFile), PathSolver.DEFAULT, null);
this.dncUnit = (MdxComplexInstance) unitDNCModel.addInstance();
this.dncUnit.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP);
this.dncUnit.setSequence(0);
}
private static String mdx(String mdxPath) {
if (mdxPath.toLowerCase().endsWith(".mdl")) {
mdxPath = mdxPath.substring(0, mdxPath.length() - 4);
}
if (!mdxPath.toLowerCase().endsWith(".mdx")) {
mdxPath += ".mdx";
}
return mdxPath;
}
@Override
public SceneLightManager createLightManager() {
return new W3xSceneLightManager(this);
}
}

View File

@ -26,7 +26,6 @@ import com.badlogic.gdx.math.Intersector;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.math.collision.Ray;
import com.etheller.warsmash.datasources.DataSource;
import com.etheller.warsmash.parsers.w3x.w3e.Corner;
import com.etheller.warsmash.parsers.w3x.w3e.War3MapW3e;
@ -34,7 +33,6 @@ import com.etheller.warsmash.parsers.w3x.w3i.War3MapW3i;
import com.etheller.warsmash.parsers.w3x.wpm.War3MapWpm;
import com.etheller.warsmash.units.DataTable;
import com.etheller.warsmash.units.Element;
import com.etheller.warsmash.units.StandardObjectData;
import com.etheller.warsmash.util.ImageUtils;
import com.etheller.warsmash.util.RenderMathUtils;
import com.etheller.warsmash.util.War3ID;
@ -127,7 +125,7 @@ public class Terrain {
public Terrain(final War3MapW3e w3eFile, final War3MapWpm terrainPathing, final War3MapW3i w3iFile,
final WebGL webGL, final DataSource dataSource, final WorldEditStrings worldEditStrings,
final War3MapViewer viewer) throws IOException {
final War3MapViewer viewer, final DataTable worldEditData) throws IOException {
this.webGL = webGL;
this.viewer = viewer;
this.camera = viewer.worldScene.camera;
@ -221,8 +219,6 @@ public class Terrain {
this.groundTextureToId.put(groundTile.asStringValue(), this.groundTextures.size() - 1);
}
final StandardObjectData standardObjectData = new StandardObjectData(dataSource);
final DataTable worldEditData = standardObjectData.getWorldEditData();
final Element tilesets = worldEditData.get("TileSets");
this.blightTextureIndex = this.groundTextures.size();
@ -1179,67 +1175,10 @@ public class Terrain {
// return out;
// }
static Vector3 best = new Vector3();
static Vector3 tmp = new Vector3();
static Vector3 tmp1 = new Vector3();
static Vector3 tmp2 = new Vector3();
static Vector3 tmp3 = new Vector3();
private final WaveBuilder waveBuilder;
public PathingGrid pathingGrid;
private final Rectangle shaderMapBoundsRectangle;
/**
* Intersects the given ray with list of triangles. Returns the nearest
* intersection point in intersection
*
* @param ray The ray
* @param vertices the vertices
* @param indices the indices, each successive 3 shorts index the 3
* vertices of a triangle
* @param vertexSize the size of a vertex in floats
* @param intersection The nearest intersection point (optional)
* @return Whether the ray and the triangles intersect.
*/
public static boolean intersectRayTriangles(final Ray ray, final float[] vertices, final int[] indices,
final int vertexSize, final Vector3 intersection) {
float min_dist = Float.MAX_VALUE;
boolean hit = false;
if ((indices.length % 3) != 0) {
throw new RuntimeException("triangle list size is not a multiple of 3");
}
for (int i = 0; i < indices.length; i += 3) {
final int i1 = indices[i] * vertexSize;
final int i2 = indices[i + 1] * vertexSize;
final int i3 = indices[i + 2] * vertexSize;
final boolean result = Intersector.intersectRayTriangle(ray,
tmp1.set(vertices[i1], vertices[i1 + 1], vertices[i1 + 2]),
tmp2.set(vertices[i2], vertices[i2 + 1], vertices[i2 + 2]),
tmp3.set(vertices[i3], vertices[i3 + 1], vertices[i3 + 2]), tmp);
if (result == true) {
final float dist = ray.origin.dst2(tmp);
if (dist < min_dist) {
min_dist = dist;
best.set(tmp);
hit = true;
}
}
}
if (hit == false) {
return false;
}
else {
if (intersection != null) {
intersection.set(best);
}
return true;
}
}
private static final class UnloadedTexture {
private final int width;
private final int height;

View File

@ -19,6 +19,10 @@ public class CGameplayConstants {
private final float boneDecayTime;
private final float bulletDeathTime;
private final float closeEnoughRange;
private final float dawnTimeGameHours;
private final float duskTimeGameHours;
private final float gameDayHours;
private final float gameDayLength;
public CGameplayConstants(final DataTable parsedDataTable) {
final Element miscData = parsedDataTable.get("Misc");
@ -30,6 +34,11 @@ public class CGameplayConstants {
this.bulletDeathTime = miscData.getFieldFloatValue("BulletDeathTime");
this.closeEnoughRange = miscData.getFieldFloatValue("CloseEnoughRange");
this.dawnTimeGameHours = miscData.getFieldFloatValue("Dawn");
this.duskTimeGameHours = miscData.getFieldFloatValue("Dusk");
this.gameDayHours = miscData.getFieldFloatValue("DayHours");
this.gameDayLength = miscData.getFieldFloatValue("DayLength");
final CDefenseType[] defenseTypeOrder = { CDefenseType.SMALL, CDefenseType.MEDIUM, CDefenseType.LARGE,
CDefenseType.FORT, CDefenseType.NORMAL, CDefenseType.HERO, CDefenseType.DIVINE, CDefenseType.NONE, };
this.damageBonusTable = new float[CAttackType.values().length][defenseTypeOrder.length];
@ -80,4 +89,20 @@ public class CGameplayConstants {
public float getCloseEnoughRange() {
return this.closeEnoughRange;
}
public float getGameDayHours() {
return this.gameDayHours;
}
public float getGameDayLength() {
return this.gameDayLength;
}
public float getDawnTimeGameHours() {
return this.dawnTimeGameHours;
}
public float getDuskTimeGameHours() {
return this.duskTimeGameHours;
}
}

View File

@ -40,6 +40,7 @@ public class CSimulation {
private final CPathfindingProcessor pathfindingProcessor;
private final CGameplayConstants gameplayConstants;
private final Random seededRandom;
private float currentGameDayTimeElapsed;
public CSimulation(final DataTable miscData, final MutableObjectData parsedUnitData,
final MutableObjectData parsedAbilityData, final SimulationRenderController simulationRenderController,
@ -145,6 +146,13 @@ public class CSimulation {
this.projectiles.addAll(this.newProjectiles);
this.newProjectiles.clear();
this.gameTurnTick++;
this.currentGameDayTimeElapsed = (this.currentGameDayTimeElapsed + WarsmashConstants.SIMULATION_STEP_TIME)
% this.gameplayConstants.getGameDayLength();
}
public float getGameTimeOfDay() {
return (this.currentGameDayTimeElapsed / this.gameplayConstants.getGameDayLength())
* this.gameplayConstants.getGameDayHours();
}
public int getGameTurnTick() {

View File

@ -132,6 +132,26 @@ public class CUnitData {
final int speed = unitType.getFieldAsInteger(MOVEMENT_SPEED_BASE, 0);
final int defense = unitType.getFieldAsInteger(DEFENSE, 0);
final CUnitType unitTypeInstance = getUnitTypeInstance(typeId, buildingPathingPixelMap, unitType);
final CUnit unit = new CUnit(handleId, playerIndex, x, y, life, typeId, facing, manaInitial, life, manaMaximum,
speed, defense, unitTypeInstance);
if (speed > 0) {
unit.add(simulation, CAbilityMove.INSTANCE);
unit.add(simulation, CAbilityPatrol.INSTANCE);
unit.add(simulation, CAbilityHoldPosition.INSTANCE);
unit.add(simulation, CAbilityStop.INSTANCE);
}
final int dmgDice1 = unitType.getFieldAsInteger(ATTACK1_DMG_DICE, 0);
final int dmgDice2 = unitType.getFieldAsInteger(ATTACK2_DMG_DICE, 0);
if ((dmgDice1 != 0) || (dmgDice2 != 0)) {
unit.add(simulation, CAbilityAttack.INSTANCE);
}
return unit;
}
private CUnitType getUnitTypeInstance(final War3ID typeId, final BufferedImage buildingPathingPixelMap,
final MutableGameObject unitType) {
CUnitType unitTypeInstance = this.unitIdToUnitType.get(typeId);
if (unitTypeInstance == null) {
final float moveHeight = unitType.getFieldAsFloat(MOVE_HEIGHT, 0);
@ -252,21 +272,7 @@ public class CUnitData {
targetedAs);
this.unitIdToUnitType.put(typeId, unitTypeInstance);
}
final CUnit unit = new CUnit(handleId, playerIndex, x, y, life, typeId, facing, manaInitial, life, manaMaximum,
speed, defense, unitTypeInstance);
if (speed > 0) {
unit.add(simulation, CAbilityMove.INSTANCE);
unit.add(simulation, CAbilityPatrol.INSTANCE);
unit.add(simulation, CAbilityHoldPosition.INSTANCE);
unit.add(simulation, CAbilityStop.INSTANCE);
}
final int dmgDice1 = unitType.getFieldAsInteger(ATTACK1_DMG_DICE, 0);
final int dmgDice2 = unitType.getFieldAsInteger(ATTACK2_DMG_DICE, 0);
if ((dmgDice1 != 0) || (dmgDice2 != 0)) {
unit.add(simulation, CAbilityAttack.INSTANCE);
}
return unit;
return unitTypeInstance;
}
private CUnitAttack createAttack(final float animationBackswingPoint, final float animationDamagePoint,

View File

@ -18,6 +18,7 @@ import com.etheller.warsmash.parsers.fdf.GameUI;
import com.etheller.warsmash.parsers.fdf.datamodel.AnchorDefinition;
import com.etheller.warsmash.parsers.fdf.datamodel.FramePoint;
import com.etheller.warsmash.parsers.fdf.frames.SetPoint;
import com.etheller.warsmash.parsers.fdf.frames.SpriteFrame;
import com.etheller.warsmash.parsers.fdf.frames.StringFrame;
import com.etheller.warsmash.parsers.fdf.frames.TextureFrame;
import com.etheller.warsmash.parsers.fdf.frames.UIFrame;
@ -55,7 +56,7 @@ public class MeleeUI implements CUnitStateListener {
private StringFrame resourceBarLumberText;
private StringFrame resourceBarSupplyText;
private StringFrame resourceBarUpkeepText;
private UIFrame timeIndicator;
private SpriteFrame timeIndicator;
private UIFrame unitPortrait;
private StringFrame unitLifeText;
private StringFrame unitManaText;
@ -107,7 +108,7 @@ public class MeleeUI implements CUnitStateListener {
// =================================
// Load skins and templates
// =================================
this.rootFrame = new GameUI(this.dataSource, GameUI.loadSkin(this.dataSource, 3), this.uiViewport,
this.rootFrame = new GameUI(this.dataSource, GameUI.loadSkin(this.dataSource, 0), this.uiViewport,
this.fontGenerator, this.uiScene, this.war3MapViewer);
try {
this.rootFrame.loadTOCFile("UI\\FrameDef\\FrameDef.toc");
@ -148,7 +149,9 @@ public class MeleeUI implements CUnitStateListener {
this.resourceBarUpkeepText.setColor(Color.RED);
// Create the Time Indicator (clock)
this.timeIndicator = this.rootFrame.createFrame("TimeOfDayIndicator", this.rootFrame, 0, 0);
this.timeIndicator = (SpriteFrame) this.rootFrame.createFrame("TimeOfDayIndicator", this.rootFrame, 0, 0);
this.timeIndicator.setSequence(0); // play the stand
this.timeIndicator.setAnimationSpeed(0.0f); // do not advance automatically
// Create the unit portrait stuff
this.portrait = new Portrait(this.war3MapViewer);
@ -221,6 +224,9 @@ public class MeleeUI implements CUnitStateListener {
}
}
}
this.timeIndicator.setFrameByRatio(this.war3MapViewer.simulation.getGameTimeOfDay()
/ this.war3MapViewer.simulation.getGameplayConstants().getGameDayHours());
}
public void portraitTalk() {
@ -229,12 +235,12 @@ public class MeleeUI implements CUnitStateListener {
private static final class Portrait {
private MdxComplexInstance modelInstance;
private final WarsmashGdxMapGame.CameraManager portraitCameraManager;
private final WarsmashGdxMapGame.PortraitCameraManager portraitCameraManager;
private final Scene portraitScene;
public Portrait(final War3MapViewer war3MapViewer) {
this.portraitScene = war3MapViewer.addSimpleScene();
this.portraitCameraManager = new WarsmashGdxMapGame.CameraManager();
this.portraitCameraManager = new WarsmashGdxMapGame.PortraitCameraManager();
this.portraitCameraManager.setupCamera(this.portraitScene);
this.portraitScene.camera.viewport(new Rectangle(100, 0, 6400, 48));
}
@ -303,6 +309,11 @@ public class MeleeUI implements CUnitStateListener {
this.simpleNameValue.setText(unit.getSimulationUnit().getUnitType().getName());
String classText = null;
for (final CUnitClassification classification : unit.getSimulationUnit().getClassifications()) {
if ((classification == CUnitClassification.MECHANICAL)
&& unit.getSimulationUnit().getUnitType().isBuilding()) {
// buildings dont display MECHANICAL
continue;
}
if (classification.getDisplayName() != null) {
classText = classification.getDisplayName();
}
@ -325,7 +336,11 @@ public class MeleeUI implements CUnitStateListener {
}
this.simpleBuildingActionLabel.setText("");
if (unit.getSimulationUnit().getUnitType().getAttacks().size() > 0) {
final boolean anyAttacks = unit.getSimulationUnit().getUnitType().getAttacks().size() > 0;
final UIFrame localArmorIcon;
final TextureFrame localArmorIconBackdrop;
final StringFrame localArmorInfoPanelIconValue;
if (anyAttacks) {
final CUnitAttack attackOne = unit.getSimulationUnit().getUnitType().getAttacks().get(0);
this.attack1Icon.setVisible(attackOne.isShowUI());
this.attack1IconBackdrop.setTexture(this.damageBackdrops.getTexture(attackOne.getAttackType()));
@ -339,21 +354,29 @@ public class MeleeUI implements CUnitStateListener {
else {
this.attack2Icon.setVisible(false);
}
localArmorIcon = this.armorIcon;
localArmorIconBackdrop = this.armorIconBackdrop;
localArmorInfoPanelIconValue = this.armorInfoPanelIconValue;
}
else {
this.attack1Icon.setVisible(false);
this.armorIcon.setVisible(false);
this.attack2Icon.setVisible(false);
localArmorIcon = this.attack1Icon;
localArmorIconBackdrop = this.attack1IconBackdrop;
localArmorInfoPanelIconValue = this.attack1InfoPanelIconValue;
}
this.armorIcon.setVisible(true);
localArmorIcon.setVisible(true);
final Texture defenseTexture = this.defenseBackdrops
.getTexture(unit.getSimulationUnit().getUnitType().getDefenseType());
if (defenseTexture == null) {
throw new RuntimeException(
unit.getSimulationUnit().getUnitType().getDefenseType() + " can't find texture!");
}
this.armorIconBackdrop.setTexture(defenseTexture);
this.armorInfoPanelIconValue.setText(Integer.toString(unit.getSimulationUnit().getDefense()));
localArmorIconBackdrop.setTexture(defenseTexture);
localArmorInfoPanelIconValue.setText(Integer.toString(unit.getSimulationUnit().getDefense()));
}
}