Add game source, converter pipeline, and web port
@@ -0,0 +1,86 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Convert Ogre3D .mesh.xml to Three.js geometry modules."""
|
||||||
|
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def parse_mesh_xml(filepath):
|
||||||
|
tree = ET.parse(filepath)
|
||||||
|
root = tree.getroot()
|
||||||
|
|
||||||
|
sharedgeom = root.find('sharedgeometry')
|
||||||
|
vertices = []
|
||||||
|
if sharedgeom is not None:
|
||||||
|
for vb in sharedgeom.findall('vertexbuffer'):
|
||||||
|
for vertex in vb.findall('vertex'):
|
||||||
|
pos = vertex.find('position')
|
||||||
|
normal = vertex.find('normal')
|
||||||
|
v = {}
|
||||||
|
if pos is not None:
|
||||||
|
v['x'] = float(pos.get('x', 0))
|
||||||
|
v['y'] = float(pos.get('y', 0))
|
||||||
|
v['z'] = float(pos.get('z', 0))
|
||||||
|
if normal is not None:
|
||||||
|
v['nx'] = float(normal.get('x', 0))
|
||||||
|
v['ny'] = float(normal.get('y', 0))
|
||||||
|
v['nz'] = float(normal.get('z', 0))
|
||||||
|
vertices.append(v)
|
||||||
|
|
||||||
|
faces = []
|
||||||
|
for submeshes in root.findall('submeshes'):
|
||||||
|
for submesh in submeshes.findall('submesh'):
|
||||||
|
for faces_elem in submesh.findall('faces'):
|
||||||
|
for face in faces_elem.findall('face'):
|
||||||
|
v1 = int(face.get('v1', 0))
|
||||||
|
v2 = int(face.get('v2', 0))
|
||||||
|
v3 = int(face.get('v3', 0))
|
||||||
|
faces.append((v1, v2, v3))
|
||||||
|
|
||||||
|
return vertices, faces
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
base_dir = '/var/home/nico/Gamedev/Wackelpeter/extracted/Models'
|
||||||
|
output_dir = '/var/home/nico/Gamedev/Wackelpeter/web/public/models'
|
||||||
|
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
|
||||||
|
for model_name in ['blob/Blob', 'sword/sword', 'shield/shield']:
|
||||||
|
filepath = os.path.join(base_dir, f'{model_name}.mesh.xml')
|
||||||
|
if not os.path.exists(filepath):
|
||||||
|
print(f'Skipping {filepath} - not found')
|
||||||
|
continue
|
||||||
|
|
||||||
|
print(f'Converting {model_name}...')
|
||||||
|
vertices, faces = parse_mesh_xml(filepath)
|
||||||
|
|
||||||
|
positions = []
|
||||||
|
normals = []
|
||||||
|
indices = []
|
||||||
|
|
||||||
|
for v in vertices:
|
||||||
|
positions.extend([v.get('x', 0), v.get('y', 0), v.get('z', 0)])
|
||||||
|
normals.extend([v.get('nx', 0), v.get('ny', 0), v.get('nz', 0)])
|
||||||
|
|
||||||
|
for f in faces:
|
||||||
|
indices.extend(list(f))
|
||||||
|
|
||||||
|
model_data = {
|
||||||
|
'positions': positions,
|
||||||
|
'normals': normals,
|
||||||
|
'indices': indices,
|
||||||
|
'vertexCount': len(vertices),
|
||||||
|
'faceCount': len(faces),
|
||||||
|
}
|
||||||
|
|
||||||
|
name = os.path.basename(model_name).lower()
|
||||||
|
outpath = os.path.join(output_dir, f'{name}.json')
|
||||||
|
with open(outpath, 'w') as f:
|
||||||
|
json.dump(model_data, f)
|
||||||
|
|
||||||
|
print(f' -> {outpath} ({len(vertices)} vertices, {len(faces)} faces)')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* com.jme3.animation.AnimChannel
|
||||||
|
* com.jme3.animation.AnimControl
|
||||||
|
* com.jme3.animation.AnimEventListener
|
||||||
|
* com.jme3.animation.LoopMode
|
||||||
|
* com.jme3.app.SimpleApplication
|
||||||
|
* com.jme3.input.controls.ActionListener
|
||||||
|
* com.jme3.input.controls.InputListener
|
||||||
|
* com.jme3.input.controls.KeyTrigger
|
||||||
|
* com.jme3.input.controls.MouseButtonTrigger
|
||||||
|
* com.jme3.input.controls.Trigger
|
||||||
|
* com.jme3.light.DirectionalLight
|
||||||
|
* com.jme3.light.Light
|
||||||
|
* com.jme3.material.Material
|
||||||
|
* com.jme3.math.ColorRGBA
|
||||||
|
* com.jme3.math.Vector3f
|
||||||
|
* com.jme3.scene.Node
|
||||||
|
* com.jme3.scene.Spatial
|
||||||
|
* com.jme3.scene.debug.SkeletonDebugger
|
||||||
|
*/
|
||||||
|
package jme3.hello;
|
||||||
|
|
||||||
|
import com.jme3.animation.AnimChannel;
|
||||||
|
import com.jme3.animation.AnimControl;
|
||||||
|
import com.jme3.animation.AnimEventListener;
|
||||||
|
import com.jme3.animation.LoopMode;
|
||||||
|
import com.jme3.app.SimpleApplication;
|
||||||
|
import com.jme3.input.controls.ActionListener;
|
||||||
|
import com.jme3.input.controls.InputListener;
|
||||||
|
import com.jme3.input.controls.KeyTrigger;
|
||||||
|
import com.jme3.input.controls.MouseButtonTrigger;
|
||||||
|
import com.jme3.input.controls.Trigger;
|
||||||
|
import com.jme3.light.DirectionalLight;
|
||||||
|
import com.jme3.light.Light;
|
||||||
|
import com.jme3.material.Material;
|
||||||
|
import com.jme3.math.ColorRGBA;
|
||||||
|
import com.jme3.math.Vector3f;
|
||||||
|
import com.jme3.scene.Node;
|
||||||
|
import com.jme3.scene.Spatial;
|
||||||
|
import com.jme3.scene.debug.SkeletonDebugger;
|
||||||
|
|
||||||
|
public class HelloAnimation
|
||||||
|
extends SimpleApplication
|
||||||
|
implements AnimEventListener {
|
||||||
|
private AnimChannel channel;
|
||||||
|
private AnimControl control;
|
||||||
|
Node player;
|
||||||
|
private ActionListener actionListener = new ActionListener(){
|
||||||
|
|
||||||
|
public void onAction(String name, boolean keyPressed, float tpf) {
|
||||||
|
if (name.equals("Walk") && !keyPressed && !HelloAnimation.this.channel.getAnimationName().equals("Walk")) {
|
||||||
|
HelloAnimation.this.channel.setAnim("Walk", 0.5f);
|
||||||
|
HelloAnimation.this.channel.setLoopMode(LoopMode.Loop);
|
||||||
|
}
|
||||||
|
if (name.equals("Dodge") && !keyPressed && !HelloAnimation.this.channel.getAnimationName().equals("Push")) {
|
||||||
|
HelloAnimation.this.channel.setAnim("push", 1.0f);
|
||||||
|
HelloAnimation.this.channel.setLoopMode(LoopMode.DontLoop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static void main(String[] agrs) {
|
||||||
|
HelloAnimation app = new HelloAnimation();
|
||||||
|
app.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void simpleInitApp() {
|
||||||
|
this.viewPort.setBackgroundColor(ColorRGBA.LightGray);
|
||||||
|
this.initKeys();
|
||||||
|
DirectionalLight dl = new DirectionalLight();
|
||||||
|
dl.setDirection(new Vector3f(-0.1f, -1.0f, -1.0f).normalizeLocal());
|
||||||
|
this.rootNode.addLight((Light)dl);
|
||||||
|
this.player = (Node)this.assetManager.loadModel("Models/Oto/Oto.mesh.xml");
|
||||||
|
this.player.setLocalScale(0.5f);
|
||||||
|
this.rootNode.attachChild((Spatial)this.player);
|
||||||
|
this.control = (AnimControl)this.player.getControl(AnimControl.class);
|
||||||
|
this.control.addListener((AnimEventListener)this);
|
||||||
|
this.channel = this.control.createChannel();
|
||||||
|
this.channel.setAnim("stand");
|
||||||
|
SkeletonDebugger skeletonDebug = new SkeletonDebugger("skeleton", this.control.getSkeleton());
|
||||||
|
Material mat = new Material(this.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||||
|
mat.setColor("Color", ColorRGBA.Green);
|
||||||
|
mat.getAdditionalRenderState().setDepthTest(false);
|
||||||
|
skeletonDebug.setMaterial(mat);
|
||||||
|
this.player.attachChild((Spatial)skeletonDebug);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) {
|
||||||
|
if (animName.equals("Walk")) {
|
||||||
|
channel.setAnim("stand", 0.5f);
|
||||||
|
channel.setLoopMode(LoopMode.DontLoop);
|
||||||
|
channel.setSpeed(1.0f);
|
||||||
|
}
|
||||||
|
if (animName.equals("push")) {
|
||||||
|
channel.setAnim("stand", 1.0f);
|
||||||
|
channel.setLoopMode(LoopMode.DontLoop);
|
||||||
|
channel.setSpeed(1.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onAnimChange(AnimControl control, AnimChannel channel, String animName) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initKeys() {
|
||||||
|
this.inputManager.addMapping("Walk", new Trigger[]{new KeyTrigger(57)});
|
||||||
|
this.inputManager.addMapping("Dodge", new Trigger[]{new MouseButtonTrigger(0)});
|
||||||
|
this.inputManager.addListener((InputListener)this.actionListener, new String[]{"Walk", "Dodge"});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* com.jme3.app.SimpleApplication
|
||||||
|
* com.jme3.font.BitmapText
|
||||||
|
* com.jme3.light.DirectionalLight
|
||||||
|
* com.jme3.light.Light
|
||||||
|
* com.jme3.material.Material
|
||||||
|
* com.jme3.math.Vector3f
|
||||||
|
* com.jme3.scene.Geometry
|
||||||
|
* com.jme3.scene.Mesh
|
||||||
|
* com.jme3.scene.Spatial
|
||||||
|
* com.jme3.scene.shape.Box
|
||||||
|
*/
|
||||||
|
package jme3.hello;
|
||||||
|
|
||||||
|
import com.jme3.app.SimpleApplication;
|
||||||
|
import com.jme3.font.BitmapText;
|
||||||
|
import com.jme3.light.DirectionalLight;
|
||||||
|
import com.jme3.light.Light;
|
||||||
|
import com.jme3.material.Material;
|
||||||
|
import com.jme3.math.Vector3f;
|
||||||
|
import com.jme3.scene.Geometry;
|
||||||
|
import com.jme3.scene.Mesh;
|
||||||
|
import com.jme3.scene.Spatial;
|
||||||
|
import com.jme3.scene.shape.Box;
|
||||||
|
|
||||||
|
public class HelloAssets
|
||||||
|
extends SimpleApplication {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
HelloAssets app = new HelloAssets();
|
||||||
|
app.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void simpleInitApp() {
|
||||||
|
Spatial teapot = this.assetManager.loadModel("Models/Teapot/Teapot.obj");
|
||||||
|
Material mat_default = new Material(this.assetManager, "Common/MatDefs/Misc/ShowNormals.j3md");
|
||||||
|
teapot.setMaterial(mat_default);
|
||||||
|
this.rootNode.attachChild(teapot);
|
||||||
|
Box box = new Box(Vector3f.ZERO, 2.5f, 2.5f, 1.0f);
|
||||||
|
Geometry wall = new Geometry("Box", (Mesh)box);
|
||||||
|
Material mat_brick = new Material(this.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||||
|
mat_brick.setTexture("ColorMap", this.assetManager.loadTexture("Textures/Terrain/BrickWall/BrickWall.jpg"));
|
||||||
|
wall.setMaterial(mat_brick);
|
||||||
|
wall.setLocalTranslation(2.0f, -2.5f, 0.0f);
|
||||||
|
this.rootNode.attachChild((Spatial)wall);
|
||||||
|
this.guiNode.detachAllChildren();
|
||||||
|
this.guiFont = this.assetManager.loadFont("Interface/Fonts/Default.fnt");
|
||||||
|
BitmapText helloText = new BitmapText(this.guiFont, false);
|
||||||
|
helloText.setSize((float)this.guiFont.getCharSet().getRenderedSize());
|
||||||
|
helloText.setText("Hello World");
|
||||||
|
helloText.setLocalTranslation(300.0f, helloText.getLineHeight(), 0.0f);
|
||||||
|
this.guiNode.attachChild((Spatial)helloText);
|
||||||
|
Spatial ninja = this.assetManager.loadModel("Models/Ninja/Ninja.mesh.xml");
|
||||||
|
ninja.scale(0.05f, 0.05f, 0.05f);
|
||||||
|
ninja.rotate(0.0f, -3.0f, 0.0f);
|
||||||
|
ninja.setLocalTranslation(0.0f, -5.0f, -2.0f);
|
||||||
|
this.rootNode.attachChild(ninja);
|
||||||
|
DirectionalLight sun = new DirectionalLight();
|
||||||
|
sun.setDirection(new Vector3f(-0.1f, -0.7f, -1.0f));
|
||||||
|
this.rootNode.addLight((Light)sun);
|
||||||
|
Spatial gameLevel = this.assetManager.loadModel("Scenes/main.j3o");
|
||||||
|
gameLevel.setLocalTranslation(0.0f, -5.2f, 0.0f);
|
||||||
|
gameLevel.setLocalScale(2.0f);
|
||||||
|
this.rootNode.attachChild(gameLevel);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* com.jme3.app.SimpleApplication
|
||||||
|
* com.jme3.audio.AudioNode
|
||||||
|
* com.jme3.input.controls.ActionListener
|
||||||
|
* com.jme3.input.controls.InputListener
|
||||||
|
* com.jme3.input.controls.MouseButtonTrigger
|
||||||
|
* com.jme3.input.controls.Trigger
|
||||||
|
* com.jme3.material.Material
|
||||||
|
* com.jme3.math.ColorRGBA
|
||||||
|
* com.jme3.math.Vector3f
|
||||||
|
* com.jme3.scene.Geometry
|
||||||
|
* com.jme3.scene.Mesh
|
||||||
|
* com.jme3.scene.Spatial
|
||||||
|
* com.jme3.scene.shape.Box
|
||||||
|
*/
|
||||||
|
package jme3.hello;
|
||||||
|
|
||||||
|
import com.jme3.app.SimpleApplication;
|
||||||
|
import com.jme3.audio.AudioNode;
|
||||||
|
import com.jme3.input.controls.ActionListener;
|
||||||
|
import com.jme3.input.controls.InputListener;
|
||||||
|
import com.jme3.input.controls.MouseButtonTrigger;
|
||||||
|
import com.jme3.input.controls.Trigger;
|
||||||
|
import com.jme3.material.Material;
|
||||||
|
import com.jme3.math.ColorRGBA;
|
||||||
|
import com.jme3.math.Vector3f;
|
||||||
|
import com.jme3.scene.Geometry;
|
||||||
|
import com.jme3.scene.Mesh;
|
||||||
|
import com.jme3.scene.Spatial;
|
||||||
|
import com.jme3.scene.shape.Box;
|
||||||
|
|
||||||
|
public class HelloAudio
|
||||||
|
extends SimpleApplication {
|
||||||
|
private AudioNode audio_gun;
|
||||||
|
private AudioNode audio_nature;
|
||||||
|
private Geometry player;
|
||||||
|
private ActionListener actionListener = new ActionListener(){
|
||||||
|
|
||||||
|
public void onAction(String name, boolean keyPressed, float tpf) {
|
||||||
|
if (name.equals("Shoot") && !keyPressed) {
|
||||||
|
HelloAudio.this.audio_gun.playInstance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
HelloAudio app = new HelloAudio();
|
||||||
|
app.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void simpleInitApp() {
|
||||||
|
this.flyCam.setMoveSpeed(40.0f);
|
||||||
|
Box box1 = new Box(Vector3f.ZERO, 1.0f, 1.0f, 1.0f);
|
||||||
|
this.player = new Geometry("Player", (Mesh)box1);
|
||||||
|
Material mat1 = new Material(this.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||||
|
mat1.setColor("Color", ColorRGBA.Blue);
|
||||||
|
this.player.setMaterial(mat1);
|
||||||
|
this.rootNode.attachChild((Spatial)this.player);
|
||||||
|
this.initKeys();
|
||||||
|
this.initAudio();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initAudio() {
|
||||||
|
this.audio_gun = new AudioNode(this.assetManager, "Sound/Effects/Gun.wav", false);
|
||||||
|
this.audio_gun.setLooping(false);
|
||||||
|
this.audio_gun.setVolume(2.0f);
|
||||||
|
this.rootNode.attachChild((Spatial)this.audio_gun);
|
||||||
|
this.audio_nature = new AudioNode(this.assetManager, "Sound/Environment/Nature.ogg", false);
|
||||||
|
this.audio_nature.setLooping(true);
|
||||||
|
this.audio_nature.setPositional(false);
|
||||||
|
this.audio_nature.setLocalTranslation(Vector3f.ZERO.clone());
|
||||||
|
this.audio_nature.setVolume(3.0f);
|
||||||
|
this.rootNode.attachChild((Spatial)this.audio_nature);
|
||||||
|
this.audio_nature.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initKeys() {
|
||||||
|
this.inputManager.addMapping("Shoot", new Trigger[]{new MouseButtonTrigger(0)});
|
||||||
|
this.inputManager.addListener((InputListener)this.actionListener, new String[]{"Shoot"});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void simpleUpdate(float tpf) {
|
||||||
|
this.listener.setLocation(this.cam.getLocation());
|
||||||
|
this.listener.setRotation(this.cam.getRotation());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* com.jme3.app.SimpleApplication
|
||||||
|
* com.jme3.app.state.AppState
|
||||||
|
* com.jme3.asset.plugins.ZipLocator
|
||||||
|
* com.jme3.bullet.BulletAppState
|
||||||
|
* com.jme3.bullet.collision.shapes.CapsuleCollisionShape
|
||||||
|
* com.jme3.bullet.collision.shapes.CollisionShape
|
||||||
|
* com.jme3.bullet.control.CharacterControl
|
||||||
|
* com.jme3.bullet.control.RigidBodyControl
|
||||||
|
* com.jme3.bullet.util.CollisionShapeFactory
|
||||||
|
* com.jme3.input.controls.ActionListener
|
||||||
|
* com.jme3.input.controls.InputListener
|
||||||
|
* com.jme3.input.controls.KeyTrigger
|
||||||
|
* com.jme3.input.controls.Trigger
|
||||||
|
* com.jme3.light.AmbientLight
|
||||||
|
* com.jme3.light.DirectionalLight
|
||||||
|
* com.jme3.light.Light
|
||||||
|
* com.jme3.math.ColorRGBA
|
||||||
|
* com.jme3.math.Vector3f
|
||||||
|
* com.jme3.scene.Node
|
||||||
|
* com.jme3.scene.Spatial
|
||||||
|
* com.jme3.scene.control.Control
|
||||||
|
*/
|
||||||
|
package jme3.hello;
|
||||||
|
|
||||||
|
import com.jme3.app.SimpleApplication;
|
||||||
|
import com.jme3.app.state.AppState;
|
||||||
|
import com.jme3.asset.plugins.ZipLocator;
|
||||||
|
import com.jme3.bullet.BulletAppState;
|
||||||
|
import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
|
||||||
|
import com.jme3.bullet.collision.shapes.CollisionShape;
|
||||||
|
import com.jme3.bullet.control.CharacterControl;
|
||||||
|
import com.jme3.bullet.control.RigidBodyControl;
|
||||||
|
import com.jme3.bullet.util.CollisionShapeFactory;
|
||||||
|
import com.jme3.input.controls.ActionListener;
|
||||||
|
import com.jme3.input.controls.InputListener;
|
||||||
|
import com.jme3.input.controls.KeyTrigger;
|
||||||
|
import com.jme3.input.controls.Trigger;
|
||||||
|
import com.jme3.light.AmbientLight;
|
||||||
|
import com.jme3.light.DirectionalLight;
|
||||||
|
import com.jme3.light.Light;
|
||||||
|
import com.jme3.math.ColorRGBA;
|
||||||
|
import com.jme3.math.Vector3f;
|
||||||
|
import com.jme3.scene.Node;
|
||||||
|
import com.jme3.scene.Spatial;
|
||||||
|
import com.jme3.scene.control.Control;
|
||||||
|
|
||||||
|
public class HelloCollision
|
||||||
|
extends SimpleApplication
|
||||||
|
implements ActionListener {
|
||||||
|
private Spatial sceneModel;
|
||||||
|
private BulletAppState bulletAppState;
|
||||||
|
private RigidBodyControl landscape;
|
||||||
|
private CharacterControl player;
|
||||||
|
private Vector3f walkDirection = new Vector3f();
|
||||||
|
private boolean left = false;
|
||||||
|
private boolean right = false;
|
||||||
|
private boolean up = false;
|
||||||
|
private boolean down = false;
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
HelloCollision app = new HelloCollision();
|
||||||
|
app.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void simpleInitApp() {
|
||||||
|
this.bulletAppState = new BulletAppState();
|
||||||
|
this.stateManager.attach((AppState)this.bulletAppState);
|
||||||
|
this.bulletAppState.getPhysicsSpace().enableDebug(this.assetManager);
|
||||||
|
this.viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1.0f, 1.0f));
|
||||||
|
this.flyCam.setMoveSpeed(100.0f);
|
||||||
|
this.setUpKeys();
|
||||||
|
this.setUpLight();
|
||||||
|
this.assetManager.registerLocator("town.zip", ZipLocator.class);
|
||||||
|
this.sceneModel = this.assetManager.loadModel("main.scene");
|
||||||
|
this.sceneModel.setLocalScale(2.0f);
|
||||||
|
CollisionShape sceneShape = CollisionShapeFactory.createMeshShape((Spatial)((Node)this.sceneModel));
|
||||||
|
this.landscape = new RigidBodyControl(sceneShape, 0.0f);
|
||||||
|
this.sceneModel.addControl((Control)this.landscape);
|
||||||
|
CapsuleCollisionShape capsuleShape = new CapsuleCollisionShape(1.5f, 6.0f, 1);
|
||||||
|
this.player = new CharacterControl((CollisionShape)capsuleShape, 0.5f);
|
||||||
|
this.player.setJumpSpeed(20.0f);
|
||||||
|
this.player.setFallSpeed(30.0f);
|
||||||
|
this.player.setGravity(30.0f);
|
||||||
|
this.player.setPhysicsLocation(new Vector3f(0.0f, 10.0f, 0.0f));
|
||||||
|
this.rootNode.attachChild(this.sceneModel);
|
||||||
|
this.bulletAppState.getPhysicsSpace().add((Object)this.landscape);
|
||||||
|
this.bulletAppState.getPhysicsSpace().add((Object)this.player);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setUpLight() {
|
||||||
|
AmbientLight al = new AmbientLight();
|
||||||
|
al.setColor(ColorRGBA.White.mult(1.3f));
|
||||||
|
this.rootNode.addLight((Light)al);
|
||||||
|
DirectionalLight dl = new DirectionalLight();
|
||||||
|
dl.setColor(ColorRGBA.White);
|
||||||
|
dl.setDirection(new Vector3f(2.8f, -2.8f, -2.8f).normalizeLocal());
|
||||||
|
this.rootNode.addLight((Light)dl);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setUpKeys() {
|
||||||
|
this.inputManager.addMapping("Left", new Trigger[]{new KeyTrigger(30)});
|
||||||
|
this.inputManager.addMapping("Right", new Trigger[]{new KeyTrigger(32)});
|
||||||
|
this.inputManager.addMapping("Up", new Trigger[]{new KeyTrigger(17)});
|
||||||
|
this.inputManager.addMapping("Down", new Trigger[]{new KeyTrigger(31)});
|
||||||
|
this.inputManager.addMapping("Jump", new Trigger[]{new KeyTrigger(57)});
|
||||||
|
this.inputManager.addListener((InputListener)this, new String[]{"Left"});
|
||||||
|
this.inputManager.addListener((InputListener)this, new String[]{"Right"});
|
||||||
|
this.inputManager.addListener((InputListener)this, new String[]{"Up"});
|
||||||
|
this.inputManager.addListener((InputListener)this, new String[]{"Down"});
|
||||||
|
this.inputManager.addListener((InputListener)this, new String[]{"Jump"});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onAction(String binding, boolean value, float tpf) {
|
||||||
|
if (binding.equals("Left")) {
|
||||||
|
this.left = value;
|
||||||
|
} else if (binding.equals("Right")) {
|
||||||
|
this.right = value;
|
||||||
|
} else if (binding.equals("Up")) {
|
||||||
|
this.up = value;
|
||||||
|
} else if (binding.equals("Down")) {
|
||||||
|
this.down = value;
|
||||||
|
} else if (binding.equals("Jump")) {
|
||||||
|
this.player.jump();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void simpleUpdate(float tpf) {
|
||||||
|
Vector3f camDir = this.cam.getDirection().clone().multLocal(0.6f);
|
||||||
|
Vector3f camLeft = this.cam.getLeft().clone().multLocal(0.4f);
|
||||||
|
this.walkDirection.set(0.0f, 0.0f, 0.0f);
|
||||||
|
if (this.left) {
|
||||||
|
this.walkDirection.addLocal(camLeft);
|
||||||
|
}
|
||||||
|
if (this.right) {
|
||||||
|
this.walkDirection.addLocal(camLeft.negate());
|
||||||
|
}
|
||||||
|
if (this.up) {
|
||||||
|
this.walkDirection.addLocal(camDir);
|
||||||
|
}
|
||||||
|
if (this.down) {
|
||||||
|
this.walkDirection.addLocal(camDir.negate());
|
||||||
|
}
|
||||||
|
this.player.setWalkDirection(this.walkDirection);
|
||||||
|
this.cam.setLocation(this.player.getPhysicsLocation());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* com.jme3.app.SimpleApplication
|
||||||
|
* com.jme3.effect.ParticleEmitter
|
||||||
|
* com.jme3.effect.ParticleMesh$Type
|
||||||
|
* com.jme3.material.Material
|
||||||
|
* com.jme3.math.ColorRGBA
|
||||||
|
* com.jme3.math.Vector3f
|
||||||
|
* com.jme3.scene.Spatial
|
||||||
|
*/
|
||||||
|
package jme3.hello;
|
||||||
|
|
||||||
|
import com.jme3.app.SimpleApplication;
|
||||||
|
import com.jme3.effect.ParticleEmitter;
|
||||||
|
import com.jme3.effect.ParticleMesh;
|
||||||
|
import com.jme3.material.Material;
|
||||||
|
import com.jme3.math.ColorRGBA;
|
||||||
|
import com.jme3.math.Vector3f;
|
||||||
|
import com.jme3.scene.Spatial;
|
||||||
|
|
||||||
|
public class HelloEffects
|
||||||
|
extends SimpleApplication {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
HelloEffects app = new HelloEffects();
|
||||||
|
app.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void simpleInitApp() {
|
||||||
|
ParticleEmitter fire = new ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, 30);
|
||||||
|
Material mat_red = new Material(this.assetManager, "Common/MatDefs/Misc/Particle.j3md");
|
||||||
|
mat_red.setTexture("Texture", this.assetManager.loadTexture("Effects/Explosion/flame.png"));
|
||||||
|
fire.setMaterial(mat_red);
|
||||||
|
fire.setImagesX(2);
|
||||||
|
fire.setImagesY(2);
|
||||||
|
fire.setEndColor(ColorRGBA.Cyan);
|
||||||
|
fire.setStartColor(ColorRGBA.Blue);
|
||||||
|
fire.getParticleInfluencer().setInitialVelocity(new Vector3f(0.0f, -2.0f, 0.0f));
|
||||||
|
fire.setStartSize(0.1f);
|
||||||
|
fire.setEndSize(1.5f);
|
||||||
|
fire.setGravity(0.0f, 1.0f, 0.0f);
|
||||||
|
fire.setLowLife(1.0f);
|
||||||
|
fire.setHighLife(3.0f);
|
||||||
|
fire.getParticleInfluencer().setVelocityVariation(0.3f);
|
||||||
|
this.rootNode.attachChild((Spatial)fire);
|
||||||
|
ParticleEmitter debris = new ParticleEmitter("Debris", ParticleMesh.Type.Triangle, 10);
|
||||||
|
Material debris_mat = new Material(this.assetManager, "Common/MatDefs/Misc/Particle.j3md");
|
||||||
|
debris_mat.setTexture("Texture", this.assetManager.loadTexture("Effects/Explosion/Debris.png"));
|
||||||
|
debris.setMaterial(debris_mat);
|
||||||
|
debris.setImagesX(3);
|
||||||
|
debris.setImagesY(3);
|
||||||
|
debris.setRotateSpeed(4.0f);
|
||||||
|
debris.setSelectRandomImage(true);
|
||||||
|
debris.getParticleInfluencer().setInitialVelocity(new Vector3f(0.0f, 4.0f, 0.0f));
|
||||||
|
debris.setStartColor(ColorRGBA.White);
|
||||||
|
debris.setGravity(0.0f, 6.0f, 0.0f);
|
||||||
|
debris.getParticleInfluencer().setVelocityVariation(0.6f);
|
||||||
|
this.rootNode.attachChild((Spatial)debris);
|
||||||
|
debris.emitAllParticles();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* com.jme3.app.SimpleApplication
|
||||||
|
* com.jme3.input.controls.ActionListener
|
||||||
|
* com.jme3.input.controls.AnalogListener
|
||||||
|
* com.jme3.input.controls.InputListener
|
||||||
|
* com.jme3.input.controls.KeyTrigger
|
||||||
|
* com.jme3.input.controls.MouseAxisTrigger
|
||||||
|
* com.jme3.input.controls.MouseButtonTrigger
|
||||||
|
* com.jme3.input.controls.Trigger
|
||||||
|
* com.jme3.light.DirectionalLight
|
||||||
|
* com.jme3.light.Light
|
||||||
|
* com.jme3.material.Material
|
||||||
|
* com.jme3.math.ColorRGBA
|
||||||
|
* com.jme3.math.Vector3f
|
||||||
|
* com.jme3.scene.Geometry
|
||||||
|
* com.jme3.scene.Mesh
|
||||||
|
* com.jme3.scene.Spatial
|
||||||
|
* com.jme3.scene.shape.Box
|
||||||
|
*/
|
||||||
|
package jme3.hello;
|
||||||
|
|
||||||
|
import com.jme3.app.SimpleApplication;
|
||||||
|
import com.jme3.input.controls.ActionListener;
|
||||||
|
import com.jme3.input.controls.AnalogListener;
|
||||||
|
import com.jme3.input.controls.InputListener;
|
||||||
|
import com.jme3.input.controls.KeyTrigger;
|
||||||
|
import com.jme3.input.controls.MouseAxisTrigger;
|
||||||
|
import com.jme3.input.controls.MouseButtonTrigger;
|
||||||
|
import com.jme3.input.controls.Trigger;
|
||||||
|
import com.jme3.light.DirectionalLight;
|
||||||
|
import com.jme3.light.Light;
|
||||||
|
import com.jme3.material.Material;
|
||||||
|
import com.jme3.math.ColorRGBA;
|
||||||
|
import com.jme3.math.Vector3f;
|
||||||
|
import com.jme3.scene.Geometry;
|
||||||
|
import com.jme3.scene.Mesh;
|
||||||
|
import com.jme3.scene.Spatial;
|
||||||
|
import com.jme3.scene.shape.Box;
|
||||||
|
|
||||||
|
public class HelloInput
|
||||||
|
extends SimpleApplication {
|
||||||
|
protected Geometry player;
|
||||||
|
boolean isRunning = true;
|
||||||
|
private ActionListener actionListener = new ActionListener(){
|
||||||
|
|
||||||
|
public void onAction(String name, boolean keyPressed, float tpf) {
|
||||||
|
if (name.equals("Pause") && !keyPressed) {
|
||||||
|
HelloInput.this.isRunning = !HelloInput.this.isRunning;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private AnalogListener analogListener = new AnalogListener(){
|
||||||
|
|
||||||
|
public void onAnalog(String name, float value, float tpf) {
|
||||||
|
if (HelloInput.this.isRunning) {
|
||||||
|
Vector3f v;
|
||||||
|
if (name.equals("RotateLeft")) {
|
||||||
|
HelloInput.this.player.rotate(0.0f, value * HelloInput.this.speed, 0.0f);
|
||||||
|
}
|
||||||
|
if (name.equals("RotateRight")) {
|
||||||
|
HelloInput.this.player.rotate(0.0f, value * -HelloInput.this.speed, 0.0f);
|
||||||
|
}
|
||||||
|
if (name.equals("Right")) {
|
||||||
|
v = HelloInput.this.player.getLocalTranslation();
|
||||||
|
HelloInput.this.player.setLocalTranslation(v.x + value * HelloInput.this.speed, v.y, v.z);
|
||||||
|
}
|
||||||
|
if (name.equals("Left")) {
|
||||||
|
v = HelloInput.this.player.getLocalTranslation();
|
||||||
|
HelloInput.this.player.setLocalTranslation(v.x - value * HelloInput.this.speed, v.y, v.z);
|
||||||
|
}
|
||||||
|
if (name.equals("Up")) {
|
||||||
|
v = HelloInput.this.player.getLocalTranslation();
|
||||||
|
HelloInput.this.player.setLocalTranslation(v.x, v.y + value * HelloInput.this.speed, v.z);
|
||||||
|
}
|
||||||
|
if (name.equals("Down")) {
|
||||||
|
v = HelloInput.this.player.getLocalTranslation();
|
||||||
|
HelloInput.this.player.setLocalTranslation(v.x, v.y - value * HelloInput.this.speed, v.z);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
System.out.println("Press P to unpause.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
HelloInput app = new HelloInput();
|
||||||
|
app.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void simpleInitApp() {
|
||||||
|
Box b = new Box(Vector3f.ZERO, 1.0f, 1.0f, 1.0f);
|
||||||
|
this.player = new Geometry("Player", (Mesh)b);
|
||||||
|
Material mat = new Material(this.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||||
|
mat.setColor("Color", ColorRGBA.randomColor());
|
||||||
|
this.player.setMaterial(mat);
|
||||||
|
this.rootNode.attachChild((Spatial)this.player);
|
||||||
|
this.initKeys();
|
||||||
|
Spatial gameLevel = this.assetManager.loadModel("Scenes/main.j3o");
|
||||||
|
gameLevel.setLocalTranslation(0.0f, -5.2f, 0.0f);
|
||||||
|
gameLevel.setLocalScale(2.0f);
|
||||||
|
this.rootNode.attachChild(gameLevel);
|
||||||
|
DirectionalLight sun = new DirectionalLight();
|
||||||
|
sun.setColor(ColorRGBA.White);
|
||||||
|
sun.setDirection(new Vector3f(-0.5f, -0.5f, -0.5f).normalizeLocal());
|
||||||
|
this.rootNode.addLight((Light)sun);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initKeys() {
|
||||||
|
this.inputManager.addMapping("Pause", new Trigger[]{new KeyTrigger(25)});
|
||||||
|
this.inputManager.addMapping("Left", new Trigger[]{new KeyTrigger(36)});
|
||||||
|
this.inputManager.addMapping("Right", new Trigger[]{new KeyTrigger(38)});
|
||||||
|
this.inputManager.addMapping("Up", new Trigger[]{new KeyTrigger(23), new MouseAxisTrigger(2, true)});
|
||||||
|
this.inputManager.addMapping("Down", new Trigger[]{new KeyTrigger(37), new MouseAxisTrigger(2, false)});
|
||||||
|
this.inputManager.addMapping("RotateLeft", new Trigger[]{new KeyTrigger(57), new MouseButtonTrigger(0)});
|
||||||
|
this.inputManager.addMapping("RotateRight", new Trigger[]{new MouseButtonTrigger(1)});
|
||||||
|
this.inputManager.addListener((InputListener)this.actionListener, new String[]{"Pause"});
|
||||||
|
this.inputManager.addListener((InputListener)this.analogListener, new String[]{"Left", "Right", "RotateLeft", "RotateRight", "Up", "Down"});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* com.jme3.app.SimpleApplication
|
||||||
|
* com.jme3.light.DirectionalLight
|
||||||
|
* com.jme3.light.Light
|
||||||
|
* com.jme3.material.Material
|
||||||
|
* com.jme3.material.RenderState$BlendMode
|
||||||
|
* com.jme3.math.ColorRGBA
|
||||||
|
* com.jme3.math.Vector3f
|
||||||
|
* com.jme3.renderer.queue.RenderQueue$Bucket
|
||||||
|
* com.jme3.scene.Geometry
|
||||||
|
* com.jme3.scene.Mesh
|
||||||
|
* com.jme3.scene.Spatial
|
||||||
|
* com.jme3.scene.shape.Box
|
||||||
|
* com.jme3.scene.shape.Sphere
|
||||||
|
* com.jme3.scene.shape.Sphere$TextureMode
|
||||||
|
* com.jme3.util.TangentBinormalGenerator
|
||||||
|
*/
|
||||||
|
package jme3.hello;
|
||||||
|
|
||||||
|
import com.jme3.app.SimpleApplication;
|
||||||
|
import com.jme3.light.DirectionalLight;
|
||||||
|
import com.jme3.light.Light;
|
||||||
|
import com.jme3.material.Material;
|
||||||
|
import com.jme3.material.RenderState;
|
||||||
|
import com.jme3.math.ColorRGBA;
|
||||||
|
import com.jme3.math.Vector3f;
|
||||||
|
import com.jme3.renderer.queue.RenderQueue;
|
||||||
|
import com.jme3.scene.Geometry;
|
||||||
|
import com.jme3.scene.Mesh;
|
||||||
|
import com.jme3.scene.Spatial;
|
||||||
|
import com.jme3.scene.shape.Box;
|
||||||
|
import com.jme3.scene.shape.Sphere;
|
||||||
|
import com.jme3.util.TangentBinormalGenerator;
|
||||||
|
|
||||||
|
public class HelloMaterial
|
||||||
|
extends SimpleApplication {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
HelloMaterial app = new HelloMaterial();
|
||||||
|
app.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void simpleInitApp() {
|
||||||
|
Box boxshape3 = new Box(new Vector3f(0.0f, 0.0f, 0.0f), 1.0f, 1.0f, 0.01f);
|
||||||
|
Geometry window_frame = new Geometry("window frame", (Mesh)boxshape3);
|
||||||
|
Material mat_tt = new Material(this.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||||
|
mat_tt.setTexture("ColorMap", this.assetManager.loadTexture("Textures/ColoredTex/Monkey.png"));
|
||||||
|
mat_tt.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
|
||||||
|
window_frame.setMaterial(mat_tt);
|
||||||
|
window_frame.setQueueBucket(RenderQueue.Bucket.Transparent);
|
||||||
|
this.rootNode.attachChild((Spatial)window_frame);
|
||||||
|
Box boxshape4 = new Box(new Vector3f(3.0f, -1.0f, 0.0f), 1.0f, 1.0f, 1.0f);
|
||||||
|
Geometry cube_leak = new Geometry("Leak-through color cube", (Mesh)boxshape4);
|
||||||
|
cube_leak.setMaterial(this.assetManager.loadMaterial("Materials/LeakThrough.j3m"));
|
||||||
|
this.rootNode.attachChild((Spatial)cube_leak);
|
||||||
|
Sphere rock = new Sphere(32, 32, 2.0f);
|
||||||
|
Geometry shiny_rock = new Geometry("Shiny rock", (Mesh)rock);
|
||||||
|
rock.setTextureMode(Sphere.TextureMode.Projected);
|
||||||
|
TangentBinormalGenerator.generate((Mesh)rock);
|
||||||
|
Material mat_lit = new Material(this.assetManager, "Common/MatDefs/Light/Lighting.j3md");
|
||||||
|
mat_lit.setTexture("DiffuseMap", this.assetManager.loadTexture("Textures/Terrain/Pond/Pond.jpg"));
|
||||||
|
mat_lit.setTexture("NormalMap", this.assetManager.loadTexture("Textures/Terrain/Pond/Pond_normal.png"));
|
||||||
|
mat_lit.setBoolean("UseMaterialColors", true);
|
||||||
|
mat_lit.setColor("Specular", ColorRGBA.White);
|
||||||
|
mat_lit.setColor("Diffuse", ColorRGBA.White);
|
||||||
|
mat_lit.setFloat("Shininess", 127.0f);
|
||||||
|
shiny_rock.setMaterial(mat_lit);
|
||||||
|
shiny_rock.setLocalTranslation(0.0f, 2.0f, -2.0f);
|
||||||
|
shiny_rock.rotate(1.6f, 0.0f, 0.0f);
|
||||||
|
this.rootNode.attachChild((Spatial)shiny_rock);
|
||||||
|
DirectionalLight sun = new DirectionalLight();
|
||||||
|
sun.setDirection(new Vector3f(1.0f, 0.0f, -2.0f).normalizeLocal());
|
||||||
|
sun.setColor(ColorRGBA.White);
|
||||||
|
this.rootNode.addLight((Light)sun);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* com.jme3.app.SimpleApplication
|
||||||
|
* com.jme3.app.state.AppState
|
||||||
|
* com.jme3.asset.TextureKey
|
||||||
|
* com.jme3.bullet.BulletAppState
|
||||||
|
* com.jme3.bullet.control.RigidBodyControl
|
||||||
|
* com.jme3.font.BitmapText
|
||||||
|
* com.jme3.input.controls.ActionListener
|
||||||
|
* com.jme3.input.controls.InputListener
|
||||||
|
* com.jme3.input.controls.MouseButtonTrigger
|
||||||
|
* com.jme3.input.controls.Trigger
|
||||||
|
* com.jme3.material.Material
|
||||||
|
* com.jme3.math.Vector2f
|
||||||
|
* com.jme3.math.Vector3f
|
||||||
|
* com.jme3.scene.Geometry
|
||||||
|
* com.jme3.scene.Mesh
|
||||||
|
* com.jme3.scene.Spatial
|
||||||
|
* com.jme3.scene.control.Control
|
||||||
|
* com.jme3.scene.shape.Box
|
||||||
|
* com.jme3.scene.shape.Sphere
|
||||||
|
* com.jme3.scene.shape.Sphere$TextureMode
|
||||||
|
* com.jme3.texture.Texture
|
||||||
|
* com.jme3.texture.Texture$WrapMode
|
||||||
|
*/
|
||||||
|
package jme3.hello;
|
||||||
|
|
||||||
|
import com.jme3.app.SimpleApplication;
|
||||||
|
import com.jme3.app.state.AppState;
|
||||||
|
import com.jme3.asset.TextureKey;
|
||||||
|
import com.jme3.bullet.BulletAppState;
|
||||||
|
import com.jme3.bullet.control.RigidBodyControl;
|
||||||
|
import com.jme3.font.BitmapText;
|
||||||
|
import com.jme3.input.controls.ActionListener;
|
||||||
|
import com.jme3.input.controls.InputListener;
|
||||||
|
import com.jme3.input.controls.MouseButtonTrigger;
|
||||||
|
import com.jme3.input.controls.Trigger;
|
||||||
|
import com.jme3.material.Material;
|
||||||
|
import com.jme3.math.Vector2f;
|
||||||
|
import com.jme3.math.Vector3f;
|
||||||
|
import com.jme3.scene.Geometry;
|
||||||
|
import com.jme3.scene.Mesh;
|
||||||
|
import com.jme3.scene.Spatial;
|
||||||
|
import com.jme3.scene.control.Control;
|
||||||
|
import com.jme3.scene.shape.Box;
|
||||||
|
import com.jme3.scene.shape.Sphere;
|
||||||
|
import com.jme3.texture.Texture;
|
||||||
|
|
||||||
|
public class HelloPhysik
|
||||||
|
extends SimpleApplication {
|
||||||
|
private BulletAppState bulletAppState;
|
||||||
|
Material wall_mat;
|
||||||
|
Material stone_mat;
|
||||||
|
Material floor_mat;
|
||||||
|
private RigidBodyControl brick_phy;
|
||||||
|
private static final Box box;
|
||||||
|
private RigidBodyControl ball_phy;
|
||||||
|
private static final Sphere sphere;
|
||||||
|
private RigidBodyControl floor_phy;
|
||||||
|
private static final Box floor;
|
||||||
|
private static final float brickLength = 0.48f;
|
||||||
|
private static final float brickWidth = 0.24f;
|
||||||
|
private static final float brickHeight = 0.12f;
|
||||||
|
private ActionListener actionListener = new ActionListener(){
|
||||||
|
|
||||||
|
public void onAction(String name, boolean keyPressed, float tpf) {
|
||||||
|
if (name.equals("shoot") && !keyPressed) {
|
||||||
|
HelloPhysik.this.makeCannonBall();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
HelloPhysik app = new HelloPhysik();
|
||||||
|
app.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void simpleInitApp() {
|
||||||
|
this.bulletAppState = new BulletAppState();
|
||||||
|
this.stateManager.attach((AppState)this.bulletAppState);
|
||||||
|
this.bulletAppState.getPhysicsSpace().enableDebug(this.assetManager);
|
||||||
|
this.cam.setLocation(new Vector3f(0.0f, 4.0f, 6.0f));
|
||||||
|
this.cam.lookAt(new Vector3f(2.0f, 2.0f, 0.0f), Vector3f.UNIT_Y);
|
||||||
|
this.inputManager.addMapping("shoot", new Trigger[]{new MouseButtonTrigger(0)});
|
||||||
|
this.inputManager.addListener((InputListener)this.actionListener, new String[]{"shoot"});
|
||||||
|
this.initMaterials();
|
||||||
|
this.initWall();
|
||||||
|
this.initFloor();
|
||||||
|
this.initCrossHairs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initMaterials() {
|
||||||
|
this.wall_mat = new Material(this.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||||
|
TextureKey key = new TextureKey("Textures/Terrain/BrickWall/BrickWall.jpg");
|
||||||
|
key.setGenerateMips(true);
|
||||||
|
Texture tex = this.assetManager.loadTexture(key);
|
||||||
|
this.wall_mat.setTexture("ColorMap", tex);
|
||||||
|
this.stone_mat = new Material(this.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||||
|
TextureKey key2 = new TextureKey("Textures/Terrain/Rock/Rock.PNG");
|
||||||
|
key2.setGenerateMips(true);
|
||||||
|
Texture tex2 = this.assetManager.loadTexture(key2);
|
||||||
|
this.stone_mat.setTexture("ColorMap", tex2);
|
||||||
|
this.floor_mat = new Material(this.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||||
|
TextureKey key3 = new TextureKey("Textures/Terrain/Pond/Pond.jpg");
|
||||||
|
key3.setGenerateMips(true);
|
||||||
|
Texture tex3 = this.assetManager.loadTexture(key3);
|
||||||
|
tex3.setWrap(Texture.WrapMode.Repeat);
|
||||||
|
this.floor_mat.setTexture("ColorMap", tex3);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initFloor() {
|
||||||
|
Geometry floor_geo = new Geometry("Floor", (Mesh)floor);
|
||||||
|
floor_geo.setMaterial(this.floor_mat);
|
||||||
|
floor_geo.setLocalTranslation(0.0f, -0.1f, 0.0f);
|
||||||
|
this.rootNode.attachChild((Spatial)floor_geo);
|
||||||
|
this.floor_phy = new RigidBodyControl(0.0f);
|
||||||
|
floor_geo.addControl((Control)this.floor_phy);
|
||||||
|
this.bulletAppState.getPhysicsSpace().add((Object)this.floor_phy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initWall() {
|
||||||
|
float startpt = 0.12f;
|
||||||
|
float height = 0.0f;
|
||||||
|
for (int j = 0; j < 15; ++j) {
|
||||||
|
for (int i = 0; i < 6; ++i) {
|
||||||
|
Vector3f vt = new Vector3f((float)i * 0.48f * 2.0f + startpt, 0.12f + height, 0.0f);
|
||||||
|
this.makeBrick(vt);
|
||||||
|
}
|
||||||
|
startpt = -startpt;
|
||||||
|
height += 0.24f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void makeBrick(Vector3f loc) {
|
||||||
|
Geometry brick_geo = new Geometry("brick", (Mesh)box);
|
||||||
|
brick_geo.setMaterial(this.wall_mat);
|
||||||
|
this.rootNode.attachChild((Spatial)brick_geo);
|
||||||
|
brick_geo.setLocalTranslation(loc);
|
||||||
|
this.brick_phy = new RigidBodyControl(2.0f);
|
||||||
|
brick_geo.addControl((Control)this.brick_phy);
|
||||||
|
this.bulletAppState.getPhysicsSpace().add((Object)this.brick_phy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void makeCannonBall() {
|
||||||
|
Geometry ball_geo = new Geometry("cannon ball", (Mesh)sphere);
|
||||||
|
ball_geo.setMaterial(this.stone_mat);
|
||||||
|
this.rootNode.attachChild((Spatial)ball_geo);
|
||||||
|
ball_geo.setLocalTranslation(this.cam.getLocation());
|
||||||
|
this.ball_phy = new RigidBodyControl(10.0f);
|
||||||
|
ball_geo.addControl((Control)this.ball_phy);
|
||||||
|
this.bulletAppState.getPhysicsSpace().add((Object)this.ball_phy);
|
||||||
|
this.ball_phy.setLinearVelocity(this.cam.getDirection().mult(25.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void initCrossHairs() {
|
||||||
|
this.guiNode.detachAllChildren();
|
||||||
|
this.guiFont = this.assetManager.loadFont("Interface/Fonts/Default.fnt");
|
||||||
|
BitmapText ch = new BitmapText(this.guiFont, false);
|
||||||
|
ch.setSize((float)(this.guiFont.getCharSet().getRenderedSize() * 2));
|
||||||
|
ch.setText("+");
|
||||||
|
ch.setLocalTranslation((float)(this.settings.getWidth() / 2 - this.guiFont.getCharSet().getRenderedSize() / 3 * 2), (float)(this.settings.getHeight() / 2) + ch.getLineHeight() / 2.0f, 0.0f);
|
||||||
|
this.guiNode.attachChild((Spatial)ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
sphere = new Sphere(32, 32, 0.4f, true, false);
|
||||||
|
sphere.setTextureMode(Sphere.TextureMode.Projected);
|
||||||
|
box = new Box(Vector3f.ZERO, 0.48f, 0.12f, 0.24f);
|
||||||
|
box.scaleTextureCoordinates(new Vector2f(1.0f, 0.5f));
|
||||||
|
floor = new Box(Vector3f.ZERO, 10.0f, 0.1f, 5.0f);
|
||||||
|
floor.scaleTextureCoordinates(new Vector2f(3.0f, 6.0f));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* com.jme3.app.SimpleApplication
|
||||||
|
* com.jme3.collision.Collidable
|
||||||
|
* com.jme3.collision.CollisionResult
|
||||||
|
* com.jme3.collision.CollisionResults
|
||||||
|
* com.jme3.effect.ParticleEmitter
|
||||||
|
* com.jme3.effect.ParticleMesh$Type
|
||||||
|
* com.jme3.font.BitmapText
|
||||||
|
* com.jme3.input.controls.ActionListener
|
||||||
|
* com.jme3.input.controls.InputListener
|
||||||
|
* com.jme3.input.controls.KeyTrigger
|
||||||
|
* com.jme3.input.controls.MouseButtonTrigger
|
||||||
|
* com.jme3.input.controls.Trigger
|
||||||
|
* com.jme3.light.DirectionalLight
|
||||||
|
* com.jme3.light.Light
|
||||||
|
* com.jme3.material.Material
|
||||||
|
* com.jme3.math.ColorRGBA
|
||||||
|
* com.jme3.math.Ray
|
||||||
|
* com.jme3.math.Vector3f
|
||||||
|
* com.jme3.scene.Geometry
|
||||||
|
* com.jme3.scene.Mesh
|
||||||
|
* com.jme3.scene.Node
|
||||||
|
* com.jme3.scene.Spatial
|
||||||
|
* com.jme3.scene.shape.Box
|
||||||
|
*/
|
||||||
|
package jme3.hello;
|
||||||
|
|
||||||
|
import com.jme3.app.SimpleApplication;
|
||||||
|
import com.jme3.collision.Collidable;
|
||||||
|
import com.jme3.collision.CollisionResult;
|
||||||
|
import com.jme3.collision.CollisionResults;
|
||||||
|
import com.jme3.effect.ParticleEmitter;
|
||||||
|
import com.jme3.effect.ParticleMesh;
|
||||||
|
import com.jme3.font.BitmapText;
|
||||||
|
import com.jme3.input.controls.ActionListener;
|
||||||
|
import com.jme3.input.controls.InputListener;
|
||||||
|
import com.jme3.input.controls.KeyTrigger;
|
||||||
|
import com.jme3.input.controls.MouseButtonTrigger;
|
||||||
|
import com.jme3.input.controls.Trigger;
|
||||||
|
import com.jme3.light.DirectionalLight;
|
||||||
|
import com.jme3.light.Light;
|
||||||
|
import com.jme3.material.Material;
|
||||||
|
import com.jme3.math.ColorRGBA;
|
||||||
|
import com.jme3.math.Ray;
|
||||||
|
import com.jme3.math.Vector3f;
|
||||||
|
import com.jme3.scene.Geometry;
|
||||||
|
import com.jme3.scene.Mesh;
|
||||||
|
import com.jme3.scene.Node;
|
||||||
|
import com.jme3.scene.Spatial;
|
||||||
|
import com.jme3.scene.shape.Box;
|
||||||
|
|
||||||
|
public class HelloPicking
|
||||||
|
extends SimpleApplication {
|
||||||
|
Node shootables;
|
||||||
|
Node inventory;
|
||||||
|
boolean pickedUp;
|
||||||
|
Geometry mark;
|
||||||
|
private ActionListener actionListener = new ActionListener(){
|
||||||
|
|
||||||
|
public void onAction(String name, boolean keyPressed, float tpf) {
|
||||||
|
Ray ray;
|
||||||
|
CollisionResults results;
|
||||||
|
if (name.equals("Shoot") && !keyPressed) {
|
||||||
|
results = new CollisionResults();
|
||||||
|
ray = new Ray(HelloPicking.this.cam.getLocation(), HelloPicking.this.cam.getDirection());
|
||||||
|
HelloPicking.this.shootables.collideWith((Collidable)ray, results);
|
||||||
|
System.out.println("----- Collisions?" + results.size() + "-----");
|
||||||
|
for (int i = 0; i < results.size(); ++i) {
|
||||||
|
float dist = results.getCollision(i).getDistance();
|
||||||
|
Vector3f pt = results.getCollision(i).getContactPoint();
|
||||||
|
String hit = results.getCollision(i).getGeometry().getName();
|
||||||
|
System.out.println("* Collision #" + i);
|
||||||
|
System.out.println(" You shot " + hit + "at" + pt + "," + dist + "wu away.");
|
||||||
|
}
|
||||||
|
if (results.size() > 0) {
|
||||||
|
CollisionResult closest = results.getClosestCollision();
|
||||||
|
HelloPicking.this.mark.setLocalTranslation(closest.getContactPoint());
|
||||||
|
Geometry geo = closest.getGeometry();
|
||||||
|
Boolean isModel = (Boolean)geo.getUserData("isModel");
|
||||||
|
if (isModel != null && !isModel.booleanValue()) {
|
||||||
|
Material mat = new Material(HelloPicking.this.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||||
|
mat.setColor("Color", ColorRGBA.randomColor());
|
||||||
|
geo.setMaterial(mat);
|
||||||
|
}
|
||||||
|
HelloPicking.this.rootNode.attachChild((Spatial)HelloPicking.this.mark);
|
||||||
|
} else {
|
||||||
|
HelloPicking.this.rootNode.detachChild((Spatial)HelloPicking.this.mark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (name.equals("Inventar") && !keyPressed) {
|
||||||
|
results = new CollisionResults();
|
||||||
|
ray = new Ray(HelloPicking.this.cam.getLocation(), HelloPicking.this.cam.getDirection());
|
||||||
|
HelloPicking.this.shootables.collideWith((Collidable)ray, results);
|
||||||
|
if (results.size() > 0) {
|
||||||
|
CollisionResult closest = results.getClosestCollision();
|
||||||
|
Geometry inv = closest.getGeometry();
|
||||||
|
if (!HelloPicking.this.pickedUp) {
|
||||||
|
HelloPicking.this.pickedUp = true;
|
||||||
|
HelloPicking.this.shootables.detachChild((Spatial)inv);
|
||||||
|
inv.setLocalScale(100.0f);
|
||||||
|
HelloPicking.this.inventory.attachChild((Spatial)inv);
|
||||||
|
} else {
|
||||||
|
HelloPicking.this.pickedUp = false;
|
||||||
|
Geometry box = (Geometry)HelloPicking.this.inventory.getChild(0);
|
||||||
|
box.setLocalScale(1.0f);
|
||||||
|
box.setLocalTranslation(closest.getContactPoint());
|
||||||
|
HelloPicking.this.inventory.detachChild((Spatial)box);
|
||||||
|
HelloPicking.this.shootables.attachChild((Spatial)box);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
HelloPicking app = new HelloPicking();
|
||||||
|
app.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void simpleInitApp() {
|
||||||
|
this.initCrossHairs();
|
||||||
|
this.initKeys();
|
||||||
|
this.initMark();
|
||||||
|
Spatial model = this.assetManager.loadModel("Models/Oto/Oto.mesh.xml");
|
||||||
|
model.setLocalTranslation(new Vector3f(1.0f, 2.0f, -3.0f));
|
||||||
|
model.setUserData("isModel", (Object)new Boolean(true));
|
||||||
|
model.setLocalScale(0.5f);
|
||||||
|
DirectionalLight light = new DirectionalLight();
|
||||||
|
light.setDirection(new Vector3f(-0.1f, -1.0f, -1.0f).normalizeLocal());
|
||||||
|
this.rootNode.addLight((Light)light);
|
||||||
|
this.shootables = new Node("Shootables");
|
||||||
|
this.inventory = new Node("Inventar");
|
||||||
|
this.guiNode.attachChild((Spatial)this.inventory);
|
||||||
|
this.rootNode.attachChild((Spatial)this.shootables);
|
||||||
|
this.shootables.attachChild((Spatial)this.makeCube("a Dragon", -2.0f, 0.0f, 1.0f));
|
||||||
|
this.shootables.attachChild((Spatial)this.makeCube("a tin can", 1.0f, -2.0f, 0.0f));
|
||||||
|
this.shootables.attachChild((Spatial)this.makeCube("the Sheriff", 0.0f, 1.0f, -2.0f));
|
||||||
|
this.shootables.attachChild((Spatial)this.makeCube("the Deputy", 1.0f, 0.0f, -4.0f));
|
||||||
|
this.shootables.attachChild((Spatial)this.makeFloor());
|
||||||
|
this.shootables.attachChild(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initKeys() {
|
||||||
|
this.inputManager.addMapping("Shoot", new Trigger[]{new KeyTrigger(57), new MouseButtonTrigger(0)});
|
||||||
|
this.inputManager.addMapping("Inventar", new Trigger[]{new MouseButtonTrigger(1)});
|
||||||
|
this.inputManager.addListener((InputListener)this.actionListener, new String[]{"Shoot", "Inventar"});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Geometry makeCube(String name, float x, float y, float z) {
|
||||||
|
Box box = new Box(new Vector3f(x, y, z), 1.0f, 1.0f, 1.0f);
|
||||||
|
Geometry cube = new Geometry(name, (Mesh)box);
|
||||||
|
cube.setUserData("isModel", (Object)new Boolean(false));
|
||||||
|
Material mat1 = new Material(this.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||||
|
mat1.setColor("Color", ColorRGBA.randomColor());
|
||||||
|
cube.setMaterial(mat1);
|
||||||
|
return cube;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Geometry makeFloor() {
|
||||||
|
Box box = new Box(new Vector3f(0.0f, -4.0f, -5.0f), 15.0f, 0.2f, 15.0f);
|
||||||
|
Geometry floor = new Geometry("the Floor", (Mesh)box);
|
||||||
|
Material mat1 = new Material(this.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||||
|
mat1.setColor("Color", ColorRGBA.LightGray);
|
||||||
|
floor.setMaterial(mat1);
|
||||||
|
return floor;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void initMark() {
|
||||||
|
ParticleEmitter fire = new ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, 30);
|
||||||
|
Material mat_red = new Material(this.assetManager, "Common/MatDefs/Misc/Particle.j3md");
|
||||||
|
mat_red.setTexture("Texture", this.assetManager.loadTexture("Effects/Explosion/flame.png"));
|
||||||
|
fire.setMaterial(mat_red);
|
||||||
|
fire.setImagesX(2);
|
||||||
|
fire.setImagesY(2);
|
||||||
|
fire.setEndColor(new ColorRGBA(1.0f, 0.0f, 0.0f, 1.0f));
|
||||||
|
fire.setStartColor(new ColorRGBA(1.0f, 1.0f, 0.0f, 0.5f));
|
||||||
|
fire.getParticleInfluencer().setInitialVelocity(new Vector3f(0.0f, 2.0f, 0.0f));
|
||||||
|
fire.setStartSize(1.5f);
|
||||||
|
fire.setEndSize(0.1f);
|
||||||
|
fire.setGravity(0.0f, 0.0f, 0.0f);
|
||||||
|
fire.setLowLife(1.0f);
|
||||||
|
fire.setHighLife(3.0f);
|
||||||
|
fire.getParticleInfluencer().setVelocityVariation(0.3f);
|
||||||
|
this.mark = fire;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void initCrossHairs() {
|
||||||
|
this.guiNode.detachAllChildren();
|
||||||
|
this.guiFont = this.assetManager.loadFont("Interface/Fonts/Default.fnt");
|
||||||
|
BitmapText ch = new BitmapText(this.guiFont, false);
|
||||||
|
ch.setSize((float)(this.guiFont.getCharSet().getRenderedSize() * 2));
|
||||||
|
ch.setText("+");
|
||||||
|
ch.setLocalTranslation((float)(this.settings.getWidth() / 2 - this.guiFont.getCharSet().getRenderedSize() / 3 * 2), (float)(this.settings.getHeight() / 2) + ch.getLineHeight() / 2.0f, 0.0f);
|
||||||
|
this.guiNode.attachChild((Spatial)ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* com.jme3.app.SimpleApplication
|
||||||
|
* com.jme3.material.Material
|
||||||
|
* com.jme3.scene.Spatial
|
||||||
|
* com.jme3.scene.control.Control
|
||||||
|
* com.jme3.terrain.Terrain
|
||||||
|
* com.jme3.terrain.geomipmap.TerrainLodControl
|
||||||
|
* com.jme3.terrain.geomipmap.TerrainQuad
|
||||||
|
* com.jme3.terrain.heightmap.ImageBasedHeightMap
|
||||||
|
* com.jme3.texture.Texture
|
||||||
|
* com.jme3.texture.Texture$WrapMode
|
||||||
|
*/
|
||||||
|
package jme3.hello;
|
||||||
|
|
||||||
|
import com.jme3.app.SimpleApplication;
|
||||||
|
import com.jme3.material.Material;
|
||||||
|
import com.jme3.scene.Spatial;
|
||||||
|
import com.jme3.scene.control.Control;
|
||||||
|
import com.jme3.terrain.Terrain;
|
||||||
|
import com.jme3.terrain.geomipmap.TerrainLodControl;
|
||||||
|
import com.jme3.terrain.geomipmap.TerrainQuad;
|
||||||
|
import com.jme3.terrain.heightmap.ImageBasedHeightMap;
|
||||||
|
import com.jme3.texture.Texture;
|
||||||
|
|
||||||
|
public class HelloTerrain
|
||||||
|
extends SimpleApplication {
|
||||||
|
private TerrainQuad terrain;
|
||||||
|
Material mat_terrain;
|
||||||
|
|
||||||
|
public static void main(String[] agrs) {
|
||||||
|
HelloTerrain app = new HelloTerrain();
|
||||||
|
app.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void simpleInitApp() {
|
||||||
|
this.flyCam.setMoveSpeed(50.0f);
|
||||||
|
this.mat_terrain = new Material(this.assetManager, "Common/MatDefs/Terrain/Terrain.j3md");
|
||||||
|
this.mat_terrain.setTexture("Alpha", this.assetManager.loadTexture("Textures/Terrain/splat/alphamap.png"));
|
||||||
|
Texture grass = this.assetManager.loadTexture("Textures/Terrain/splat/grass.jpg");
|
||||||
|
grass.setWrap(Texture.WrapMode.Repeat);
|
||||||
|
this.mat_terrain.setTexture("Tex1", grass);
|
||||||
|
this.mat_terrain.setFloat("Tex1Scale", 64.0f);
|
||||||
|
Texture dirt = this.assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg");
|
||||||
|
dirt.setWrap(Texture.WrapMode.Repeat);
|
||||||
|
this.mat_terrain.setTexture("Tex2", dirt);
|
||||||
|
this.mat_terrain.setFloat("Tex2Scale", 32.0f);
|
||||||
|
Texture rock = this.assetManager.loadTexture("Textures/Terrain/splat/road.jpg");
|
||||||
|
rock.setWrap(Texture.WrapMode.Repeat);
|
||||||
|
this.mat_terrain.setTexture("Tex3", rock);
|
||||||
|
this.mat_terrain.setFloat("Tex3Scale", 128.0f);
|
||||||
|
ImageBasedHeightMap heightmap = null;
|
||||||
|
Texture heightMapImage = this.assetManager.loadTexture("Textures/Terrain/splat/mountains512.png");
|
||||||
|
heightmap = new ImageBasedHeightMap(heightMapImage.getImage());
|
||||||
|
heightmap.load();
|
||||||
|
int patchSize = 65;
|
||||||
|
this.terrain = new TerrainQuad("My terrain", patchSize, 513, heightmap.getHeightMap());
|
||||||
|
this.terrain.setMaterial(this.mat_terrain);
|
||||||
|
this.terrain.setLocalTranslation(0.0f, -100.0f, 0.0f);
|
||||||
|
this.terrain.setLocalScale(2.0f, 1.0f, 2.0f);
|
||||||
|
this.rootNode.attachChild((Spatial)this.terrain);
|
||||||
|
TerrainLodControl control = new TerrainLodControl((Terrain)this.terrain, this.getCamera());
|
||||||
|
this.terrain.addControl((Control)control);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* com.jme3.app.SimpleApplication
|
||||||
|
* com.jme3.material.Material
|
||||||
|
* com.jme3.math.ColorRGBA
|
||||||
|
* com.jme3.math.Vector3f
|
||||||
|
* com.jme3.scene.Geometry
|
||||||
|
* com.jme3.scene.Mesh
|
||||||
|
* com.jme3.scene.Node
|
||||||
|
* com.jme3.scene.Spatial
|
||||||
|
* com.jme3.scene.shape.Box
|
||||||
|
*/
|
||||||
|
package jme3.hello;
|
||||||
|
|
||||||
|
import com.jme3.app.SimpleApplication;
|
||||||
|
import com.jme3.material.Material;
|
||||||
|
import com.jme3.math.ColorRGBA;
|
||||||
|
import com.jme3.math.Vector3f;
|
||||||
|
import com.jme3.scene.Geometry;
|
||||||
|
import com.jme3.scene.Mesh;
|
||||||
|
import com.jme3.scene.Node;
|
||||||
|
import com.jme3.scene.Spatial;
|
||||||
|
import com.jme3.scene.shape.Box;
|
||||||
|
|
||||||
|
public class HelloWorld
|
||||||
|
extends SimpleApplication {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
HelloWorld app = new HelloWorld();
|
||||||
|
app.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void simpleInitApp() {
|
||||||
|
Box box1 = new Box(Vector3f.ZERO, 1.0f, 1.0f, 1.0f);
|
||||||
|
Geometry blue = new Geometry("Box", (Mesh)box1);
|
||||||
|
Material mat1 = new Material(this.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||||
|
mat1.setColor("Color", ColorRGBA.Blue);
|
||||||
|
blue.setMaterial(mat1);
|
||||||
|
blue.move(1.0f, -1.0f, 1.0f);
|
||||||
|
Box box2 = new Box(Vector3f.ZERO, 1.0f, 1.0f, 1.0f);
|
||||||
|
Geometry red = new Geometry("Box", (Mesh)box2);
|
||||||
|
Material mat2 = new Material(this.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||||
|
mat2.setColor("Color", ColorRGBA.Red);
|
||||||
|
red.setMaterial(mat2);
|
||||||
|
red.move(1.0f, 3.0f, 4.0f);
|
||||||
|
Node pivot = new Node("pivot");
|
||||||
|
this.rootNode.attachChild((Spatial)pivot);
|
||||||
|
pivot.attachChild((Spatial)blue);
|
||||||
|
pivot.attachChild((Spatial)red);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* com.jme3.app.SimpleApplication
|
||||||
|
* com.jme3.material.Material
|
||||||
|
* com.jme3.math.ColorRGBA
|
||||||
|
* com.jme3.math.Vector3f
|
||||||
|
* com.jme3.scene.Geometry
|
||||||
|
* com.jme3.scene.Mesh
|
||||||
|
* com.jme3.scene.Spatial
|
||||||
|
* com.jme3.scene.shape.Box
|
||||||
|
*/
|
||||||
|
package jme3.hello;
|
||||||
|
|
||||||
|
import com.jme3.app.SimpleApplication;
|
||||||
|
import com.jme3.material.Material;
|
||||||
|
import com.jme3.math.ColorRGBA;
|
||||||
|
import com.jme3.math.Vector3f;
|
||||||
|
import com.jme3.scene.Geometry;
|
||||||
|
import com.jme3.scene.Mesh;
|
||||||
|
import com.jme3.scene.Spatial;
|
||||||
|
import com.jme3.scene.shape.Box;
|
||||||
|
|
||||||
|
public class SimpleLoop
|
||||||
|
extends SimpleApplication {
|
||||||
|
protected Geometry player;
|
||||||
|
protected Geometry box2;
|
||||||
|
boolean shrink;
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SimpleLoop app = new SimpleLoop();
|
||||||
|
app.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void simpleInitApp() {
|
||||||
|
Box b = new Box(Vector3f.ZERO, 1.0f, 1.0f, 1.0f);
|
||||||
|
this.player = new Geometry("blue cube", (Mesh)b);
|
||||||
|
Material mat = new Material(this.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||||
|
mat.setColor("Color", ColorRGBA.Yellow);
|
||||||
|
this.player.setMaterial(mat);
|
||||||
|
this.rootNode.attachChild((Spatial)this.player);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void simpleUpdate(float tpf) {
|
||||||
|
this.player.rotate(-2.0f * tpf, 0.0f, -2.0f * tpf);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,522 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* com.jme3.animation.AnimChannel
|
||||||
|
* com.jme3.animation.AnimControl
|
||||||
|
* com.jme3.animation.AnimEventListener
|
||||||
|
* com.jme3.animation.LoopMode
|
||||||
|
* com.jme3.app.Application
|
||||||
|
* com.jme3.app.SimpleApplication
|
||||||
|
* com.jme3.app.state.AppState
|
||||||
|
* com.jme3.audio.AudioNode
|
||||||
|
* com.jme3.bullet.BulletAppState
|
||||||
|
* com.jme3.bullet.collision.PhysicsCollisionEvent
|
||||||
|
* com.jme3.bullet.collision.PhysicsCollisionListener
|
||||||
|
* com.jme3.bullet.control.RigidBodyControl
|
||||||
|
* com.jme3.collision.Collidable
|
||||||
|
* com.jme3.collision.CollisionResults
|
||||||
|
* com.jme3.input.controls.ActionListener
|
||||||
|
* com.jme3.input.controls.AnalogListener
|
||||||
|
* com.jme3.input.controls.InputListener
|
||||||
|
* com.jme3.input.controls.KeyTrigger
|
||||||
|
* com.jme3.input.controls.MouseButtonTrigger
|
||||||
|
* com.jme3.input.controls.Trigger
|
||||||
|
* com.jme3.light.AmbientLight
|
||||||
|
* com.jme3.light.Light
|
||||||
|
* com.jme3.math.ColorRGBA
|
||||||
|
* com.jme3.math.Ray
|
||||||
|
* com.jme3.math.Vector2f
|
||||||
|
* com.jme3.math.Vector3f
|
||||||
|
* com.jme3.post.Filter
|
||||||
|
* com.jme3.post.FilterPostProcessor
|
||||||
|
* com.jme3.post.SceneProcessor
|
||||||
|
* com.jme3.post.filters.CartoonEdgeFilter
|
||||||
|
* com.jme3.renderer.Caps
|
||||||
|
* com.jme3.renderer.queue.RenderQueue$ShadowMode
|
||||||
|
* com.jme3.scene.Spatial
|
||||||
|
* com.jme3.scene.control.Control
|
||||||
|
* com.jme3.shadow.DirectionalLightShadowFilter
|
||||||
|
* com.jme3.shadow.DirectionalLightShadowRenderer
|
||||||
|
* tonegod.gui.controls.extras.Indicator
|
||||||
|
* tonegod.gui.controls.extras.Indicator$Orientation
|
||||||
|
* tonegod.gui.controls.text.Label
|
||||||
|
* tonegod.gui.core.Element
|
||||||
|
* tonegod.gui.core.Screen
|
||||||
|
* tonegod.gui.effects.Effect
|
||||||
|
* tonegod.gui.effects.Effect$EffectEvent
|
||||||
|
* tonegod.gui.effects.Effect$EffectType
|
||||||
|
*/
|
||||||
|
package jme3.hello;
|
||||||
|
|
||||||
|
import com.jme3.animation.AnimChannel;
|
||||||
|
import com.jme3.animation.AnimControl;
|
||||||
|
import com.jme3.animation.AnimEventListener;
|
||||||
|
import com.jme3.animation.LoopMode;
|
||||||
|
import com.jme3.app.Application;
|
||||||
|
import com.jme3.app.SimpleApplication;
|
||||||
|
import com.jme3.app.state.AppState;
|
||||||
|
import com.jme3.audio.AudioNode;
|
||||||
|
import com.jme3.bullet.BulletAppState;
|
||||||
|
import com.jme3.bullet.collision.PhysicsCollisionEvent;
|
||||||
|
import com.jme3.bullet.collision.PhysicsCollisionListener;
|
||||||
|
import com.jme3.bullet.control.RigidBodyControl;
|
||||||
|
import com.jme3.collision.Collidable;
|
||||||
|
import com.jme3.collision.CollisionResults;
|
||||||
|
import com.jme3.input.controls.ActionListener;
|
||||||
|
import com.jme3.input.controls.AnalogListener;
|
||||||
|
import com.jme3.input.controls.InputListener;
|
||||||
|
import com.jme3.input.controls.KeyTrigger;
|
||||||
|
import com.jme3.input.controls.MouseButtonTrigger;
|
||||||
|
import com.jme3.input.controls.Trigger;
|
||||||
|
import com.jme3.light.AmbientLight;
|
||||||
|
import com.jme3.light.Light;
|
||||||
|
import com.jme3.math.ColorRGBA;
|
||||||
|
import com.jme3.math.Ray;
|
||||||
|
import com.jme3.math.Vector2f;
|
||||||
|
import com.jme3.math.Vector3f;
|
||||||
|
import com.jme3.post.Filter;
|
||||||
|
import com.jme3.post.FilterPostProcessor;
|
||||||
|
import com.jme3.post.SceneProcessor;
|
||||||
|
import com.jme3.post.filters.CartoonEdgeFilter;
|
||||||
|
import com.jme3.renderer.Caps;
|
||||||
|
import com.jme3.renderer.queue.RenderQueue;
|
||||||
|
import com.jme3.scene.Spatial;
|
||||||
|
import com.jme3.scene.control.Control;
|
||||||
|
import com.jme3.shadow.DirectionalLightShadowFilter;
|
||||||
|
import com.jme3.shadow.DirectionalLightShadowRenderer;
|
||||||
|
import java.util.Random;
|
||||||
|
import mygame.Exchange;
|
||||||
|
import mygame.Player;
|
||||||
|
import mygame.Staff;
|
||||||
|
import mygame.Sword;
|
||||||
|
import mygame.WalkingEnemy;
|
||||||
|
import tonegod.gui.controls.extras.Indicator;
|
||||||
|
import tonegod.gui.controls.text.Label;
|
||||||
|
import tonegod.gui.core.Element;
|
||||||
|
import tonegod.gui.core.Screen;
|
||||||
|
import tonegod.gui.effects.Effect;
|
||||||
|
import wpq.tests.PlayerCam;
|
||||||
|
|
||||||
|
public class TestCameraNode
|
||||||
|
extends SimpleApplication
|
||||||
|
implements AnalogListener,
|
||||||
|
ActionListener,
|
||||||
|
AnimEventListener,
|
||||||
|
PhysicsCollisionListener {
|
||||||
|
private Player player;
|
||||||
|
Vector3f direction = new Vector3f();
|
||||||
|
private FilterPostProcessor fpp;
|
||||||
|
private BulletAppState bulletAppState;
|
||||||
|
Screen screen;
|
||||||
|
Indicator ind1;
|
||||||
|
Label spiderCounter;
|
||||||
|
Label label;
|
||||||
|
Indicator waveCounter;
|
||||||
|
Indicator cooldownLeft;
|
||||||
|
Indicator cooldownRight;
|
||||||
|
Indicator skill2;
|
||||||
|
Label gameover;
|
||||||
|
int change = 0;
|
||||||
|
private int winCount = 0;
|
||||||
|
PlayerCam playerCam;
|
||||||
|
private float spawnTimer;
|
||||||
|
private float timer;
|
||||||
|
boolean left = false;
|
||||||
|
boolean right = false;
|
||||||
|
boolean down = false;
|
||||||
|
boolean up = false;
|
||||||
|
Vector3f walkDirection = new Vector3f(Vector3f.ZERO);
|
||||||
|
private DirectionalLightShadowRenderer dlsr;
|
||||||
|
private DirectionalLightShadowFilter dlsf;
|
||||||
|
private static int SPAWN_TIME = 10;
|
||||||
|
private int spawns = 1;
|
||||||
|
private boolean gameover_flag = false;
|
||||||
|
public Exchange exchange;
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
TestCameraNode app = new TestCameraNode();
|
||||||
|
app.setDisplayFps(false);
|
||||||
|
app.setDisplayStatView(false);
|
||||||
|
app.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void simpleInitApp() {
|
||||||
|
this.bulletAppState = new BulletAppState();
|
||||||
|
this.stateManager.attach((AppState)this.bulletAppState);
|
||||||
|
this.exchange = new Exchange();
|
||||||
|
Exchange.setRootNode(this.rootNode);
|
||||||
|
Exchange.setBulletAppState(this.bulletAppState);
|
||||||
|
Exchange.setAssetManager(this.assetManager);
|
||||||
|
Exchange.setViewPort(this.viewPort);
|
||||||
|
Exchange.setApp(this);
|
||||||
|
this.spawnTimer = 0.0f;
|
||||||
|
this.timer = 0.0f;
|
||||||
|
this.setupPlayer();
|
||||||
|
this.setupGround();
|
||||||
|
this.setupWalls();
|
||||||
|
this.setupCam();
|
||||||
|
this.setupLight();
|
||||||
|
this.registerInput();
|
||||||
|
this.setupFilters();
|
||||||
|
this.setupGUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupPlayer() {
|
||||||
|
this.player = new Player("Player");
|
||||||
|
this.player.setHat(this.assetManager.loadModel("Models/helmet/helmet.j3o"));
|
||||||
|
Staff staff = new Staff("Staff", this.assetManager, this.exchange);
|
||||||
|
staff.setModel(this.assetManager.loadModel("Models/staff/staff.mesh.j3o"));
|
||||||
|
this.player.setLeftHand(staff);
|
||||||
|
Sword sword = new Sword("Sword", this.assetManager, this.exchange);
|
||||||
|
sword.setModel(this.assetManager.loadModel("Models/sword/sword.mesh.j3o"));
|
||||||
|
this.bulletAppState.getPhysicsSpace().add((Object)sword);
|
||||||
|
this.player.setRightHand(sword);
|
||||||
|
this.player.setModel(this.assetManager.loadModel("Models/blob/Blob.mesh.j3o"));
|
||||||
|
this.bulletAppState.getPhysicsSpace().add((Object)this.player.playerControl);
|
||||||
|
this.rootNode.attachChild((Spatial)this.player);
|
||||||
|
this.bulletAppState.getPhysicsSpace().addCollisionListener((PhysicsCollisionListener)this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupSpider(Vector3f spawn) {
|
||||||
|
WalkingEnemy walkingSpider = new WalkingEnemy("Spider", this.assetManager);
|
||||||
|
walkingSpider.setLocalTranslation(spawn);
|
||||||
|
walkingSpider.setModel(this.assetManager.loadModel("Models/spider/spider.mesh.j3o"));
|
||||||
|
walkingSpider.modelAnimChannel.setAnim("walk");
|
||||||
|
walkingSpider.setHealth(100.0f);
|
||||||
|
walkingSpider.modelAnimChannel.setLoopMode(LoopMode.Loop);
|
||||||
|
Exchange.enemies.add(walkingSpider);
|
||||||
|
AudioNode walksound = new AudioNode(this.assetManager, "Sounds/Effects/spiderwalking.ogg", false);
|
||||||
|
walksound.setName("walkingSound");
|
||||||
|
walksound.setLooping(true);
|
||||||
|
walksound.setVolume(2.0f);
|
||||||
|
walkingSpider.attachChild((Spatial)walksound);
|
||||||
|
walksound.play();
|
||||||
|
this.bulletAppState.getPhysicsSpace().add((Object)walkingSpider.ghost);
|
||||||
|
this.bulletAppState.getPhysicsSpace().add((Object)walkingSpider.walkingEnemyControl);
|
||||||
|
this.rootNode.attachChild((Spatial)walkingSpider);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void spawnSpider() {
|
||||||
|
Random r = new Random();
|
||||||
|
int spawnx = r.nextInt(81) - 40;
|
||||||
|
int spawnz = r.nextInt(81) - 40;
|
||||||
|
Vector3f spawnpoint = new Vector3f((float)spawnx, 0.0f, (float)spawnz);
|
||||||
|
AudioNode spawnsound = new AudioNode(this.assetManager, "Sounds/Effects/spawn.ogg", false);
|
||||||
|
spawnsound.setLooping(false);
|
||||||
|
spawnsound.setPositional(true);
|
||||||
|
spawnsound.setLocalTranslation(spawnpoint);
|
||||||
|
spawnsound.setVolume(5.0f);
|
||||||
|
this.rootNode.attachChild((Spatial)spawnsound);
|
||||||
|
spawnsound.play();
|
||||||
|
this.setupSpider(spawnpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupGround() {
|
||||||
|
int rotation_flag = 0;
|
||||||
|
for (int x = -50; x < 50; x += 10) {
|
||||||
|
for (int z = -50; z < 50; z += 10) {
|
||||||
|
Spatial ground;
|
||||||
|
if (rotation_flag == 5) {
|
||||||
|
rotation_flag = 0;
|
||||||
|
ground = this.assetManager.loadModel("Models/tile2/tile.mesh.j3o");
|
||||||
|
} else {
|
||||||
|
ground = this.assetManager.loadModel("Models/tile1/tile.mesh.j3o");
|
||||||
|
++rotation_flag;
|
||||||
|
}
|
||||||
|
ground.scale(5.0f);
|
||||||
|
ground.setLocalTranslation((float)x, -1.0f, (float)z);
|
||||||
|
RigidBodyControl ground_solid = new RigidBodyControl(0.0f);
|
||||||
|
ground.addControl((Control)ground_solid);
|
||||||
|
this.bulletAppState.getPhysicsSpace().add((Object)ground_solid);
|
||||||
|
ground.setShadowMode(RenderQueue.ShadowMode.Receive);
|
||||||
|
this.rootNode.attachChild(ground);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupWalls() {
|
||||||
|
float x;
|
||||||
|
for (x = -60.0f; x < 60.0f; x += 10.0f) {
|
||||||
|
this.createWall(x, 50.0f);
|
||||||
|
this.createWall(x, -50.0f);
|
||||||
|
}
|
||||||
|
for (x = -60.0f; x < 60.0f; x += 10.0f) {
|
||||||
|
this.createWall(50.0f, x);
|
||||||
|
this.createWall(-50.0f, x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createWall(float x, float z) {
|
||||||
|
Spatial wall = this.assetManager.loadModel("Models/wall1/wall1.mesh.j3o");
|
||||||
|
wall.scale(5.0f);
|
||||||
|
wall.setLocalTranslation(x, 0.0f, z);
|
||||||
|
RigidBodyControl wall_solid = new RigidBodyControl(0.0f);
|
||||||
|
wall.addControl((Control)wall_solid);
|
||||||
|
this.bulletAppState.getPhysicsSpace().add((Object)wall);
|
||||||
|
wall.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
|
||||||
|
this.rootNode.attachChild(wall);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupCam() {
|
||||||
|
this.playerCam = new PlayerCam(this.player, "CamNode", this.cam);
|
||||||
|
this.playerCam.setupCamera();
|
||||||
|
this.flyCam.setEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupLight() {
|
||||||
|
AmbientLight al = new AmbientLight();
|
||||||
|
al.setColor(new ColorRGBA(0.6f, 0.6f, 0.9f, 1.0f));
|
||||||
|
this.rootNode.addLight((Light)al);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerInput() {
|
||||||
|
Trigger[] triggerArray = new Trigger[2];
|
||||||
|
triggerArray[0] = new KeyTrigger(200);
|
||||||
|
triggerArray[1] = new KeyTrigger(17);
|
||||||
|
this.inputManager.addMapping("moveForward", triggerArray);
|
||||||
|
Trigger[] triggerArray2 = new Trigger[2];
|
||||||
|
triggerArray2[0] = new KeyTrigger(208);
|
||||||
|
triggerArray2[1] = new KeyTrigger(31);
|
||||||
|
this.inputManager.addMapping("moveBackward", triggerArray2);
|
||||||
|
Trigger[] triggerArray3 = new Trigger[2];
|
||||||
|
triggerArray3[0] = new KeyTrigger(205);
|
||||||
|
triggerArray3[1] = new KeyTrigger(32);
|
||||||
|
this.inputManager.addMapping("moveRight", triggerArray3);
|
||||||
|
Trigger[] triggerArray4 = new Trigger[2];
|
||||||
|
triggerArray4[0] = new KeyTrigger(203);
|
||||||
|
triggerArray4[1] = new KeyTrigger(30);
|
||||||
|
this.inputManager.addMapping("moveLeft", triggerArray4);
|
||||||
|
this.inputManager.addMapping("rightHand", new Trigger[]{new MouseButtonTrigger(1)});
|
||||||
|
Trigger[] triggerArray5 = new Trigger[1];
|
||||||
|
triggerArray5[0] = new KeyTrigger(18);
|
||||||
|
this.inputManager.addMapping("skill2", triggerArray5);
|
||||||
|
this.inputManager.addMapping("leftHand", new Trigger[]{new MouseButtonTrigger(0)});
|
||||||
|
this.inputManager.addMapping("die", new Trigger[]{new MouseButtonTrigger(2)});
|
||||||
|
this.inputManager.addMapping("jump", new Trigger[]{new KeyTrigger(57)});
|
||||||
|
this.inputManager.addListener((InputListener)this, new String[]{"moveForward", "moveBackward", "moveRight", "moveLeft", "rightHand", "leftHand", "die", "jump", "skill2"});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setupFilters() {
|
||||||
|
if (this.renderer.getCaps().contains(Caps.GLSL100)) {
|
||||||
|
this.fpp = new FilterPostProcessor(this.assetManager);
|
||||||
|
CartoonEdgeFilter toon = new CartoonEdgeFilter();
|
||||||
|
toon.setEdgeColor(ColorRGBA.Black);
|
||||||
|
this.fpp.addFilter((Filter)toon);
|
||||||
|
this.viewPort.addProcessor((SceneProcessor)this.fpp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onAnalog(String name, float value, float tpf) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onAction(String name, boolean keyPressed, float tpf) {
|
||||||
|
try {
|
||||||
|
if (name.equals("rightHand") && !keyPressed) {
|
||||||
|
this.player.rightHand.skill1();
|
||||||
|
}
|
||||||
|
if (name.equals("leftHand") && !keyPressed) {
|
||||||
|
this.player.leftHand.skill1();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (NullPointerException e) {
|
||||||
|
// empty catch block
|
||||||
|
}
|
||||||
|
if (name.equals("skill2") && !keyPressed) {
|
||||||
|
this.player.leftHand.skill2();
|
||||||
|
}
|
||||||
|
if (name.equals("die") && !keyPressed) {
|
||||||
|
this.player.switchHands();
|
||||||
|
}
|
||||||
|
if (name.equals("jump")) {
|
||||||
|
this.player.playerControl.jump();
|
||||||
|
}
|
||||||
|
if (name.equals("moveLeft")) {
|
||||||
|
this.left = keyPressed;
|
||||||
|
} else if (name.equals("moveRight")) {
|
||||||
|
this.right = keyPressed;
|
||||||
|
} else if (name.equals("moveForward")) {
|
||||||
|
this.up = keyPressed;
|
||||||
|
} else if (name.equals("moveBackward")) {
|
||||||
|
this.down = keyPressed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setupGUI() {
|
||||||
|
this.screen = new Screen((Application)this, "tonegod/gui/style/def/style_map.xml");
|
||||||
|
this.screen.initialize();
|
||||||
|
this.guiNode.addControl((Control)this.screen);
|
||||||
|
this.ind1 = new Indicator(this.screen, "Healthbar", new Vector2f(10.0f, 10.0f), Indicator.Orientation.HORIZONTAL){
|
||||||
|
|
||||||
|
public void onChange(float arg0, float arg1) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.ind1.setMaxValue(100.0f);
|
||||||
|
this.ind1.setCurrentValue(this.player.health);
|
||||||
|
this.ind1.setIndicatorColor(ColorRGBA.Red);
|
||||||
|
this.ind1.setDisplayPercentage();
|
||||||
|
this.screen.addElement((Element)this.ind1);
|
||||||
|
this.cooldownLeft = new Indicator(this.screen, "cooldownLeft", new Vector2f(10.0f, (float)(this.settings.getHeight() - 30)), Indicator.Orientation.HORIZONTAL){
|
||||||
|
|
||||||
|
public void onChange(float arg0, float arg1) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.cooldownLeft.setMaxValue(100.0f);
|
||||||
|
this.cooldownLeft.setCurrentValue(100.0f);
|
||||||
|
this.cooldownLeft.setIndicatorColor(ColorRGBA.Blue);
|
||||||
|
this.cooldownLeft.setText("Fireball");
|
||||||
|
this.screen.addElement((Element)this.cooldownLeft);
|
||||||
|
this.cooldownRight = new Indicator(this.screen, "cooldownRight", new Vector2f(10.0f, (float)(this.settings.getHeight() - 70)), Indicator.Orientation.HORIZONTAL){
|
||||||
|
|
||||||
|
public void onChange(float arg0, float arg1) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.cooldownRight.setMaxValue(100.0f);
|
||||||
|
this.cooldownRight.setCurrentValue(100.0f);
|
||||||
|
this.cooldownRight.setIndicatorColor(ColorRGBA.Blue);
|
||||||
|
this.cooldownRight.setText("Sword");
|
||||||
|
this.screen.addElement((Element)this.cooldownRight);
|
||||||
|
this.skill2 = new Indicator(this.screen, "skill2", new Vector2f(10.0f, (float)(this.settings.getHeight() - 50)), Indicator.Orientation.HORIZONTAL){
|
||||||
|
|
||||||
|
public void onChange(float arg0, float arg1) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.skill2.setMaxValue(100.0f);
|
||||||
|
this.skill2.setCurrentValue(100.0f);
|
||||||
|
this.skill2.setIndicatorColor(ColorRGBA.Blue);
|
||||||
|
this.skill2.setText("Teleport");
|
||||||
|
this.screen.addElement((Element)this.skill2);
|
||||||
|
this.label = new Label(this.screen, "Timer", new Vector2f(10.0f, 50.0f), new Vector2f(200.0f, 100.0f));
|
||||||
|
this.label.setFontColor(ColorRGBA.Yellow);
|
||||||
|
this.label.setFontSize(30.0f);
|
||||||
|
this.screen.addElement((Element)this.label);
|
||||||
|
this.waveCounter = new Indicator(this.screen, "waveCounter", new Vector2f(10.0f, 30.0f), Indicator.Orientation.HORIZONTAL){
|
||||||
|
|
||||||
|
public void onChange(float arg0, float arg1) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.waveCounter.setMaxValue((float)SPAWN_TIME);
|
||||||
|
this.waveCounter.setCurrentValue((float)SPAWN_TIME - this.spawnTimer);
|
||||||
|
this.waveCounter.setIndicatorColor(ColorRGBA.Yellow);
|
||||||
|
this.waveCounter.setFontColor(ColorRGBA.Black);
|
||||||
|
this.waveCounter.setText("next Wave in...");
|
||||||
|
this.screen.addElement((Element)this.waveCounter);
|
||||||
|
this.spiderCounter = new Label(this.screen, "SpiderCounter", new Vector2f(10.0f, 30.0f), new Vector2f(200.0f, 100.0f));
|
||||||
|
this.spiderCounter.setFontColor(ColorRGBA.Yellow);
|
||||||
|
this.spiderCounter.setFontSize(30.0f);
|
||||||
|
this.screen.addElement((Element)this.spiderCounter);
|
||||||
|
this.gameover = new Label(this.screen, "GameOver", new Vector2f(0.0f, 0.0f), new Vector2f((float)this.settings.getWidth(), 400.0f));
|
||||||
|
this.gameover.setFont("/Interface/Fonts/FleshWound.fnt");
|
||||||
|
this.gameover.setFontSize(100.0f);
|
||||||
|
this.gameover.setFontColor(ColorRGBA.White);
|
||||||
|
this.gameover.setText("Verloren!");
|
||||||
|
Effect ef = new Effect(Effect.EffectType.FadeIn, Effect.EffectEvent.Show, 2.0f);
|
||||||
|
this.gameover.hide();
|
||||||
|
this.gameover.addEffect(Effect.EffectEvent.Show, ef);
|
||||||
|
this.screen.addElement((Element)this.gameover);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reduceCooldowns(float tpf) {
|
||||||
|
this.player.leftHand.reduceCooldowns(tpf);
|
||||||
|
this.player.rightHand.reduceCooldowns(tpf);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void simpleUpdate(float tpf) {
|
||||||
|
this.reduceCooldowns(tpf);
|
||||||
|
this.cooldownLeft.setCurrentValue(this.player.leftHand.cooldown1 * (this.cooldownLeft.getMaxValue() / this.player.leftHand.COOLDOWN1_TIME));
|
||||||
|
this.skill2.setCurrentValue(this.player.leftHand.cooldown2 * (this.skill2.getMaxValue() / this.player.leftHand.COOLDOWN2_TIME));
|
||||||
|
this.cooldownRight.setCurrentValue(this.player.rightHand.cooldown1 * (this.cooldownRight.getMaxValue() / this.player.rightHand.COOLDOWN1_TIME));
|
||||||
|
this.spiderCounter.setText("Kills: " + Exchange.killed);
|
||||||
|
if (!this.gameover_flag) {
|
||||||
|
CollisionResults results = new CollisionResults();
|
||||||
|
Vector2f click2d = this.inputManager.getCursorPosition();
|
||||||
|
Vector3f click3d = this.cam.getWorldCoordinates(new Vector2f(click2d.x, click2d.y), 0.0f).clone();
|
||||||
|
Vector3f dir = this.cam.getWorldCoordinates(new Vector2f(click2d.x, click2d.y), 1.0f).subtractLocal(click3d).normalizeLocal();
|
||||||
|
Ray ray = new Ray(click3d, dir);
|
||||||
|
this.rootNode.collideWith((Collidable)ray, results);
|
||||||
|
for (int i = 0; i < results.size(); ++i) {
|
||||||
|
float dist = results.getCollision(i).getDistance();
|
||||||
|
Vector3f pt = results.getCollision(i).getContactPoint();
|
||||||
|
this.player.wholeBody.lookAt(pt, Vector3f.UNIT_Y);
|
||||||
|
this.player.pt = pt;
|
||||||
|
}
|
||||||
|
Vector3f camDir = this.cam.getDirection().clone().multLocal(8.0f);
|
||||||
|
Vector3f camLeft = this.cam.getLeft().clone().multLocal(8.0f);
|
||||||
|
camDir.y = 0.0f;
|
||||||
|
camLeft.y = 0.0f;
|
||||||
|
this.walkDirection.set(0.0f, 0.0f, 0.0f);
|
||||||
|
if (this.left) {
|
||||||
|
this.walkDirection.addLocal(camLeft);
|
||||||
|
}
|
||||||
|
if (this.right) {
|
||||||
|
this.walkDirection.addLocal(camLeft.negate());
|
||||||
|
}
|
||||||
|
if (this.up) {
|
||||||
|
this.walkDirection.addLocal(camDir);
|
||||||
|
}
|
||||||
|
if (this.down) {
|
||||||
|
this.walkDirection.addLocal(camDir.negate());
|
||||||
|
}
|
||||||
|
if (this.walkDirection.length() == 0.0f) {
|
||||||
|
if ("walk".equals(this.player.modelAnimChannel.getAnimationName())) {
|
||||||
|
this.player.modelAnimChannel.setAnim("stand", 1.0f);
|
||||||
|
}
|
||||||
|
} else if ("stand".equals(this.player.modelAnimChannel.getAnimationName())) {
|
||||||
|
this.player.modelAnimChannel.setAnim("walk", 0.7f);
|
||||||
|
}
|
||||||
|
this.walkDirection.normalizeLocal().multLocal(7.5f);
|
||||||
|
this.player.playerControl.setWalkDirection(this.walkDirection);
|
||||||
|
this.ind1.setCurrentValue(this.player.health);
|
||||||
|
if (this.player.health <= 0.0f) {
|
||||||
|
this.player.die();
|
||||||
|
this.gameover.showWithEffect();
|
||||||
|
AudioNode gameover = new AudioNode(this.assetManager, "Sounds/Effects/gameover.ogg", false);
|
||||||
|
gameover.setLooping(false);
|
||||||
|
gameover.setVolume(5.0f);
|
||||||
|
this.rootNode.attachChild((Spatial)gameover);
|
||||||
|
gameover.play();
|
||||||
|
this.gameover_flag = true;
|
||||||
|
}
|
||||||
|
this.waveCounter.setCurrentValue((float)SPAWN_TIME - this.spawnTimer);
|
||||||
|
this.spawnTimer += tpf;
|
||||||
|
if (this.spawnTimer >= (float)SPAWN_TIME) {
|
||||||
|
for (int spawn_counts = 0; spawn_counts < this.spawns; ++spawn_counts) {
|
||||||
|
this.spawnSpider();
|
||||||
|
}
|
||||||
|
++this.spawns;
|
||||||
|
this.spawnTimer = 0.0f;
|
||||||
|
}
|
||||||
|
this.label.setText("Wave No.:" + (this.spawns - 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) {
|
||||||
|
if (animName.equals("walk")) {
|
||||||
|
channel.setAnim("stand", 0.1f);
|
||||||
|
channel.setLoopMode(LoopMode.DontLoop);
|
||||||
|
channel.setSpeed(1.0f);
|
||||||
|
}
|
||||||
|
if (animName.equals("skill1")) {
|
||||||
|
channel.setAnim("normal", 0.1f);
|
||||||
|
channel.setLoopMode(LoopMode.DontLoop);
|
||||||
|
channel.setSpeed(1.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onAnimChange(AnimControl control, AnimChannel channel, String animName) {
|
||||||
|
if (animName.equals("skill1")) {
|
||||||
|
// empty if block
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void collision(PhysicsCollisionEvent event) {
|
||||||
|
try {
|
||||||
|
if (event.getNodeA().getName().equals("Spider") && event.getNodeB().getName().equals("Player") || event.getNodeB().getName().equals("Spider") && event.getNodeA().getName().equals("Player")) {
|
||||||
|
this.player.health -= 0.25f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception) {
|
||||||
|
// empty catch block
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* com.jme3.app.SimpleApplication
|
||||||
|
* com.jme3.input.ChaseCamera
|
||||||
|
* com.jme3.input.controls.ActionListener
|
||||||
|
* com.jme3.input.controls.AnalogListener
|
||||||
|
* com.jme3.input.controls.InputListener
|
||||||
|
* com.jme3.input.controls.KeyTrigger
|
||||||
|
* com.jme3.input.controls.Trigger
|
||||||
|
* com.jme3.material.Material
|
||||||
|
* com.jme3.math.Quaternion
|
||||||
|
* com.jme3.math.Vector3f
|
||||||
|
* com.jme3.scene.Geometry
|
||||||
|
* com.jme3.scene.Mesh
|
||||||
|
* com.jme3.scene.Spatial
|
||||||
|
* com.jme3.scene.shape.Quad
|
||||||
|
*/
|
||||||
|
package jme3.hello;
|
||||||
|
|
||||||
|
import com.jme3.app.SimpleApplication;
|
||||||
|
import com.jme3.input.ChaseCamera;
|
||||||
|
import com.jme3.input.controls.ActionListener;
|
||||||
|
import com.jme3.input.controls.AnalogListener;
|
||||||
|
import com.jme3.input.controls.InputListener;
|
||||||
|
import com.jme3.input.controls.KeyTrigger;
|
||||||
|
import com.jme3.input.controls.Trigger;
|
||||||
|
import com.jme3.material.Material;
|
||||||
|
import com.jme3.math.Quaternion;
|
||||||
|
import com.jme3.math.Vector3f;
|
||||||
|
import com.jme3.scene.Geometry;
|
||||||
|
import com.jme3.scene.Mesh;
|
||||||
|
import com.jme3.scene.Spatial;
|
||||||
|
import com.jme3.scene.shape.Quad;
|
||||||
|
|
||||||
|
public class TestChaseCamera
|
||||||
|
extends SimpleApplication
|
||||||
|
implements AnalogListener,
|
||||||
|
ActionListener {
|
||||||
|
private Spatial teaGeom;
|
||||||
|
private ChaseCamera chaseCam;
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
TestChaseCamera app = new TestChaseCamera();
|
||||||
|
app.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void simpleInitApp() {
|
||||||
|
this.teaGeom = this.assetManager.loadModel("Models/blob/Blob.mesh.xml");
|
||||||
|
Material mat_tea = new Material(this.assetManager, "Common/MatDefs/Misc/ShowNormals.j3md");
|
||||||
|
this.teaGeom.setMaterial(mat_tea);
|
||||||
|
this.rootNode.attachChild(this.teaGeom);
|
||||||
|
Material mat_ground = new Material(this.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||||
|
Geometry ground = new Geometry("ground", (Mesh)new Quad(50.0f, 50.0f));
|
||||||
|
ground.setLocalRotation(new Quaternion().fromAngleAxis(-1.5707964f, Vector3f.UNIT_X));
|
||||||
|
ground.setLocalTranslation(-25.0f, -1.0f, 25.0f);
|
||||||
|
ground.setMaterial(mat_ground);
|
||||||
|
this.rootNode.attachChild((Spatial)ground);
|
||||||
|
this.flyCam.setEnabled(false);
|
||||||
|
this.chaseCam = new ChaseCamera(this.cam, this.teaGeom, this.inputManager);
|
||||||
|
this.chaseCam.setRotationSpeed(0.0f);
|
||||||
|
this.chaseCam.setSmoothMotion(true);
|
||||||
|
this.chaseCam.setTrailingEnabled(true);
|
||||||
|
this.chaseCam.setLookAtOffset(Vector3f.UNIT_Y.mult(3.0f));
|
||||||
|
this.registerInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerInput() {
|
||||||
|
Trigger[] triggerArray = new Trigger[2];
|
||||||
|
triggerArray[0] = new KeyTrigger(200);
|
||||||
|
triggerArray[1] = new KeyTrigger(17);
|
||||||
|
this.inputManager.addMapping("moveForward", triggerArray);
|
||||||
|
Trigger[] triggerArray2 = new Trigger[2];
|
||||||
|
triggerArray2[0] = new KeyTrigger(208);
|
||||||
|
triggerArray2[1] = new KeyTrigger(31);
|
||||||
|
this.inputManager.addMapping("moveBackward", triggerArray2);
|
||||||
|
Trigger[] triggerArray3 = new Trigger[2];
|
||||||
|
triggerArray3[0] = new KeyTrigger(205);
|
||||||
|
triggerArray3[1] = new KeyTrigger(32);
|
||||||
|
this.inputManager.addMapping("moveRight", triggerArray3);
|
||||||
|
Trigger[] triggerArray4 = new Trigger[2];
|
||||||
|
triggerArray4[0] = new KeyTrigger(203);
|
||||||
|
triggerArray4[1] = new KeyTrigger(30);
|
||||||
|
this.inputManager.addMapping("moveLeft", triggerArray4);
|
||||||
|
Trigger[] triggerArray5 = new Trigger[1];
|
||||||
|
triggerArray5[0] = new KeyTrigger(25);
|
||||||
|
this.inputManager.addMapping("displayPosition", triggerArray5);
|
||||||
|
this.inputManager.addListener((InputListener)this, new String[]{"moveForward", "moveBackward", "moveRight", "moveLeft"});
|
||||||
|
this.inputManager.addListener((InputListener)this, new String[]{"displayPosition"});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onAnalog(String name, float value, float tpf) {
|
||||||
|
if (name.equals("moveForward")) {
|
||||||
|
this.teaGeom.move(0.0f, 0.0f, -5.0f * tpf);
|
||||||
|
}
|
||||||
|
if (name.equals("moveBackward")) {
|
||||||
|
this.teaGeom.move(0.0f, 0.0f, 5.0f * tpf);
|
||||||
|
}
|
||||||
|
if (name.equals("moveRight")) {
|
||||||
|
this.teaGeom.move(5.0f * tpf, 0.0f, 0.0f);
|
||||||
|
}
|
||||||
|
if (name.equals("moveLeft")) {
|
||||||
|
this.teaGeom.move(-5.0f * tpf, 0.0f, 0.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onAction(String name, boolean keyPressed, float tpf) {
|
||||||
|
if (name.equals("displayPosition") && keyPressed) {
|
||||||
|
this.teaGeom.move(10.0f, 10.0f, 10.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void simpleUpdate(float tpf) {
|
||||||
|
super.simpleUpdate(tpf);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,206 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* com.jme3.animation.AnimChannel
|
||||||
|
* com.jme3.animation.AnimControl
|
||||||
|
* com.jme3.animation.AnimEventListener
|
||||||
|
* com.jme3.app.SimpleApplication
|
||||||
|
* com.jme3.app.state.AppState
|
||||||
|
* com.jme3.bullet.BulletAppState
|
||||||
|
* com.jme3.bullet.BulletAppState$ThreadingType
|
||||||
|
* com.jme3.bullet.collision.PhysicsCollisionEvent
|
||||||
|
* com.jme3.bullet.collision.PhysicsCollisionListener
|
||||||
|
* com.jme3.bullet.collision.shapes.CapsuleCollisionShape
|
||||||
|
* com.jme3.bullet.collision.shapes.CollisionShape
|
||||||
|
* com.jme3.bullet.control.CharacterControl
|
||||||
|
* com.jme3.bullet.control.RigidBodyControl
|
||||||
|
* com.jme3.input.ChaseCamera
|
||||||
|
* com.jme3.input.controls.ActionListener
|
||||||
|
* com.jme3.input.controls.InputListener
|
||||||
|
* com.jme3.input.controls.KeyTrigger
|
||||||
|
* com.jme3.input.controls.Trigger
|
||||||
|
* com.jme3.light.AmbientLight
|
||||||
|
* com.jme3.light.DirectionalLight
|
||||||
|
* com.jme3.light.Light
|
||||||
|
* com.jme3.material.Material
|
||||||
|
* com.jme3.math.ColorRGBA
|
||||||
|
* com.jme3.math.Quaternion
|
||||||
|
* com.jme3.math.Vector3f
|
||||||
|
* com.jme3.scene.Geometry
|
||||||
|
* com.jme3.scene.Mesh
|
||||||
|
* com.jme3.scene.Node
|
||||||
|
* com.jme3.scene.Spatial
|
||||||
|
* com.jme3.scene.control.Control
|
||||||
|
* com.jme3.scene.shape.Quad
|
||||||
|
*/
|
||||||
|
package jme3.hello;
|
||||||
|
|
||||||
|
import com.jme3.animation.AnimChannel;
|
||||||
|
import com.jme3.animation.AnimControl;
|
||||||
|
import com.jme3.animation.AnimEventListener;
|
||||||
|
import com.jme3.app.SimpleApplication;
|
||||||
|
import com.jme3.app.state.AppState;
|
||||||
|
import com.jme3.bullet.BulletAppState;
|
||||||
|
import com.jme3.bullet.collision.PhysicsCollisionEvent;
|
||||||
|
import com.jme3.bullet.collision.PhysicsCollisionListener;
|
||||||
|
import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
|
||||||
|
import com.jme3.bullet.collision.shapes.CollisionShape;
|
||||||
|
import com.jme3.bullet.control.CharacterControl;
|
||||||
|
import com.jme3.bullet.control.RigidBodyControl;
|
||||||
|
import com.jme3.input.ChaseCamera;
|
||||||
|
import com.jme3.input.controls.ActionListener;
|
||||||
|
import com.jme3.input.controls.InputListener;
|
||||||
|
import com.jme3.input.controls.KeyTrigger;
|
||||||
|
import com.jme3.input.controls.Trigger;
|
||||||
|
import com.jme3.light.AmbientLight;
|
||||||
|
import com.jme3.light.DirectionalLight;
|
||||||
|
import com.jme3.light.Light;
|
||||||
|
import com.jme3.material.Material;
|
||||||
|
import com.jme3.math.ColorRGBA;
|
||||||
|
import com.jme3.math.Quaternion;
|
||||||
|
import com.jme3.math.Vector3f;
|
||||||
|
import com.jme3.scene.Geometry;
|
||||||
|
import com.jme3.scene.Mesh;
|
||||||
|
import com.jme3.scene.Node;
|
||||||
|
import com.jme3.scene.Spatial;
|
||||||
|
import com.jme3.scene.control.Control;
|
||||||
|
import com.jme3.scene.shape.Quad;
|
||||||
|
|
||||||
|
public class test
|
||||||
|
extends SimpleApplication
|
||||||
|
implements ActionListener,
|
||||||
|
PhysicsCollisionListener,
|
||||||
|
AnimEventListener {
|
||||||
|
private BulletAppState bulletAppState;
|
||||||
|
CharacterControl character;
|
||||||
|
Node model;
|
||||||
|
Vector3f walkDirection = new Vector3f();
|
||||||
|
Geometry ground;
|
||||||
|
ChaseCamera chaseCam;
|
||||||
|
boolean left = false;
|
||||||
|
boolean right = false;
|
||||||
|
boolean up = false;
|
||||||
|
boolean down = false;
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
test app = new test();
|
||||||
|
app.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void simpleInitApp() {
|
||||||
|
this.bulletAppState = new BulletAppState();
|
||||||
|
this.bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL);
|
||||||
|
this.stateManager.attach((AppState)this.bulletAppState);
|
||||||
|
this.setupKeys();
|
||||||
|
this.createLight();
|
||||||
|
this.createCharacter();
|
||||||
|
Material mat = new Material(this.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||||
|
mat.setTexture("ColorMap", this.assetManager.loadTexture("Textures/Rock.PNG"));
|
||||||
|
this.ground = new Geometry("ground", (Mesh)new Quad(50.0f, 50.0f));
|
||||||
|
this.ground.setLocalRotation(new Quaternion().fromAngleAxis(-1.5707964f, Vector3f.UNIT_X));
|
||||||
|
this.ground.setLocalTranslation(0.0f, 0.0f, 0.0f);
|
||||||
|
this.ground.setMaterial(mat);
|
||||||
|
RigidBodyControl ground_solid = new RigidBodyControl(0.0f);
|
||||||
|
this.ground.addControl((Control)ground_solid);
|
||||||
|
this.bulletAppState.getPhysicsSpace().add((Object)ground_solid);
|
||||||
|
this.rootNode.attachChild((Spatial)this.ground);
|
||||||
|
this.setupChaseCamera();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupKeys() {
|
||||||
|
this.inputManager.addMapping("CharLeft", new Trigger[]{new KeyTrigger(30)});
|
||||||
|
this.inputManager.addMapping("CharRight", new Trigger[]{new KeyTrigger(32)});
|
||||||
|
this.inputManager.addMapping("CharUp", new Trigger[]{new KeyTrigger(17)});
|
||||||
|
this.inputManager.addMapping("CharDown", new Trigger[]{new KeyTrigger(31)});
|
||||||
|
this.inputManager.addListener((InputListener)this, new String[]{"CharLeft", "CharRight", "CharUp", "CharDown"});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createLight() {
|
||||||
|
AmbientLight al = new AmbientLight();
|
||||||
|
al.setColor(ColorRGBA.White.mult(1.3f));
|
||||||
|
this.rootNode.addLight((Light)al);
|
||||||
|
DirectionalLight dl = new DirectionalLight();
|
||||||
|
dl.setColor(ColorRGBA.White);
|
||||||
|
dl.setDirection(new Vector3f(2.8f, -2.8f, -2.8f).normalizeLocal());
|
||||||
|
this.rootNode.addLight((Light)dl);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createGround() {
|
||||||
|
Material mat = new Material(this.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||||
|
mat.setTexture("ColorMap", this.assetManager.loadTexture("Textures/Rock.PNG"));
|
||||||
|
this.ground = new Geometry("ground", (Mesh)new Quad(50.0f, 50.0f));
|
||||||
|
this.ground.setLocalRotation(new Quaternion().fromAngleAxis(-1.5707964f, Vector3f.UNIT_X));
|
||||||
|
this.ground.setLocalTranslation(0.0f, 0.0f, 0.0f);
|
||||||
|
this.ground.setMaterial(mat);
|
||||||
|
RigidBodyControl ground_solid = new RigidBodyControl(0.0f);
|
||||||
|
this.ground.addControl((Control)ground_solid);
|
||||||
|
this.bulletAppState.getPhysicsSpace().add((Object)ground_solid);
|
||||||
|
this.rootNode.attachChild((Spatial)this.ground);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createCharacter() {
|
||||||
|
CapsuleCollisionShape capsule = new CapsuleCollisionShape(1.0f, 1.0f);
|
||||||
|
this.character = new CharacterControl((CollisionShape)capsule, 1.0f);
|
||||||
|
this.model = (Node)this.assetManager.loadModel("Models/spider/Spider.mesh.j3o");
|
||||||
|
this.model.addControl((Control)this.character);
|
||||||
|
this.model.setLocalTranslation(0.0f, 10.0f, 0.0f);
|
||||||
|
this.character.setPhysicsLocation(new Vector3f(-140.0f, 15.0f, -10.0f));
|
||||||
|
this.rootNode.attachChild((Spatial)this.model);
|
||||||
|
this.bulletAppState.getPhysicsSpace().add((Object)this.character);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupChaseCamera() {
|
||||||
|
this.flyCam.setEnabled(false);
|
||||||
|
this.chaseCam = new ChaseCamera(this.cam, (Spatial)this.model, this.inputManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void simpleUpdate(float tpf) {
|
||||||
|
this.walkDirection.set(0.0f, 0.0f, 0.0f);
|
||||||
|
Vector3f camDir = this.cam.getDirection().clone().multLocal(0.1f);
|
||||||
|
Vector3f camLeft = this.cam.getLeft().clone().multLocal(0.1f);
|
||||||
|
camDir.y = 0.0f;
|
||||||
|
camLeft.y = 0.0f;
|
||||||
|
this.walkDirection.set(0.0f, 0.0f, 0.0f);
|
||||||
|
if (this.left) {
|
||||||
|
this.walkDirection.addLocal(camLeft);
|
||||||
|
}
|
||||||
|
if (this.right) {
|
||||||
|
this.walkDirection.addLocal(camLeft.negate());
|
||||||
|
}
|
||||||
|
if (this.up) {
|
||||||
|
this.walkDirection.addLocal(camDir);
|
||||||
|
}
|
||||||
|
if (this.down) {
|
||||||
|
this.walkDirection.addLocal(camDir.negate());
|
||||||
|
}
|
||||||
|
if (this.walkDirection.length() != 0.0f) {
|
||||||
|
this.character.setViewDirection(this.walkDirection);
|
||||||
|
}
|
||||||
|
this.character.setWalkDirection(this.walkDirection);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onAction(String binding, boolean value, float tpf) {
|
||||||
|
if (binding.equals("CharLeft")) {
|
||||||
|
this.left = value;
|
||||||
|
} else if (binding.equals("CharRight")) {
|
||||||
|
this.right = value;
|
||||||
|
} else if (binding.equals("CharUp")) {
|
||||||
|
this.up = value;
|
||||||
|
} else if (binding.equals("CharDown")) {
|
||||||
|
this.down = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void collision(PhysicsCollisionEvent event) {
|
||||||
|
throw new UnsupportedOperationException("Not supported yet.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) {
|
||||||
|
throw new UnsupportedOperationException("Not supported yet.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onAnimChange(AnimControl control, AnimChannel channel, String animName) {
|
||||||
|
throw new UnsupportedOperationException("Not supported yet.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*/
|
||||||
|
package mygame;
|
||||||
|
|
||||||
|
interface AnimeEventListener {
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* com.jme3.animation.AnimChannel
|
||||||
|
* com.jme3.animation.AnimControl
|
||||||
|
* com.jme3.scene.Spatial
|
||||||
|
*/
|
||||||
|
package mygame;
|
||||||
|
|
||||||
|
import com.jme3.animation.AnimChannel;
|
||||||
|
import com.jme3.animation.AnimControl;
|
||||||
|
import com.jme3.scene.Spatial;
|
||||||
|
import mygame.Interactive;
|
||||||
|
|
||||||
|
public abstract class Character
|
||||||
|
extends Interactive {
|
||||||
|
public AnimChannel modelAnimChannel;
|
||||||
|
public AnimControl modelAnimControl;
|
||||||
|
|
||||||
|
public Character(String name) {
|
||||||
|
super(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setModel(Spatial model) {
|
||||||
|
super.setModel(model);
|
||||||
|
this.setupModelControl();
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void setupModelControl();
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* com.jme3.animation.AnimControl
|
||||||
|
* com.jme3.bullet.collision.shapes.CapsuleCollisionShape
|
||||||
|
* com.jme3.bullet.collision.shapes.CollisionShape
|
||||||
|
* com.jme3.bullet.control.GhostControl
|
||||||
|
* com.jme3.effect.ParticleEmitter
|
||||||
|
* com.jme3.effect.ParticleMesh$Type
|
||||||
|
* com.jme3.light.Light
|
||||||
|
* com.jme3.light.PointLight
|
||||||
|
* com.jme3.material.Material
|
||||||
|
* com.jme3.math.ColorRGBA
|
||||||
|
* com.jme3.math.Vector3f
|
||||||
|
* com.jme3.scene.Spatial
|
||||||
|
* com.jme3.scene.control.Control
|
||||||
|
*/
|
||||||
|
package mygame;
|
||||||
|
|
||||||
|
import com.jme3.animation.AnimControl;
|
||||||
|
import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
|
||||||
|
import com.jme3.bullet.collision.shapes.CollisionShape;
|
||||||
|
import com.jme3.bullet.control.GhostControl;
|
||||||
|
import com.jme3.effect.ParticleEmitter;
|
||||||
|
import com.jme3.effect.ParticleMesh;
|
||||||
|
import com.jme3.light.Light;
|
||||||
|
import com.jme3.light.PointLight;
|
||||||
|
import com.jme3.material.Material;
|
||||||
|
import com.jme3.math.ColorRGBA;
|
||||||
|
import com.jme3.math.Vector3f;
|
||||||
|
import com.jme3.scene.Spatial;
|
||||||
|
import com.jme3.scene.control.Control;
|
||||||
|
import mygame.Character;
|
||||||
|
import mygame.CrossControl;
|
||||||
|
import mygame.Exchange;
|
||||||
|
|
||||||
|
public class Cross
|
||||||
|
extends Character {
|
||||||
|
public GhostControl collision;
|
||||||
|
public CrossControl control;
|
||||||
|
private PointLight light;
|
||||||
|
|
||||||
|
public Cross(String name) {
|
||||||
|
super(name);
|
||||||
|
this.setModel();
|
||||||
|
this.setupControl();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setupModelControl() {
|
||||||
|
this.modelAnimControl = (AnimControl)this.model.getControl(AnimControl.class);
|
||||||
|
this.modelAnimChannel = this.modelAnimControl.createChannel();
|
||||||
|
this.modelAnimChannel.setAnim("normal");
|
||||||
|
this.modelAnimChannel.setSpeed(0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setupModel() {
|
||||||
|
this.wholeBody.attachChild(this.model);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setupControl() {
|
||||||
|
this.collision = new GhostControl((CollisionShape)new CapsuleCollisionShape(1.0f, 0.5f));
|
||||||
|
this.collision.addCollideWithGroup(2);
|
||||||
|
this.addControl((Control)this.collision);
|
||||||
|
this.control = new CrossControl();
|
||||||
|
this.addControl((Control)this.control);
|
||||||
|
Exchange.bulletAppState.getPhysicsSpace().add((Object)this.collision);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setModel() {
|
||||||
|
this.model = Exchange.assetManager.loadModel("Models/kross/kross.mesh.j3o");
|
||||||
|
this.setLocalScale(0.25f);
|
||||||
|
this.setupModel();
|
||||||
|
this.setupModelControl();
|
||||||
|
this.wholeBody.attachChild(this.model);
|
||||||
|
ParticleEmitter funken = new ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, 50);
|
||||||
|
Material mat_red = new Material(Exchange.assetManager, "Common/MatDefs/Misc/Particle.j3md");
|
||||||
|
mat_red.setTexture("Texture", Exchange.assetManager.loadTexture("Effects/flame.png"));
|
||||||
|
funken.setMaterial(mat_red);
|
||||||
|
funken.setImagesX(2);
|
||||||
|
funken.setImagesY(2);
|
||||||
|
funken.setEndColor(new ColorRGBA(0.0f, 1.0f, 0.0f, 1.0f));
|
||||||
|
funken.setStartColor(new ColorRGBA(0.0f, 1.0f, 0.0f, 1.0f));
|
||||||
|
funken.getParticleInfluencer().setInitialVelocity(new Vector3f(0.25f, 2.0f, 0.25f));
|
||||||
|
funken.setStartSize(0.1f);
|
||||||
|
funken.setEndSize(0.1f);
|
||||||
|
funken.setGravity(0.0f, 0.0f, 0.0f);
|
||||||
|
funken.setLowLife(1.0f);
|
||||||
|
funken.setHighLife(2.0f);
|
||||||
|
funken.getParticleInfluencer().setVelocityVariation(1.0f);
|
||||||
|
this.attachChild((Spatial)funken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void kill() {
|
||||||
|
this.removeFromParent();
|
||||||
|
Exchange.rootNode.removeLight((Light)this.light);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLight() {
|
||||||
|
this.light = new PointLight();
|
||||||
|
this.light.setPosition(this.getLocalTranslation().add(new Vector3f(0.0f, 1.0f, 0.0f)));
|
||||||
|
this.light.setColor(ColorRGBA.Green);
|
||||||
|
this.light.setRadius(10.0f);
|
||||||
|
Exchange.rootNode.addLight((Light)this.light);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* com.jme3.renderer.RenderManager
|
||||||
|
* com.jme3.renderer.ViewPort
|
||||||
|
* com.jme3.scene.control.AbstractControl
|
||||||
|
*/
|
||||||
|
package mygame;
|
||||||
|
|
||||||
|
import com.jme3.renderer.RenderManager;
|
||||||
|
import com.jme3.renderer.ViewPort;
|
||||||
|
import com.jme3.scene.control.AbstractControl;
|
||||||
|
import mygame.Cross;
|
||||||
|
import mygame.Exchange;
|
||||||
|
import mygame.Player;
|
||||||
|
|
||||||
|
public class CrossControl
|
||||||
|
extends AbstractControl {
|
||||||
|
protected void controlUpdate(float tpf) {
|
||||||
|
Cross cross = (Cross)this.spatial;
|
||||||
|
Player player = (Player)Exchange.rootNode.getChild("Player");
|
||||||
|
if (cross.collision.getOverlappingObjects().contains(player.collision)) {
|
||||||
|
player.heal(25.0f);
|
||||||
|
cross.kill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void controlRender(RenderManager rm, ViewPort vp) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* com.jme3.app.SimpleApplication
|
||||||
|
* com.jme3.asset.AssetManager
|
||||||
|
* com.jme3.bullet.BulletAppState
|
||||||
|
* com.jme3.math.Vector3f
|
||||||
|
* com.jme3.renderer.ViewPort
|
||||||
|
* com.jme3.scene.Node
|
||||||
|
* com.jme3.scene.Spatial
|
||||||
|
*/
|
||||||
|
package mygame;
|
||||||
|
|
||||||
|
import com.jme3.app.SimpleApplication;
|
||||||
|
import com.jme3.asset.AssetManager;
|
||||||
|
import com.jme3.bullet.BulletAppState;
|
||||||
|
import com.jme3.math.Vector3f;
|
||||||
|
import com.jme3.renderer.ViewPort;
|
||||||
|
import com.jme3.scene.Node;
|
||||||
|
import com.jme3.scene.Spatial;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Random;
|
||||||
|
import mygame.Cross;
|
||||||
|
import mygame.WalkingEnemy;
|
||||||
|
|
||||||
|
public class Exchange {
|
||||||
|
public static ArrayList<WalkingEnemy> enemies;
|
||||||
|
public static Node rootNode;
|
||||||
|
public static BulletAppState bulletAppState;
|
||||||
|
public static AssetManager assetManager;
|
||||||
|
public static ViewPort viewPort;
|
||||||
|
public static SimpleApplication app;
|
||||||
|
public static int killed;
|
||||||
|
|
||||||
|
public static int getKilled() {
|
||||||
|
return killed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setKilled(int killed) {
|
||||||
|
Exchange.killed = killed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SimpleApplication getApp() {
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setApp(SimpleApplication app) {
|
||||||
|
Exchange.app = app;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AssetManager getAssetManager() {
|
||||||
|
return assetManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setAssetManager(AssetManager assetManager) {
|
||||||
|
Exchange.assetManager = assetManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BulletAppState getBulletAppState() {
|
||||||
|
return bulletAppState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setBulletAppState(BulletAppState bulletAppState) {
|
||||||
|
Exchange.bulletAppState = bulletAppState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Node getRootNode() {
|
||||||
|
return rootNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setRootNode(Node rootNode) {
|
||||||
|
Exchange.rootNode = rootNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Exchange() {
|
||||||
|
enemies = new ArrayList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void removeEnemy(WalkingEnemy en) {
|
||||||
|
enemies.remove((Object)en);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ViewPort getViewPort() {
|
||||||
|
return viewPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setViewPort(ViewPort viewPort) {
|
||||||
|
Exchange.viewPort = viewPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void spawnItem(Vector3f place) {
|
||||||
|
Random r = new Random();
|
||||||
|
if (r.nextInt(100) > 90) {
|
||||||
|
Cross cross = new Cross("Cross");
|
||||||
|
cross.setLocalTranslation(place);
|
||||||
|
cross.setLight();
|
||||||
|
rootNode.attachChild((Spatial)cross);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* com.jme3.asset.AssetManager
|
||||||
|
* com.jme3.audio.AudioNode
|
||||||
|
* com.jme3.bullet.collision.shapes.CapsuleCollisionShape
|
||||||
|
* com.jme3.bullet.collision.shapes.CollisionShape
|
||||||
|
* com.jme3.bullet.control.BetterCharacterControl
|
||||||
|
* com.jme3.bullet.control.GhostControl
|
||||||
|
* com.jme3.effect.ParticleEmitter
|
||||||
|
* com.jme3.effect.ParticleMesh$Type
|
||||||
|
* com.jme3.light.Light
|
||||||
|
* com.jme3.light.PointLight
|
||||||
|
* com.jme3.material.Material
|
||||||
|
* com.jme3.math.ColorRGBA
|
||||||
|
* com.jme3.math.Vector3f
|
||||||
|
* com.jme3.post.SceneProcessor
|
||||||
|
* com.jme3.scene.Node
|
||||||
|
* com.jme3.scene.Spatial
|
||||||
|
* com.jme3.scene.control.Control
|
||||||
|
* com.jme3.scene.control.LightControl
|
||||||
|
* com.jme3.shadow.EdgeFilteringMode
|
||||||
|
* com.jme3.shadow.PointLightShadowFilter
|
||||||
|
* com.jme3.shadow.PointLightShadowRenderer
|
||||||
|
*/
|
||||||
|
package mygame;
|
||||||
|
|
||||||
|
import com.jme3.asset.AssetManager;
|
||||||
|
import com.jme3.audio.AudioNode;
|
||||||
|
import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
|
||||||
|
import com.jme3.bullet.collision.shapes.CollisionShape;
|
||||||
|
import com.jme3.bullet.control.BetterCharacterControl;
|
||||||
|
import com.jme3.bullet.control.GhostControl;
|
||||||
|
import com.jme3.effect.ParticleEmitter;
|
||||||
|
import com.jme3.effect.ParticleMesh;
|
||||||
|
import com.jme3.light.Light;
|
||||||
|
import com.jme3.light.PointLight;
|
||||||
|
import com.jme3.material.Material;
|
||||||
|
import com.jme3.math.ColorRGBA;
|
||||||
|
import com.jme3.math.Vector3f;
|
||||||
|
import com.jme3.post.SceneProcessor;
|
||||||
|
import com.jme3.scene.Node;
|
||||||
|
import com.jme3.scene.Spatial;
|
||||||
|
import com.jme3.scene.control.Control;
|
||||||
|
import com.jme3.scene.control.LightControl;
|
||||||
|
import com.jme3.shadow.EdgeFilteringMode;
|
||||||
|
import com.jme3.shadow.PointLightShadowFilter;
|
||||||
|
import com.jme3.shadow.PointLightShadowRenderer;
|
||||||
|
import mygame.Exchange;
|
||||||
|
import mygame.FireballControl;
|
||||||
|
|
||||||
|
public class Fireball
|
||||||
|
extends Node {
|
||||||
|
ParticleEmitter fire;
|
||||||
|
PointLight light;
|
||||||
|
LightControl lightControl;
|
||||||
|
GhostControl collisionGhost;
|
||||||
|
GhostControl damageAreaGhost;
|
||||||
|
BetterCharacterControl bodyControl;
|
||||||
|
PointLightShadowRenderer dlsr;
|
||||||
|
PointLightShadowFilter dlsf;
|
||||||
|
FireballControl fireballControl;
|
||||||
|
private AssetManager assetManager;
|
||||||
|
Vector3f direction;
|
||||||
|
Exchange exchange;
|
||||||
|
boolean explode;
|
||||||
|
float timer;
|
||||||
|
float lifetime = 1.0f;
|
||||||
|
|
||||||
|
public Fireball(AssetManager assetManager, Vector3f finish, Vector3f start, Exchange ex) {
|
||||||
|
this.direction = finish;
|
||||||
|
this.exchange = ex;
|
||||||
|
assetManager = Exchange.assetManager;
|
||||||
|
this.setLocalTranslation(start.add(new Vector3f(1.0f, 1.0f, 1.0f)));
|
||||||
|
this.fire = new ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, 30);
|
||||||
|
Material mat_red = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
|
||||||
|
mat_red.setTexture("Texture", assetManager.loadTexture("Effects/flame.png"));
|
||||||
|
this.fire.setMaterial(mat_red);
|
||||||
|
this.fire.setImagesX(2);
|
||||||
|
this.fire.setImagesY(2);
|
||||||
|
this.fire.setEndColor(new ColorRGBA(1.0f, 0.0f, 0.0f, 1.0f));
|
||||||
|
this.fire.setStartColor(new ColorRGBA(1.0f, 1.0f, 0.0f, 0.5f));
|
||||||
|
this.fire.getParticleInfluencer().setInitialVelocity(new Vector3f(0.0f, 2.0f, 0.0f));
|
||||||
|
this.fire.setStartSize(1.5f);
|
||||||
|
this.fire.setEndSize(0.1f);
|
||||||
|
this.fire.setGravity(0.0f, 0.0f, 0.0f);
|
||||||
|
this.fire.setLowLife(1.0f);
|
||||||
|
this.fire.setHighLife(3.0f);
|
||||||
|
this.fire.getParticleInfluencer().setVelocityVariation(0.3f);
|
||||||
|
this.attachChild((Spatial)this.fire);
|
||||||
|
this.light = new PointLight();
|
||||||
|
this.light.setColor(new ColorRGBA(1.0f, 0.5f, 0.0f, 1.0f));
|
||||||
|
this.light.setPosition(this.getLocalTranslation());
|
||||||
|
this.light.setRadius(100.0f);
|
||||||
|
Exchange.rootNode.addLight((Light)this.light);
|
||||||
|
this.dlsr = new PointLightShadowRenderer(assetManager, 128);
|
||||||
|
this.dlsr.setLight(this.light);
|
||||||
|
this.dlsr.setShadowIntensity(0.2f);
|
||||||
|
this.dlsr.setEdgeFilteringMode(EdgeFilteringMode.Dither);
|
||||||
|
Exchange.viewPort.addProcessor((SceneProcessor)this.dlsr);
|
||||||
|
this.dlsf = new PointLightShadowFilter(assetManager, 128);
|
||||||
|
this.dlsf.setLight(this.light);
|
||||||
|
this.dlsf.setShadowIntensity(0.2f);
|
||||||
|
this.dlsf.setEdgeFilteringMode(EdgeFilteringMode.Dither);
|
||||||
|
this.dlsf.setEnabled(false);
|
||||||
|
this.collisionGhost = new GhostControl((CollisionShape)new CapsuleCollisionShape(1.0f, 0.5f));
|
||||||
|
this.collisionGhost.setSpatial((Spatial)this);
|
||||||
|
this.collisionGhost.addCollideWithGroup(1);
|
||||||
|
this.addControl((Control)this.collisionGhost);
|
||||||
|
this.damageAreaGhost = new GhostControl((CollisionShape)new CapsuleCollisionShape(5.0f, 1.0f));
|
||||||
|
this.damageAreaGhost.addCollideWithGroup(1);
|
||||||
|
this.addControl((Control)this.damageAreaGhost);
|
||||||
|
this.bodyControl = new BetterCharacterControl(0.1f, 0.1f, 1.0f);
|
||||||
|
this.addControl((Control)this.bodyControl);
|
||||||
|
this.fireballControl = new FireballControl();
|
||||||
|
this.addControl((Control)this.fireballControl);
|
||||||
|
Exchange.rootNode.attachChild((Spatial)this);
|
||||||
|
Exchange.bulletAppState.getPhysicsSpace().add((Object)this.damageAreaGhost);
|
||||||
|
Exchange.bulletAppState.getPhysicsSpace().add((Object)this.collisionGhost);
|
||||||
|
Exchange.bulletAppState.getPhysicsSpace().add((Object)this.bodyControl);
|
||||||
|
AudioNode hit = new AudioNode(assetManager, "Sounds/Effects/fireball.ogg", false);
|
||||||
|
hit.setLooping(false);
|
||||||
|
hit.setPositional(true);
|
||||||
|
hit.setLocalTranslation(this.getLocalTranslation());
|
||||||
|
hit.setVolume(5.0f);
|
||||||
|
this.attachChild((Spatial)hit);
|
||||||
|
hit.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void explode() {
|
||||||
|
ParticleEmitter xplosion = new ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, 50);
|
||||||
|
Material mat_red = new Material(Exchange.assetManager, "Common/MatDefs/Misc/Particle.j3md");
|
||||||
|
mat_red.setTexture("Texture", Exchange.assetManager.loadTexture("Effects/flame.png"));
|
||||||
|
xplosion.setMaterial(mat_red);
|
||||||
|
xplosion.setImagesX(2);
|
||||||
|
xplosion.setParticlesPerSec(0.0f);
|
||||||
|
xplosion.setImagesY(2);
|
||||||
|
xplosion.setEndColor(new ColorRGBA(1.0f, 0.0f, 0.0f, 1.0f));
|
||||||
|
xplosion.setStartColor(new ColorRGBA(1.0f, 1.0f, 0.0f, 0.5f));
|
||||||
|
xplosion.getParticleInfluencer().setInitialVelocity(new Vector3f(0.5f, 0.5f, 0.5f));
|
||||||
|
xplosion.setStartSize(1.5f);
|
||||||
|
xplosion.setEndSize(2.0f);
|
||||||
|
xplosion.setGravity(0.0f, 1.0f, 0.0f);
|
||||||
|
xplosion.setLowLife(1.0f);
|
||||||
|
xplosion.setHighLife(2.0f);
|
||||||
|
xplosion.getParticleInfluencer().setVelocityVariation(2.0f);
|
||||||
|
this.attachChild((Spatial)xplosion);
|
||||||
|
xplosion.emitAllParticles();
|
||||||
|
ParticleEmitter stones = new ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, 20);
|
||||||
|
Material mat_stones = new Material(Exchange.assetManager, "Common/MatDefs/Misc/Particle.j3md");
|
||||||
|
mat_stones.setTexture("Texture", Exchange.assetManager.loadTexture("Effects/stein.png"));
|
||||||
|
mat_stones.setTexture("GlowMap", Exchange.assetManager.loadTexture("Effects/stein.png"));
|
||||||
|
mat_stones.setTexture("DepthTexture", Exchange.assetManager.loadTexture("Effects/stein.png"));
|
||||||
|
stones.setMaterial(mat_stones);
|
||||||
|
stones.setImagesX(2);
|
||||||
|
stones.setParticlesPerSec(0.0f);
|
||||||
|
stones.setImagesY(2);
|
||||||
|
stones.setEndColor(new ColorRGBA(0.1f, 0.1f, 0.1f, 1.0f));
|
||||||
|
stones.setStartColor(new ColorRGBA(0.0f, 0.0f, 0.0f, 1.0f));
|
||||||
|
stones.getParticleInfluencer().setInitialVelocity(new Vector3f(2.0f, 0.0f, 2.0f));
|
||||||
|
stones.setStartSize(0.1f);
|
||||||
|
stones.setEndSize(0.5f);
|
||||||
|
stones.setGravity(2.0f, 0.0f, 2.0f);
|
||||||
|
stones.setLowLife(1.0f);
|
||||||
|
stones.setHighLife(1.0f);
|
||||||
|
stones.getParticleInfluencer().setVelocityVariation(5.0f);
|
||||||
|
this.attachChild((Spatial)stones);
|
||||||
|
stones.emitAllParticles();
|
||||||
|
AudioNode explode = new AudioNode(Exchange.assetManager, "Sounds/Effects/explosion.ogg", false);
|
||||||
|
explode.setLooping(false);
|
||||||
|
explode.setPositional(true);
|
||||||
|
explode.setLocalTranslation(this.getLocalTranslation());
|
||||||
|
explode.setVolume(5.0f);
|
||||||
|
this.attachChild((Spatial)explode);
|
||||||
|
explode.play();
|
||||||
|
this.explode = true;
|
||||||
|
this.fire.emitAllParticles();
|
||||||
|
this.timer = 0.1f;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* com.jme3.light.Light
|
||||||
|
* com.jme3.math.Vector3f
|
||||||
|
* com.jme3.post.SceneProcessor
|
||||||
|
* com.jme3.renderer.RenderManager
|
||||||
|
* com.jme3.renderer.ViewPort
|
||||||
|
* com.jme3.scene.control.AbstractControl
|
||||||
|
* com.jme3.scene.control.Control
|
||||||
|
*/
|
||||||
|
package mygame;
|
||||||
|
|
||||||
|
import com.jme3.light.Light;
|
||||||
|
import com.jme3.math.Vector3f;
|
||||||
|
import com.jme3.post.SceneProcessor;
|
||||||
|
import com.jme3.renderer.RenderManager;
|
||||||
|
import com.jme3.renderer.ViewPort;
|
||||||
|
import com.jme3.scene.control.AbstractControl;
|
||||||
|
import com.jme3.scene.control.Control;
|
||||||
|
import mygame.Exchange;
|
||||||
|
import mygame.Fireball;
|
||||||
|
import mygame.WalkingEnemy;
|
||||||
|
|
||||||
|
public class FireballControl
|
||||||
|
extends AbstractControl {
|
||||||
|
protected void controlUpdate(float tpf) {
|
||||||
|
WalkingEnemy enemy;
|
||||||
|
Fireball fireball = (Fireball)this.spatial;
|
||||||
|
if (!fireball.explode) {
|
||||||
|
fireball.bodyControl.setWalkDirection(fireball.direction);
|
||||||
|
} else {
|
||||||
|
fireball.bodyControl.setWalkDirection(Vector3f.ZERO);
|
||||||
|
}
|
||||||
|
Vector3f lightpos = fireball.collisionGhost.getPhysicsLocation();
|
||||||
|
lightpos.setY(0.5f);
|
||||||
|
fireball.light.setPosition(lightpos);
|
||||||
|
int i = 0;
|
||||||
|
while (true) {
|
||||||
|
Exchange cfr_ignored_0 = fireball.exchange;
|
||||||
|
if (i >= Exchange.enemies.size()) break;
|
||||||
|
Exchange cfr_ignored_1 = fireball.exchange;
|
||||||
|
enemy = Exchange.enemies.get(i);
|
||||||
|
if (fireball.collisionGhost.getOverlappingObjects().contains(enemy.ghost)) {
|
||||||
|
fireball.explode = true;
|
||||||
|
}
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
if (fireball.explode) {
|
||||||
|
i = 0;
|
||||||
|
while (true) {
|
||||||
|
Exchange cfr_ignored_2 = fireball.exchange;
|
||||||
|
if (i >= Exchange.enemies.size()) break;
|
||||||
|
Exchange cfr_ignored_3 = fireball.exchange;
|
||||||
|
enemy = Exchange.enemies.get(i);
|
||||||
|
if (fireball.damageAreaGhost.getOverlappingObjects().contains(enemy.ghost)) {
|
||||||
|
enemy.doDamage(100.0f);
|
||||||
|
fireball.explode();
|
||||||
|
}
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fireball.lifetime -= tpf;
|
||||||
|
if (fireball.lifetime <= 0.0f && !fireball.explode) {
|
||||||
|
fireball.explode();
|
||||||
|
}
|
||||||
|
if (fireball.timer > 0.0f) {
|
||||||
|
fireball.timer += tpf;
|
||||||
|
if ((double)fireball.timer > 0.2) {
|
||||||
|
fireball.bodyControl.setEnabled(false);
|
||||||
|
fireball.removeControl((Control)fireball.bodyControl);
|
||||||
|
fireball.removeControl((Control)fireball.collisionGhost);
|
||||||
|
fireball.collisionGhost.setEnabled(false);
|
||||||
|
fireball.removeControl((Control)fireball.damageAreaGhost);
|
||||||
|
fireball.damageAreaGhost.setEnabled(false);
|
||||||
|
fireball.fire.setEnabled(false);
|
||||||
|
}
|
||||||
|
if (fireball.timer > 2.0f) {
|
||||||
|
fireball.removeFromParent();
|
||||||
|
Exchange.viewPort.removeProcessor((SceneProcessor)fireball.dlsr);
|
||||||
|
Exchange.rootNode.removeLight((Light)fireball.light);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void controlRender(RenderManager rm, ViewPort vp) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* com.jme3.renderer.queue.RenderQueue$ShadowMode
|
||||||
|
* com.jme3.scene.Node
|
||||||
|
* com.jme3.scene.Spatial
|
||||||
|
*/
|
||||||
|
package mygame;
|
||||||
|
|
||||||
|
import com.jme3.renderer.queue.RenderQueue;
|
||||||
|
import com.jme3.scene.Node;
|
||||||
|
import com.jme3.scene.Spatial;
|
||||||
|
|
||||||
|
public abstract class Interactive
|
||||||
|
extends Node {
|
||||||
|
public Spatial model;
|
||||||
|
public Node wholeBody;
|
||||||
|
|
||||||
|
public Interactive(String name) {
|
||||||
|
super(name);
|
||||||
|
this.setShadowMode(RenderQueue.ShadowMode.Cast);
|
||||||
|
this.wholeBody = new Node(name);
|
||||||
|
this.attachChild((Spatial)this.wholeBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Spatial getModel() {
|
||||||
|
return this.model;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setModel(Spatial model) {
|
||||||
|
this.model = model;
|
||||||
|
this.setupModel();
|
||||||
|
this.wholeBody.attachChild(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract void setupModel();
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*/
|
||||||
|
package mygame;
|
||||||
|
|
||||||
|
import jme3.hello.TestCameraNode;
|
||||||
|
|
||||||
|
public class Main {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
TestCameraNode app = new TestCameraNode();
|
||||||
|
app.setDisplayFps(false);
|
||||||
|
app.setDisplayStatView(false);
|
||||||
|
app.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* com.jme3.animation.AnimChannel
|
||||||
|
* com.jme3.animation.AnimControl
|
||||||
|
* com.jme3.animation.LoopMode
|
||||||
|
* com.jme3.bullet.collision.shapes.CapsuleCollisionShape
|
||||||
|
* com.jme3.bullet.collision.shapes.CollisionShape
|
||||||
|
* com.jme3.bullet.control.BetterCharacterControl
|
||||||
|
* com.jme3.bullet.control.GhostControl
|
||||||
|
* com.jme3.math.Vector3f
|
||||||
|
* com.jme3.scene.Spatial
|
||||||
|
* com.jme3.scene.control.Control
|
||||||
|
*/
|
||||||
|
package mygame;
|
||||||
|
|
||||||
|
import com.jme3.animation.AnimChannel;
|
||||||
|
import com.jme3.animation.AnimControl;
|
||||||
|
import com.jme3.animation.LoopMode;
|
||||||
|
import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
|
||||||
|
import com.jme3.bullet.collision.shapes.CollisionShape;
|
||||||
|
import com.jme3.bullet.control.BetterCharacterControl;
|
||||||
|
import com.jme3.bullet.control.GhostControl;
|
||||||
|
import com.jme3.math.Vector3f;
|
||||||
|
import com.jme3.scene.Spatial;
|
||||||
|
import com.jme3.scene.control.Control;
|
||||||
|
import mygame.Character;
|
||||||
|
import mygame.Exchange;
|
||||||
|
import mygame.Weapon;
|
||||||
|
|
||||||
|
public class Player
|
||||||
|
extends Character {
|
||||||
|
public Weapon rightHand;
|
||||||
|
public Weapon leftHand;
|
||||||
|
public Spatial hat;
|
||||||
|
public Spatial model;
|
||||||
|
public BetterCharacterControl playerControl;
|
||||||
|
public AnimChannel leftHandAnimChannel;
|
||||||
|
public AnimControl leftHandAnimControl;
|
||||||
|
public AnimChannel rightHandAnimChannel;
|
||||||
|
public AnimControl rightHandAnimControl;
|
||||||
|
public Vector3f pt;
|
||||||
|
public GhostControl collision;
|
||||||
|
public float health = 100.0f;
|
||||||
|
public static float MAX_HEALTH = 100.0f;
|
||||||
|
|
||||||
|
public Player(String name) {
|
||||||
|
super(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Spatial getRightHand() {
|
||||||
|
return this.rightHand;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRightHand(Weapon rightHand) {
|
||||||
|
this.rightHand = rightHand;
|
||||||
|
this.setupRightHand();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Spatial getLeftHand() {
|
||||||
|
return this.leftHand;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLeftHand(Weapon leftHand) {
|
||||||
|
this.leftHand = leftHand;
|
||||||
|
this.setupLeftHand();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Spatial getHat() {
|
||||||
|
return this.hat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHat(Spatial hat) {
|
||||||
|
this.hat = hat;
|
||||||
|
this.setupHat();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Spatial getModel() {
|
||||||
|
return this.model;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setModel(Spatial model) {
|
||||||
|
this.model = model;
|
||||||
|
this.setupModel();
|
||||||
|
this.setupControl();
|
||||||
|
this.setupModelControl();
|
||||||
|
this.wholeBody.attachChild(this.model);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void switchHands() {
|
||||||
|
this.wholeBody.detachChild((Spatial)this.leftHand);
|
||||||
|
this.wholeBody.detachChild((Spatial)this.rightHand);
|
||||||
|
Weapon temp = this.rightHand;
|
||||||
|
this.setRightHand(this.leftHand);
|
||||||
|
this.setLeftHand(temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void die() {
|
||||||
|
this.modelAnimChannel.setAnim("die");
|
||||||
|
this.modelAnimChannel.setLoopMode(LoopMode.DontLoop);
|
||||||
|
this.wholeBody.setLocalTranslation(0.0f, 0.5f, 0.0f);
|
||||||
|
this.wholeBody.detachChild(this.hat);
|
||||||
|
this.wholeBody.detachChild((Spatial)this.rightHand);
|
||||||
|
this.wholeBody.detachChild((Spatial)this.leftHand);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupLeftHand() {
|
||||||
|
this.leftHand.setLocalTranslation(0.4f, 0.3f, 0.4f);
|
||||||
|
this.leftHand.setLocalScale(0.35f);
|
||||||
|
this.wholeBody.attachChild((Spatial)this.leftHand);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupRightHand() {
|
||||||
|
this.rightHand.setLocalTranslation(-0.4f, 0.3f, 0.4f);
|
||||||
|
this.rightHand.setLocalScale(0.35f);
|
||||||
|
this.wholeBody.attachChild((Spatial)this.rightHand);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupHat() {
|
||||||
|
this.hat.setLocalScale(0.35f);
|
||||||
|
this.hat.setLocalTranslation(0.0f, 0.9f, -0.2f);
|
||||||
|
this.hat.rotate(-0.4f, 0.0f, 0.0f);
|
||||||
|
this.wholeBody.attachChild(this.hat);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setupModel() {
|
||||||
|
this.model.setLocalScale(0.5f);
|
||||||
|
this.wholeBody.attachChild(this.model);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupControl() {
|
||||||
|
this.playerControl = new BetterCharacterControl(0.5f, 1.0f, 8.0f);
|
||||||
|
this.addControl((Control)this.playerControl);
|
||||||
|
this.collision = new GhostControl((CollisionShape)new CapsuleCollisionShape(1.0f, 0.5f));
|
||||||
|
this.collision.addCollideWithGroup(2);
|
||||||
|
this.addControl((Control)this.collision);
|
||||||
|
Exchange.bulletAppState.getPhysicsSpace().add((Object)this.collision);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setupModelControl() {
|
||||||
|
this.modelAnimControl = (AnimControl)this.model.getControl(AnimControl.class);
|
||||||
|
this.modelAnimChannel = this.modelAnimControl.createChannel();
|
||||||
|
this.modelAnimChannel.setAnim("stand");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void heal(float amount) {
|
||||||
|
this.health = amount + this.health > MAX_HEALTH ? MAX_HEALTH : (this.health += amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* com.jme3.animation.AnimControl
|
||||||
|
* com.jme3.bullet.collision.shapes.CapsuleCollisionShape
|
||||||
|
* com.jme3.bullet.collision.shapes.CollisionShape
|
||||||
|
* com.jme3.bullet.control.GhostControl
|
||||||
|
* com.jme3.effect.ParticleEmitter
|
||||||
|
* com.jme3.effect.ParticleMesh$Type
|
||||||
|
* com.jme3.material.Material
|
||||||
|
* com.jme3.math.ColorRGBA
|
||||||
|
* com.jme3.math.Quaternion
|
||||||
|
* com.jme3.math.Vector3f
|
||||||
|
* com.jme3.scene.Spatial
|
||||||
|
* com.jme3.scene.control.Control
|
||||||
|
*/
|
||||||
|
package mygame;
|
||||||
|
|
||||||
|
import com.jme3.animation.AnimControl;
|
||||||
|
import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
|
||||||
|
import com.jme3.bullet.collision.shapes.CollisionShape;
|
||||||
|
import com.jme3.bullet.control.GhostControl;
|
||||||
|
import com.jme3.effect.ParticleEmitter;
|
||||||
|
import com.jme3.effect.ParticleMesh;
|
||||||
|
import com.jme3.material.Material;
|
||||||
|
import com.jme3.math.ColorRGBA;
|
||||||
|
import com.jme3.math.Quaternion;
|
||||||
|
import com.jme3.math.Vector3f;
|
||||||
|
import com.jme3.scene.Spatial;
|
||||||
|
import com.jme3.scene.control.Control;
|
||||||
|
import mygame.Character;
|
||||||
|
import mygame.Exchange;
|
||||||
|
|
||||||
|
public class PowerUpShield
|
||||||
|
extends Character {
|
||||||
|
public GhostControl collision;
|
||||||
|
|
||||||
|
public PowerUpShield(String name) {
|
||||||
|
super(name);
|
||||||
|
this.setModel();
|
||||||
|
this.setupControl();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setupModelControl() {
|
||||||
|
this.modelAnimControl = (AnimControl)this.model.getControl(AnimControl.class);
|
||||||
|
this.modelAnimChannel = this.modelAnimControl.createChannel();
|
||||||
|
this.modelAnimChannel.setAnim("normal");
|
||||||
|
this.modelAnimChannel.setSpeed(0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setupModel() {
|
||||||
|
this.wholeBody.attachChild(this.model);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setupControl() {
|
||||||
|
this.collision = new GhostControl((CollisionShape)new CapsuleCollisionShape(1.0f, 0.5f));
|
||||||
|
this.collision.addCollideWithGroup(2);
|
||||||
|
this.addControl((Control)this.collision);
|
||||||
|
Exchange.bulletAppState.getPhysicsSpace().add((Object)this.collision);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setModel() {
|
||||||
|
this.model = Exchange.assetManager.loadModel("Models/shield/shield.mesh.j3o");
|
||||||
|
this.model.setMaterial(Exchange.assetManager.loadMaterial("Materials/PowerUpShield.j3m"));
|
||||||
|
this.model.rotate(new Quaternion().fromAngleAxis((float)Math.PI, new Vector3f(0.0f, 1.0f, 0.0f)));
|
||||||
|
this.model.setLocalTranslation(0.0f, 1.0f, 0.0f);
|
||||||
|
this.setLocalScale(0.5f);
|
||||||
|
this.setupModel();
|
||||||
|
this.setupModelControl();
|
||||||
|
this.wholeBody.attachChild(this.model);
|
||||||
|
ParticleEmitter bobble = new ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, 1);
|
||||||
|
Material mat_red = Exchange.assetManager.loadMaterial("Materials/bubble.j3m");
|
||||||
|
bobble.setMaterial(mat_red);
|
||||||
|
bobble.setImagesX(1);
|
||||||
|
bobble.setImagesY(1);
|
||||||
|
bobble.setEndColor(new ColorRGBA(1.0f, 1.0f, 0.0f, 0.75f));
|
||||||
|
bobble.setStartColor(new ColorRGBA(1.0f, 1.0f, 0.0f, 1.0f));
|
||||||
|
bobble.getParticleInfluencer().setInitialVelocity(new Vector3f(0.0f, 0.0f, 0.0f));
|
||||||
|
bobble.setStartSize(1.0f);
|
||||||
|
bobble.setEndSize(1.1f);
|
||||||
|
bobble.setGravity(0.0f, 0.0f, 0.0f);
|
||||||
|
bobble.setLowLife(1.0f);
|
||||||
|
bobble.setHighLife(1.0f);
|
||||||
|
bobble.getParticleInfluencer().setVelocityVariation(0.0f);
|
||||||
|
this.attachChild((Spatial)bobble);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void kill() {
|
||||||
|
this.removeFromParent();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* com.jme3.animation.AnimChannel
|
||||||
|
* com.jme3.animation.AnimControl
|
||||||
|
* com.jme3.animation.AnimEventListener
|
||||||
|
*/
|
||||||
|
package mygame;
|
||||||
|
|
||||||
|
import com.jme3.animation.AnimChannel;
|
||||||
|
import com.jme3.animation.AnimControl;
|
||||||
|
import com.jme3.animation.AnimEventListener;
|
||||||
|
import mygame.Weapon;
|
||||||
|
|
||||||
|
public class SkillListener
|
||||||
|
implements AnimEventListener {
|
||||||
|
private Weapon weapon;
|
||||||
|
|
||||||
|
public SkillListener(Weapon w) {
|
||||||
|
this.weapon = w;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) {
|
||||||
|
if (animName.equals("skill1")) {
|
||||||
|
this.weapon.skill1 = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onAnimChange(AnimControl control, AnimChannel channel, String animName) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* com.jme3.animation.LoopMode
|
||||||
|
* com.jme3.asset.AssetManager
|
||||||
|
* com.jme3.audio.AudioNode
|
||||||
|
* com.jme3.bullet.control.GhostControl
|
||||||
|
* com.jme3.math.Ray
|
||||||
|
* com.jme3.math.Vector3f
|
||||||
|
* com.jme3.scene.Spatial
|
||||||
|
*/
|
||||||
|
package mygame;
|
||||||
|
|
||||||
|
import com.jme3.animation.LoopMode;
|
||||||
|
import com.jme3.asset.AssetManager;
|
||||||
|
import com.jme3.audio.AudioNode;
|
||||||
|
import com.jme3.bullet.control.GhostControl;
|
||||||
|
import com.jme3.math.Ray;
|
||||||
|
import com.jme3.math.Vector3f;
|
||||||
|
import com.jme3.scene.Spatial;
|
||||||
|
import mygame.Exchange;
|
||||||
|
import mygame.Fireball;
|
||||||
|
import mygame.Player;
|
||||||
|
import mygame.Weapon;
|
||||||
|
|
||||||
|
public class Staff
|
||||||
|
extends Weapon {
|
||||||
|
GhostControl ghost;
|
||||||
|
AssetManager assetManager;
|
||||||
|
|
||||||
|
public Staff(String name) {
|
||||||
|
super(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Staff(String name, AssetManager am, Exchange ex) {
|
||||||
|
super(name);
|
||||||
|
this.assetManager = am;
|
||||||
|
this.exchange = ex;
|
||||||
|
this.COOLDOWN1_TIME = 5.0f;
|
||||||
|
this.COOLDOWN2_TIME = 50.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setupControl() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void skill1() {
|
||||||
|
if (this.cooldown1 <= 0.0f) {
|
||||||
|
this.cooldown1 = this.COOLDOWN1_TIME;
|
||||||
|
this.modelAnimChannel.setAnim("skill1");
|
||||||
|
Player player = (Player)this.getParent().getParent();
|
||||||
|
Ray ray = new Ray(this.getWorldTranslation(), player.pt);
|
||||||
|
Vector3f direction = player.pt.subtract(this.getWorldTranslation()).normalize().mult(25.0f);
|
||||||
|
direction.setY(0.0f);
|
||||||
|
this.modelAnimChannel.setLoopMode(LoopMode.DontLoop);
|
||||||
|
Fireball fireball = new Fireball(this.assetManager, direction, player.getLocalTranslation(), this.exchange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void skill2() {
|
||||||
|
if (this.cooldown2 <= 0.0f) {
|
||||||
|
this.cooldown2 = this.COOLDOWN2_TIME;
|
||||||
|
AudioNode hit = new AudioNode(this.assetManager, "Sounds/Effects/teleport.ogg", false);
|
||||||
|
hit.setLooping(false);
|
||||||
|
hit.setPositional(true);
|
||||||
|
hit.setLocalTranslation(this.getLocalTranslation());
|
||||||
|
hit.setVolume(5.0f);
|
||||||
|
this.attachChild((Spatial)hit);
|
||||||
|
hit.play();
|
||||||
|
Player player = (Player)Exchange.getRootNode().getChild("Player");
|
||||||
|
Vector3f place = player.pt;
|
||||||
|
place.setY(1.0f);
|
||||||
|
player.playerControl.warp(place);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* com.jme3.animation.AnimEventListener
|
||||||
|
* com.jme3.animation.LoopMode
|
||||||
|
* com.jme3.asset.AssetManager
|
||||||
|
* com.jme3.audio.AudioNode
|
||||||
|
* com.jme3.bullet.collision.shapes.BoxCollisionShape
|
||||||
|
* com.jme3.bullet.collision.shapes.CollisionShape
|
||||||
|
* com.jme3.bullet.control.GhostControl
|
||||||
|
* com.jme3.math.Vector3f
|
||||||
|
* com.jme3.scene.Spatial
|
||||||
|
* com.jme3.scene.control.Control
|
||||||
|
*/
|
||||||
|
package mygame;
|
||||||
|
|
||||||
|
import com.jme3.animation.AnimEventListener;
|
||||||
|
import com.jme3.animation.LoopMode;
|
||||||
|
import com.jme3.asset.AssetManager;
|
||||||
|
import com.jme3.audio.AudioNode;
|
||||||
|
import com.jme3.bullet.collision.shapes.BoxCollisionShape;
|
||||||
|
import com.jme3.bullet.collision.shapes.CollisionShape;
|
||||||
|
import com.jme3.bullet.control.GhostControl;
|
||||||
|
import com.jme3.math.Vector3f;
|
||||||
|
import com.jme3.scene.Spatial;
|
||||||
|
import com.jme3.scene.control.Control;
|
||||||
|
import mygame.Exchange;
|
||||||
|
import mygame.Player;
|
||||||
|
import mygame.SkillListener;
|
||||||
|
import mygame.WalkingEnemy;
|
||||||
|
import mygame.Weapon;
|
||||||
|
|
||||||
|
public class Sword
|
||||||
|
extends Weapon
|
||||||
|
implements Runnable {
|
||||||
|
GhostControl ghost;
|
||||||
|
AssetManager assetManager;
|
||||||
|
|
||||||
|
public Sword(String name) {
|
||||||
|
super(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Sword(String name, AssetManager am, Exchange ex) {
|
||||||
|
super(name);
|
||||||
|
this.assetManager = am;
|
||||||
|
this.exchange = ex;
|
||||||
|
this.COOLDOWN1_TIME = 0.5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setupControl() {
|
||||||
|
this.ghost = new GhostControl((CollisionShape)new BoxCollisionShape(new Vector3f(0.25f, 0.25f, 1.75f)));
|
||||||
|
this.ghost.addCollideWithGroup(1);
|
||||||
|
this.addControl((Control)this.ghost);
|
||||||
|
this.modelAnimControl.addListener((AnimEventListener)new SkillListener(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void skill1() {
|
||||||
|
if (this.cooldown1 <= 0.0f && !this.skill1) {
|
||||||
|
this.skill1 = true;
|
||||||
|
this.cooldown1 = this.COOLDOWN1_TIME;
|
||||||
|
this.modelAnimChannel.setAnim("skill1");
|
||||||
|
this.modelAnimChannel.setSpeed(2.0f);
|
||||||
|
this.modelAnimChannel.setLoopMode(LoopMode.DontLoop);
|
||||||
|
AudioNode hit = new AudioNode(this.assetManager, "Sounds/Effects/sword_hit1.ogg", false);
|
||||||
|
hit.setLooping(false);
|
||||||
|
hit.setPositional(true);
|
||||||
|
hit.setLocalTranslation(this.getLocalTranslation());
|
||||||
|
hit.setVolume(5.0f);
|
||||||
|
this.attachChild((Spatial)hit);
|
||||||
|
hit.play();
|
||||||
|
int i = 0;
|
||||||
|
while (true) {
|
||||||
|
if (i >= Exchange.enemies.size()) break;
|
||||||
|
WalkingEnemy enemy = Exchange.enemies.get(i);
|
||||||
|
if (this.ghost.getOverlappingObjects().contains(enemy.ghost)) {
|
||||||
|
enemy.doDamage(50.0f);
|
||||||
|
Player player = (Player)this.getParent().getParent();
|
||||||
|
}
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
this.skill1();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void skill2() {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* com.jme3.animation.AnimControl
|
||||||
|
* com.jme3.animation.LoopMode
|
||||||
|
* com.jme3.app.Application
|
||||||
|
* com.jme3.asset.AssetManager
|
||||||
|
* com.jme3.audio.AudioNode
|
||||||
|
* com.jme3.bullet.collision.shapes.CapsuleCollisionShape
|
||||||
|
* com.jme3.bullet.collision.shapes.CollisionShape
|
||||||
|
* com.jme3.bullet.control.BetterCharacterControl
|
||||||
|
* com.jme3.bullet.control.GhostControl
|
||||||
|
* com.jme3.effect.ParticleEmitter
|
||||||
|
* com.jme3.effect.ParticleMesh$Type
|
||||||
|
* com.jme3.material.Material
|
||||||
|
* com.jme3.math.ColorRGBA
|
||||||
|
* com.jme3.math.Vector3f
|
||||||
|
* com.jme3.scene.Spatial
|
||||||
|
* com.jme3.scene.control.Control
|
||||||
|
* tonegod.gui.core.Screen
|
||||||
|
*/
|
||||||
|
package mygame;
|
||||||
|
|
||||||
|
import com.jme3.animation.AnimControl;
|
||||||
|
import com.jme3.animation.LoopMode;
|
||||||
|
import com.jme3.app.Application;
|
||||||
|
import com.jme3.asset.AssetManager;
|
||||||
|
import com.jme3.audio.AudioNode;
|
||||||
|
import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
|
||||||
|
import com.jme3.bullet.collision.shapes.CollisionShape;
|
||||||
|
import com.jme3.bullet.control.BetterCharacterControl;
|
||||||
|
import com.jme3.bullet.control.GhostControl;
|
||||||
|
import com.jme3.effect.ParticleEmitter;
|
||||||
|
import com.jme3.effect.ParticleMesh;
|
||||||
|
import com.jme3.material.Material;
|
||||||
|
import com.jme3.math.ColorRGBA;
|
||||||
|
import com.jme3.math.Vector3f;
|
||||||
|
import com.jme3.scene.Spatial;
|
||||||
|
import com.jme3.scene.control.Control;
|
||||||
|
import mygame.Character;
|
||||||
|
import mygame.Exchange;
|
||||||
|
import mygame.WalkingEnemyControl;
|
||||||
|
import tonegod.gui.core.Screen;
|
||||||
|
|
||||||
|
public class WalkingEnemy
|
||||||
|
extends Character {
|
||||||
|
public BetterCharacterControl walkingEnemyControl;
|
||||||
|
public GhostControl ghost;
|
||||||
|
private WalkingEnemyControl control = new WalkingEnemyControl();
|
||||||
|
private float health;
|
||||||
|
private AssetManager assetManager;
|
||||||
|
public float reverseWalk;
|
||||||
|
public ParticleEmitter blood;
|
||||||
|
public boolean dead = false;
|
||||||
|
public float deadtimer = 0.0f;
|
||||||
|
|
||||||
|
public WalkingEnemy(String name) {
|
||||||
|
super(name);
|
||||||
|
this.addControl((Control)this.control);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WalkingEnemy(String name, AssetManager am) {
|
||||||
|
super(name);
|
||||||
|
this.addControl((Control)this.control);
|
||||||
|
this.assetManager = am;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setupModelControl() {
|
||||||
|
this.modelAnimControl = (AnimControl)this.model.getControl(AnimControl.class);
|
||||||
|
this.modelAnimChannel = this.modelAnimControl.createChannel();
|
||||||
|
this.modelAnimChannel.setAnim("stand");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setupGUI() {
|
||||||
|
Screen screen = new Screen((Application)Exchange.app, "tonegod/gui/style/def/style_map.xml");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setModel(Spatial model) {
|
||||||
|
this.model = model;
|
||||||
|
this.setupModel();
|
||||||
|
this.setupControl();
|
||||||
|
this.setupModelControl();
|
||||||
|
this.wholeBody.attachChild(this.model);
|
||||||
|
this.blood = new ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, 10);
|
||||||
|
Material mat_red = this.assetManager.loadMaterial("Materials/blood.j3m");
|
||||||
|
this.blood.setMaterial(mat_red);
|
||||||
|
this.blood.setImagesX(2);
|
||||||
|
this.blood.setImagesY(2);
|
||||||
|
this.blood.setEndColor(new ColorRGBA(1.0f, 0.0f, 0.0f, 0.75f));
|
||||||
|
this.blood.setStartColor(new ColorRGBA(1.0f, 0.0f, 0.0f, 0.25f));
|
||||||
|
this.blood.getParticleInfluencer().setInitialVelocity(new Vector3f(1.0f, 2.0f, 1.0f));
|
||||||
|
this.blood.setParticlesPerSec(0.0f);
|
||||||
|
this.blood.setStartSize(1.0f);
|
||||||
|
this.blood.setEndSize(1.0f);
|
||||||
|
this.blood.setGravity(0.0f, 2.0f, 0.0f);
|
||||||
|
this.blood.setLowLife(1.0f);
|
||||||
|
this.blood.setHighLife(0.5f);
|
||||||
|
this.blood.getParticleInfluencer().setVelocityVariation(0.3f);
|
||||||
|
this.blood.setRandomAngle(true);
|
||||||
|
this.blood.setSelectRandomImage(true);
|
||||||
|
this.attachChild((Spatial)this.blood);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setupModel() {
|
||||||
|
this.model.setLocalScale(0.5f);
|
||||||
|
this.wholeBody.attachChild(this.model);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupControl() {
|
||||||
|
this.walkingEnemyControl = new BetterCharacterControl(1.0f, 2.0f, 8.0f);
|
||||||
|
this.addControl((Control)this.walkingEnemyControl);
|
||||||
|
this.ghost = new GhostControl((CollisionShape)new CapsuleCollisionShape(1.0f, 0.5f));
|
||||||
|
this.ghost.addCollideWithGroup(1);
|
||||||
|
this.addControl((Control)this.ghost);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getHealth() {
|
||||||
|
return this.health;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHealth(float health) {
|
||||||
|
this.health = health;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void doDamage(float damage) {
|
||||||
|
this.health -= damage;
|
||||||
|
this.reverseWalk = 0.5f;
|
||||||
|
if (this.health <= 0.0f) {
|
||||||
|
this.die();
|
||||||
|
}
|
||||||
|
AudioNode hit = new AudioNode(this.assetManager, "Sounds/Effects/damage.ogg", false);
|
||||||
|
hit.setLooping(false);
|
||||||
|
hit.setVolume(100.0f);
|
||||||
|
this.getParent().attachChild((Spatial)hit);
|
||||||
|
hit.play();
|
||||||
|
this.blood.emitAllParticles();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void die() {
|
||||||
|
this.modelAnimChannel.setAnim("die");
|
||||||
|
this.modelAnimChannel.setLoopMode(LoopMode.DontLoop);
|
||||||
|
this.ghost.setEnabled(false);
|
||||||
|
this.walkingEnemyControl.setEnabled(false);
|
||||||
|
AudioNode an = (AudioNode)this.getChild("walkingSound");
|
||||||
|
an.stop();
|
||||||
|
Exchange.removeEnemy(this);
|
||||||
|
Exchange.setKilled(Exchange.killed + 1);
|
||||||
|
Exchange.spawnItem(this.getLocalTranslation());
|
||||||
|
this.dead = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* com.jme3.math.Vector3f
|
||||||
|
* com.jme3.renderer.RenderManager
|
||||||
|
* com.jme3.renderer.ViewPort
|
||||||
|
* com.jme3.scene.control.AbstractControl
|
||||||
|
*/
|
||||||
|
package mygame;
|
||||||
|
|
||||||
|
import com.jme3.math.Vector3f;
|
||||||
|
import com.jme3.renderer.RenderManager;
|
||||||
|
import com.jme3.renderer.ViewPort;
|
||||||
|
import com.jme3.scene.control.AbstractControl;
|
||||||
|
import mygame.WalkingEnemy;
|
||||||
|
|
||||||
|
public class WalkingEnemyControl
|
||||||
|
extends AbstractControl {
|
||||||
|
public WalkingEnemy walkingEnemy;
|
||||||
|
|
||||||
|
protected void controlUpdate(float tpf) {
|
||||||
|
this.walkingEnemy = (WalkingEnemy)this.spatial;
|
||||||
|
if (!this.walkingEnemy.dead) {
|
||||||
|
Vector3f walkdirection = new Vector3f(this.walkingEnemy.walkingEnemyControl.getViewDirection().x, 0.0f, this.walkingEnemy.walkingEnemyControl.getViewDirection().z).normalize().mult(6.0f);
|
||||||
|
if (this.walkingEnemy.reverseWalk > 0.0f) {
|
||||||
|
walkdirection.negateLocal();
|
||||||
|
this.walkingEnemy.reverseWalk -= tpf;
|
||||||
|
}
|
||||||
|
this.walkingEnemy.walkingEnemyControl.setViewDirection(this.spatial.getParent().getChild("Player").getLocalTranslation().subtract(this.spatial.getLocalTranslation()));
|
||||||
|
this.walkingEnemy.walkingEnemyControl.setWalkDirection(walkdirection);
|
||||||
|
} else {
|
||||||
|
this.walkingEnemy.deadtimer += tpf;
|
||||||
|
if (this.walkingEnemy.deadtimer > 10.0f) {
|
||||||
|
this.walkingEnemy.removeFromParent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void controlRender(RenderManager rm, ViewPort vp) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* com.jme3.animation.AnimControl
|
||||||
|
* com.jme3.bullet.control.GhostControl
|
||||||
|
* com.jme3.scene.Spatial
|
||||||
|
*/
|
||||||
|
package mygame;
|
||||||
|
|
||||||
|
import com.jme3.animation.AnimControl;
|
||||||
|
import com.jme3.bullet.control.GhostControl;
|
||||||
|
import com.jme3.scene.Spatial;
|
||||||
|
import mygame.Character;
|
||||||
|
import mygame.Exchange;
|
||||||
|
|
||||||
|
public abstract class Weapon
|
||||||
|
extends Character {
|
||||||
|
GhostControl weaponControl;
|
||||||
|
public Exchange exchange;
|
||||||
|
public boolean skill1 = false;
|
||||||
|
public float cooldown1 = 0.0f;
|
||||||
|
public float cooldown2 = 0.0f;
|
||||||
|
public float cooldown3 = 0.0f;
|
||||||
|
public float COOLDOWN1_TIME = 0.0f;
|
||||||
|
public float COOLDOWN2_TIME = 0.0f;
|
||||||
|
public float COOLDOWN3_TIME = 0.0f;
|
||||||
|
|
||||||
|
public Weapon(String name) {
|
||||||
|
super(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Weapon(String name, Exchange ex) {
|
||||||
|
super(name);
|
||||||
|
this.exchange = ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setModel(Spatial model) {
|
||||||
|
this.model = model;
|
||||||
|
this.setupModel();
|
||||||
|
this.setupModelControl();
|
||||||
|
this.setupControl();
|
||||||
|
this.wholeBody.attachChild(this.model);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void setupControl();
|
||||||
|
|
||||||
|
public void setupModelControl() {
|
||||||
|
this.modelAnimControl = (AnimControl)this.model.getControl(AnimControl.class);
|
||||||
|
this.modelAnimChannel = this.modelAnimControl.createChannel();
|
||||||
|
this.modelAnimChannel.setAnim("normal");
|
||||||
|
}
|
||||||
|
|
||||||
|
void setupModel() {
|
||||||
|
this.wholeBody.attachChild(this.model);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reduceCooldowns(float tpf) {
|
||||||
|
if (this.cooldown1 > 0.0f) {
|
||||||
|
this.cooldown1 -= tpf;
|
||||||
|
}
|
||||||
|
if (this.cooldown2 > 0.0f) {
|
||||||
|
this.cooldown2 -= tpf;
|
||||||
|
}
|
||||||
|
if (this.cooldown3 > 0.0f) {
|
||||||
|
this.cooldown3 -= tpf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void skill1();
|
||||||
|
|
||||||
|
public abstract void skill2();
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* com.jme3.app.SimpleApplication
|
||||||
|
* com.jme3.material.Material
|
||||||
|
* com.jme3.math.Quaternion
|
||||||
|
* com.jme3.math.Vector3f
|
||||||
|
* com.jme3.scene.Geometry
|
||||||
|
* com.jme3.scene.Mesh
|
||||||
|
* com.jme3.scene.Spatial
|
||||||
|
* com.jme3.scene.shape.Quad
|
||||||
|
*/
|
||||||
|
package mygame;
|
||||||
|
|
||||||
|
import com.jme3.app.SimpleApplication;
|
||||||
|
import com.jme3.material.Material;
|
||||||
|
import com.jme3.math.Quaternion;
|
||||||
|
import com.jme3.math.Vector3f;
|
||||||
|
import com.jme3.scene.Geometry;
|
||||||
|
import com.jme3.scene.Mesh;
|
||||||
|
import com.jme3.scene.Spatial;
|
||||||
|
import com.jme3.scene.shape.Quad;
|
||||||
|
|
||||||
|
public class test1
|
||||||
|
extends SimpleApplication {
|
||||||
|
private Spatial player;
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
test1 app = new test1();
|
||||||
|
app.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void simpleInitApp() {
|
||||||
|
this.player = this.assetManager.loadModel("Models/blob/Blob.mesh.xml");
|
||||||
|
Material mat_ground = new Material(this.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||||
|
mat_ground.setTexture("ColorMap", this.assetManager.loadTexture("Textures/Rock.PNG"));
|
||||||
|
Geometry ground = new Geometry("ground", (Mesh)new Quad(50.0f, 50.0f));
|
||||||
|
ground.setLocalRotation(new Quaternion().fromAngleAxis(-1.5707964f, Vector3f.UNIT_X));
|
||||||
|
ground.setLocalTranslation(-25.0f, -1.0f, 25.0f);
|
||||||
|
ground.setMaterial(mat_ground);
|
||||||
|
this.rootNode.attachChild((Spatial)ground);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* com.jme3.math.Quaternion
|
||||||
|
* com.jme3.math.Vector3f
|
||||||
|
* com.jme3.renderer.Camera
|
||||||
|
* com.jme3.scene.CameraNode
|
||||||
|
* com.jme3.scene.Node
|
||||||
|
* com.jme3.scene.Spatial
|
||||||
|
* com.jme3.scene.control.CameraControl
|
||||||
|
*/
|
||||||
|
package wpq.tests;
|
||||||
|
|
||||||
|
import com.jme3.math.Quaternion;
|
||||||
|
import com.jme3.math.Vector3f;
|
||||||
|
import com.jme3.renderer.Camera;
|
||||||
|
import com.jme3.scene.CameraNode;
|
||||||
|
import com.jme3.scene.Node;
|
||||||
|
import com.jme3.scene.Spatial;
|
||||||
|
import com.jme3.scene.control.CameraControl;
|
||||||
|
|
||||||
|
public class PlayerCam
|
||||||
|
extends CameraNode {
|
||||||
|
private Node player;
|
||||||
|
private Quaternion CAM_ROTATION;
|
||||||
|
public static Vector3f CAM_VECTOR = new Vector3f(0.0f, 25.0f, -15.0f);
|
||||||
|
|
||||||
|
public void setPlayer(Node player) {
|
||||||
|
this.player = player;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Node getPlayer() {
|
||||||
|
return this.player;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlayerCam(Node player, String name, Camera camera) {
|
||||||
|
super(name, new CameraControl(camera));
|
||||||
|
this.player = player;
|
||||||
|
this.CAM_ROTATION = new Quaternion();
|
||||||
|
this.CAM_ROTATION.fromAngleAxis(0.7853982f, Vector3f.UNIT_X);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setupCamera() {
|
||||||
|
this.player.attachChild((Spatial)this);
|
||||||
|
this.setLocalTranslation(CAM_VECTOR);
|
||||||
|
this.lookAt(this.player.getLocalTranslation(), Vector3f.UNIT_Y);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,392 @@
|
|||||||
|
import com.jme3.animation.AnimControl;
|
||||||
|
import com.jme3.animation.Animation;
|
||||||
|
import com.jme3.animation.Bone;
|
||||||
|
import com.jme3.animation.BoneTrack;
|
||||||
|
import com.jme3.animation.Skeleton;
|
||||||
|
import com.jme3.animation.Track;
|
||||||
|
import com.jme3.export.binary.BinaryImporter;
|
||||||
|
import com.jme3.math.Quaternion;
|
||||||
|
import com.jme3.math.Vector3f;
|
||||||
|
import com.jme3.scene.Geometry;
|
||||||
|
import com.jme3.scene.Mesh;
|
||||||
|
import com.jme3.scene.Node;
|
||||||
|
import com.jme3.scene.Spatial;
|
||||||
|
import com.jme3.scene.VertexBuffer;
|
||||||
|
import com.jme3.util.IntMap;
|
||||||
|
import com.jme3.util.SafeArrayList;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.FloatBuffer;
|
||||||
|
import java.nio.ShortBuffer;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
public class J3OConverter {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(J3OConverter.class.getName());
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
if (args.length < 2) {
|
||||||
|
System.err.println("Usage: J3OConverter <input.j3o> <output.json>");
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
File inputFile = new File(args[0]);
|
||||||
|
File outputFile = new File(args[1]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
BinaryImporter importer = new BinaryImporter();
|
||||||
|
Object root = importer.load(inputFile);
|
||||||
|
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
List<Map<String, Object>> geometries = new ArrayList<>();
|
||||||
|
List<Map<String, Object>> skeletons = new ArrayList<>();
|
||||||
|
Map<String, Object> animations = new HashMap<>();
|
||||||
|
|
||||||
|
if (root instanceof Node) {
|
||||||
|
extractFromNode((Node) root, geometries, skeletons, animations, new HashMap<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
result.put("geometries", geometries);
|
||||||
|
result.put("skeletons", skeletons);
|
||||||
|
result.put("animations", animations);
|
||||||
|
|
||||||
|
writeJson(outputFile, result);
|
||||||
|
System.out.println("Wrote " + outputFile);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void extractFromNode(Node node,
|
||||||
|
List<Map<String, Object>> geometries,
|
||||||
|
List<Map<String, Object>> skeletons,
|
||||||
|
Map<String, Object> animations,
|
||||||
|
Map<Integer, Integer> boneIdMap) {
|
||||||
|
for (Spatial child : node.getChildren()) {
|
||||||
|
if (child instanceof Node) {
|
||||||
|
extractFromNode((Node) child, geometries, skeletons, animations, boneIdMap);
|
||||||
|
} else if (child instanceof Geometry) {
|
||||||
|
Geometry geo = (Geometry) child;
|
||||||
|
Map<String, Object> meshData = extractMesh(geo.getMesh());
|
||||||
|
if (meshData != null) {
|
||||||
|
meshData.put("name", geo.getName());
|
||||||
|
geometries.add(meshData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look for AnimControl
|
||||||
|
AnimControl animControl = node.getControl(AnimControl.class);
|
||||||
|
if (animControl != null) {
|
||||||
|
Skeleton skeleton = animControl.getSkeleton();
|
||||||
|
if (skeleton != null) {
|
||||||
|
Map<String, Object> skeletonData = extractSkeleton(skeleton);
|
||||||
|
if (skeletonData != null) {
|
||||||
|
skeletons.add(skeletonData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String animName : animControl.getAnimationNames()) {
|
||||||
|
Animation anim = animControl.getAnim(animName);
|
||||||
|
Map<String, Object> animData = extractAnimation(anim);
|
||||||
|
if (animData != null) {
|
||||||
|
animations.put(animName, animData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, Object> extractMesh(Mesh mesh) {
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
result.put("vertexCount", mesh.getVertexCount());
|
||||||
|
result.put("triangleCount", mesh.getTriangleCount());
|
||||||
|
result.put("elementCount", mesh.getTriangleCount() * 3);
|
||||||
|
|
||||||
|
// Positions
|
||||||
|
VertexBuffer posBuf = mesh.getBuffer(VertexBuffer.Type.Position);
|
||||||
|
if (posBuf != null) {
|
||||||
|
FloatBuffer fb = (FloatBuffer) posBuf.getData();
|
||||||
|
float[] positions = new float[fb.limit()];
|
||||||
|
fb.rewind();
|
||||||
|
fb.get(positions);
|
||||||
|
result.put("positions", positions);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normals
|
||||||
|
VertexBuffer normBuf = mesh.getBuffer(VertexBuffer.Type.Normal);
|
||||||
|
if (normBuf != null) {
|
||||||
|
FloatBuffer fb = (FloatBuffer) normBuf.getData();
|
||||||
|
float[] normals = new float[fb.limit()];
|
||||||
|
fb.rewind();
|
||||||
|
fb.get(normals);
|
||||||
|
result.put("normals", normals);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TexCoords
|
||||||
|
VertexBuffer texBuf = mesh.getBuffer(VertexBuffer.Type.TexCoord);
|
||||||
|
if (texBuf != null) {
|
||||||
|
FloatBuffer fb = (FloatBuffer) texBuf.getData();
|
||||||
|
float[] texcoords = new float[fb.limit()];
|
||||||
|
fb.rewind();
|
||||||
|
fb.get(texcoords);
|
||||||
|
result.put("texcoords", texcoords);
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoneWeights
|
||||||
|
VertexBuffer weightBuf = mesh.getBuffer(VertexBuffer.Type.BoneWeight);
|
||||||
|
if (weightBuf != null) {
|
||||||
|
FloatBuffer fb = (FloatBuffer) weightBuf.getData();
|
||||||
|
float[] weights = new float[fb.limit()];
|
||||||
|
fb.rewind();
|
||||||
|
fb.get(weights);
|
||||||
|
result.put("boneWeights", weights);
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoneIndices
|
||||||
|
VertexBuffer indexBuf = mesh.getBuffer(VertexBuffer.Type.BoneIndex);
|
||||||
|
if (indexBuf != null) {
|
||||||
|
java.nio.Buffer buf = indexBuf.getData();
|
||||||
|
short[] indices;
|
||||||
|
if (buf instanceof ShortBuffer) {
|
||||||
|
ShortBuffer sb = (ShortBuffer) buf;
|
||||||
|
indices = new short[sb.limit()];
|
||||||
|
sb.rewind();
|
||||||
|
sb.get(indices);
|
||||||
|
int[] intIndices = new int[indices.length];
|
||||||
|
for (int i = 0; i < indices.length; i++) {
|
||||||
|
intIndices[i] = indices[i] & 0xFFFF;
|
||||||
|
}
|
||||||
|
result.put("boneIndices", intIndices);
|
||||||
|
} else if (buf instanceof java.nio.ByteBuffer) {
|
||||||
|
java.nio.ByteBuffer bb = (java.nio.ByteBuffer) buf;
|
||||||
|
byte[] bytes = new byte[bb.limit()];
|
||||||
|
bb.rewind();
|
||||||
|
bb.get(bytes);
|
||||||
|
int[] intIndices = new int[bytes.length];
|
||||||
|
for (int i = 0; i < bytes.length; i++) {
|
||||||
|
intIndices[i] = bytes[i] & 0xFF;
|
||||||
|
}
|
||||||
|
result.put("boneIndices", intIndices);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indices - JME3 doesn't always have an index buffer (uses non-indexed rendering)
|
||||||
|
// Try to get the indices from the mesh's LOD levels or mode
|
||||||
|
VertexBuffer indexBuffer = mesh.getBuffer(VertexBuffer.Type.Index);
|
||||||
|
if (indexBuffer != null) {
|
||||||
|
java.nio.Buffer buf = indexBuffer.getData();
|
||||||
|
if (buf instanceof ShortBuffer) {
|
||||||
|
ShortBuffer sb = (ShortBuffer) buf;
|
||||||
|
short[] indices = new short[sb.limit()];
|
||||||
|
sb.rewind();
|
||||||
|
sb.get(indices);
|
||||||
|
int[] intIndices = new int[indices.length];
|
||||||
|
for (int i = 0; i < indices.length; i++) {
|
||||||
|
intIndices[i] = indices[i] & 0xFFFF;
|
||||||
|
}
|
||||||
|
result.put("indices", intIndices);
|
||||||
|
} else if (buf instanceof java.nio.IntBuffer) {
|
||||||
|
java.nio.IntBuffer ib = (java.nio.IntBuffer) buf;
|
||||||
|
int[] indices = new int[ib.limit()];
|
||||||
|
ib.rewind();
|
||||||
|
ib.get(indices);
|
||||||
|
result.put("indices", indices);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result.containsKey("indices")) {
|
||||||
|
// Non-indexed rendering: generate sequential indices
|
||||||
|
int count = mesh.getVertexCount();
|
||||||
|
int[] indices = new int[count];
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
indices[i] = i;
|
||||||
|
}
|
||||||
|
result.put("indices", indices);
|
||||||
|
result.put("nonIndexed", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, Object> extractSkeleton(Skeleton skeleton) {
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
|
||||||
|
Bone[] bones = skeleton.getBones();
|
||||||
|
List<Map<String, Object>> boneList = new ArrayList<>();
|
||||||
|
Map<String, Integer> nameToIndex = new HashMap<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < bones.length; i++) {
|
||||||
|
Bone bone = bones[i];
|
||||||
|
nameToIndex.put(bone.getName(), i);
|
||||||
|
|
||||||
|
Map<String, Object> bdata = new HashMap<>();
|
||||||
|
bdata.put("name", bone.getName());
|
||||||
|
bdata.put("id", i);
|
||||||
|
|
||||||
|
Vector3f pos = bone.getBindPosition();
|
||||||
|
bdata.put("position", new float[]{pos.x, pos.y, pos.z});
|
||||||
|
|
||||||
|
Quaternion rot = bone.getBindRotation();
|
||||||
|
bdata.put("rotation", new float[]{rot.getX(), rot.getY(), rot.getZ(), rot.getW()});
|
||||||
|
|
||||||
|
Vector3f scale = bone.getBindScale();
|
||||||
|
bdata.put("scale", new float[]{scale.x, scale.y, scale.z});
|
||||||
|
|
||||||
|
// Get children bone names
|
||||||
|
List<String> children = new ArrayList<>();
|
||||||
|
for (Bone child : bone.getChildren()) {
|
||||||
|
children.add(child.getName());
|
||||||
|
}
|
||||||
|
bdata.put("children", children);
|
||||||
|
|
||||||
|
boneList.add(bdata);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.put("bones", boneList);
|
||||||
|
|
||||||
|
// Roots
|
||||||
|
List<String> roots = new ArrayList<>();
|
||||||
|
Bone[] rootBones = skeleton.getRoots();
|
||||||
|
for (Bone root : rootBones) {
|
||||||
|
roots.add(root.getName());
|
||||||
|
}
|
||||||
|
result.put("roots", roots);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, Object> extractAnimation(Animation anim) {
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
result.put("name", anim.getName());
|
||||||
|
result.put("length", anim.getLength());
|
||||||
|
|
||||||
|
Track[] tracks = anim.getTracks();
|
||||||
|
List<Map<String, Object>> trackList = new ArrayList<>();
|
||||||
|
|
||||||
|
for (Track track : tracks) {
|
||||||
|
if (track instanceof BoneTrack) {
|
||||||
|
BoneTrack boneTrack = (BoneTrack) track;
|
||||||
|
Map<String, Object> trackData = new HashMap<>();
|
||||||
|
trackData.put("boneIndex", boneTrack.getTargetBoneIndex());
|
||||||
|
trackData.put("times", boneTrack.getTimes());
|
||||||
|
|
||||||
|
// Translations
|
||||||
|
Vector3f[] translations = boneTrack.getTranslations();
|
||||||
|
float[] transData = new float[translations.length * 3];
|
||||||
|
for (int i = 0; i < translations.length; i++) {
|
||||||
|
transData[i * 3] = translations[i].x;
|
||||||
|
transData[i * 3 + 1] = translations[i].y;
|
||||||
|
transData[i * 3 + 2] = translations[i].z;
|
||||||
|
}
|
||||||
|
trackData.put("translations", transData);
|
||||||
|
|
||||||
|
// Rotations
|
||||||
|
Quaternion[] rotations = boneTrack.getRotations();
|
||||||
|
float[] rotData = new float[rotations.length * 4];
|
||||||
|
for (int i = 0; i < rotations.length; i++) {
|
||||||
|
rotData[i * 4] = rotations[i].getX();
|
||||||
|
rotData[i * 4 + 1] = rotations[i].getY();
|
||||||
|
rotData[i * 4 + 2] = rotations[i].getZ();
|
||||||
|
rotData[i * 4 + 3] = rotations[i].getW();
|
||||||
|
}
|
||||||
|
trackData.put("rotations", rotData);
|
||||||
|
|
||||||
|
// Scales
|
||||||
|
Vector3f[] scales = boneTrack.getScales();
|
||||||
|
float[] scaleData = new float[scales.length * 3];
|
||||||
|
for (int i = 0; i < scales.length; i++) {
|
||||||
|
scaleData[i * 3] = scales[i].x;
|
||||||
|
scaleData[i * 3 + 1] = scales[i].y;
|
||||||
|
scaleData[i * 3 + 2] = scales[i].z;
|
||||||
|
}
|
||||||
|
trackData.put("scales", scaleData);
|
||||||
|
|
||||||
|
trackList.add(trackData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.put("tracks", trackList);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void writeJson(File file, Object data) throws IOException {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
writeValue(sb, data);
|
||||||
|
FileWriter writer = new FileWriter(file);
|
||||||
|
writer.write(sb.toString());
|
||||||
|
writer.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void writeValue(StringBuilder sb, Object value) {
|
||||||
|
if (value == null) {
|
||||||
|
sb.append("null");
|
||||||
|
} else if (value instanceof String) {
|
||||||
|
sb.append('"').append(escapeString((String) value)).append('"');
|
||||||
|
} else if (value instanceof Integer || value instanceof Long ||
|
||||||
|
value instanceof Float || value instanceof Double ||
|
||||||
|
value instanceof Boolean) {
|
||||||
|
sb.append(value);
|
||||||
|
} else if (value instanceof float[]) {
|
||||||
|
sb.append('[');
|
||||||
|
float[] arr = (float[]) value;
|
||||||
|
for (int i = 0; i < arr.length; i++) {
|
||||||
|
if (i > 0) sb.append(',');
|
||||||
|
sb.append(arr[i]);
|
||||||
|
}
|
||||||
|
sb.append(']');
|
||||||
|
} else if (value instanceof int[]) {
|
||||||
|
sb.append('[');
|
||||||
|
int[] arr = (int[]) value;
|
||||||
|
for (int i = 0; i < arr.length; i++) {
|
||||||
|
if (i > 0) sb.append(',');
|
||||||
|
sb.append(arr[i]);
|
||||||
|
}
|
||||||
|
sb.append(']');
|
||||||
|
} else if (value instanceof List) {
|
||||||
|
sb.append('[');
|
||||||
|
List<?> list = (List<?>) value;
|
||||||
|
for (int i = 0; i < list.size(); i++) {
|
||||||
|
if (i > 0) sb.append(',');
|
||||||
|
writeValue(sb, list.get(i));
|
||||||
|
}
|
||||||
|
sb.append(']');
|
||||||
|
} else if (value instanceof Map) {
|
||||||
|
sb.append('{');
|
||||||
|
Map<?, ?> map = (Map<?, ?>) value;
|
||||||
|
boolean first = true;
|
||||||
|
for (Map.Entry<?, ?> entry : map.entrySet()) {
|
||||||
|
if (!first) sb.append(',');
|
||||||
|
sb.append('"').append(escapeString(entry.getKey().toString())).append('"').append(':');
|
||||||
|
writeValue(sb, entry.getValue());
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
sb.append('}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String escapeString(String s) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (char c : s.toCharArray()) {
|
||||||
|
switch (c) {
|
||||||
|
case '"': sb.append("\\\""); break;
|
||||||
|
case '\\': sb.append("\\\\"); break;
|
||||||
|
case '\n': sb.append("\\n"); break;
|
||||||
|
case '\r': sb.append("\\r"); break;
|
||||||
|
case '\t': sb.append("\\t"); break;
|
||||||
|
default:
|
||||||
|
if (c < 32) {
|
||||||
|
sb.append(String.format("\\u%04x", (int) c));
|
||||||
|
} else {
|
||||||
|
sb.append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,462 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Convert JME3-extracted JSON (from J3OConverter.java) to glTF 2.0 GLB with animations."""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import struct
|
||||||
|
import math
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def quat_mult(qa, qb):
|
||||||
|
ax, ay, az, aw = qa
|
||||||
|
bx, by, bz, bw = qb
|
||||||
|
return (
|
||||||
|
aw*bx + ax*bw + ay*bz - az*by,
|
||||||
|
aw*by - ax*bz + ay*bw + az*bx,
|
||||||
|
aw*bz + ax*by - ay*bx + az*bw,
|
||||||
|
aw*bw - ax*bx - ay*by - az*bz,
|
||||||
|
)
|
||||||
|
|
||||||
|
def pad4(data):
|
||||||
|
while len(data) % 4:
|
||||||
|
data += b'\x00'
|
||||||
|
return data
|
||||||
|
|
||||||
|
def mat4_mult(a, b):
|
||||||
|
r = [0]*16
|
||||||
|
for i in range(4):
|
||||||
|
for j in range(4):
|
||||||
|
for k in range(4):
|
||||||
|
r[j*4 + i] += a[k*4 + i] * b[j*4 + k]
|
||||||
|
return r
|
||||||
|
|
||||||
|
def mat4_from_trs(t, r, s):
|
||||||
|
x, y, z, w = r
|
||||||
|
xx, yy, zz = x*x, y*y, z*z
|
||||||
|
xy, xz, yz = x*y, x*z, y*z
|
||||||
|
wx, wy, wz = w*x, w*y, w*z
|
||||||
|
return [
|
||||||
|
(1-2*(yy+zz))*s[0], (2*(xy+wz))*s[0], (2*(xz-wy))*s[0], 0,
|
||||||
|
(2*(xy-wz))*s[1], (1-2*(xx+zz))*s[1], (2*(yz+wx))*s[1], 0,
|
||||||
|
(2*(xz+wy))*s[2], (2*(yz-wx))*s[2], (1-2*(xx+yy))*s[2], 0,
|
||||||
|
t[0], t[1], t[2], 1,
|
||||||
|
]
|
||||||
|
|
||||||
|
def invert_4x4_row(m):
|
||||||
|
a,b,c,d, e,f,g,h, i,j,k,l, m_,n,o,p = m
|
||||||
|
det = (a*(f*k*p+g*l*n+h*j*o-h*k*n-f*l*o-g*j*p)
|
||||||
|
- b*(e*k*p+g*l*m_+h*i*o-h*k*m_-e*l*o-g*i*p)
|
||||||
|
+ c*(e*j*p+f*l*m_+h*i*n-h*j*m_-e*l*n-f*i*p)
|
||||||
|
- d*(e*j*o+f*k*m_+g*i*n-g*j*m_-e*k*n-f*i*o))
|
||||||
|
if abs(det) < 1e-12:
|
||||||
|
return [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]
|
||||||
|
idet = 1.0/det
|
||||||
|
return [
|
||||||
|
(f*k*p+g*l*n+h*j*o-h*k*n-f*l*o-g*j*p)*idet,
|
||||||
|
-(b*k*p+c*l*n+d*j*o-d*k*n-b*l*o-c*j*p)*idet,
|
||||||
|
(b*g*p+c*h*n+d*f*o-d*g*n-b*h*o-c*f*p)*idet,
|
||||||
|
-(b*g*l+c*h*j+d*f*k-d*g*j-b*h*k-c*f*l)*idet,
|
||||||
|
-(e*k*p+g*l*m_+h*i*o-h*k*m_-e*l*o-g*i*p)*idet,
|
||||||
|
(a*k*p+c*l*m_+d*i*o-d*k*m_-a*l*o-c*i*p)*idet,
|
||||||
|
-(a*g*p+c*h*m_+d*e*o-d*g*m_-a*h*o-c*e*p)*idet,
|
||||||
|
(a*g*l+c*h*i+d*e*k-d*g*i-a*h*k-c*e*l)*idet,
|
||||||
|
(e*j*p+f*l*m_+h*i*n-h*j*m_-e*l*n-f*i*p)*idet,
|
||||||
|
-(a*j*p+b*l*m_+d*i*n-d*j*m_-a*l*n-b*i*p)*idet,
|
||||||
|
(a*f*p+b*h*m_+d*e*n-d*f*m_-a*h*n-b*e*p)*idet,
|
||||||
|
-(a*f*l+b*h*i+d*e*j-d*f*i-a*h*j-b*e*l)*idet,
|
||||||
|
-(e*j*o+f*k*m_+g*i*n-g*j*m_-e*k*n-f*i*o)*idet,
|
||||||
|
(a*j*o+b*k*m_+c*i*n-c*j*m_-a*k*n-b*i*o)*idet,
|
||||||
|
-(a*f*o+b*g*m_+c*e*n-c*f*m_-a*g*n-b*e*o)*idet,
|
||||||
|
(a*f*k+b*g*i+c*e*j-c*f*i-a*g*j-b*e*k)*idet,
|
||||||
|
]
|
||||||
|
|
||||||
|
def convert(json_path, output_path):
|
||||||
|
with open(json_path) as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
geos = data.get('geometries', [])
|
||||||
|
skels = data.get('skeletons', [])
|
||||||
|
anims = data.get('animations', {})
|
||||||
|
|
||||||
|
if not geos:
|
||||||
|
print(f' No geometry in {json_path}')
|
||||||
|
return
|
||||||
|
|
||||||
|
geo = geos[0]
|
||||||
|
positions = geo['positions']
|
||||||
|
normals = geo.get('normals', [])
|
||||||
|
indices = geo.get('indices', [])
|
||||||
|
texcoords = geo.get('texcoords', [])
|
||||||
|
vertex_count = geo.get('vertexCount', len(positions) // 3)
|
||||||
|
|
||||||
|
bone_weights = geo.get('boneWeights', [])
|
||||||
|
bone_indices = geo.get('boneIndices', [])
|
||||||
|
|
||||||
|
skel = skels[0] if skels else None
|
||||||
|
|
||||||
|
nodes = []
|
||||||
|
skin = None
|
||||||
|
animations_out = []
|
||||||
|
|
||||||
|
# ── Skeleton → glTF nodes ──────────────────────────────────────────────
|
||||||
|
if skel:
|
||||||
|
bones = skel['bones']
|
||||||
|
roots = skel['roots']
|
||||||
|
name_to_bone = {b['name']: b for b in bones}
|
||||||
|
name_to_idx = {}
|
||||||
|
|
||||||
|
# IMPORTANT: Keep the ORIGINAL bone list order (from JME3 skeleton.getBone(i)).
|
||||||
|
# BoneTrack.getTargetBoneIndex() and the mesh JOINTS_0 data reference
|
||||||
|
# this exact order.
|
||||||
|
order = [b['name'] for b in bones]
|
||||||
|
for i, name in enumerate(order):
|
||||||
|
name_to_idx[name] = i
|
||||||
|
b = name_to_bone[name]
|
||||||
|
nodes.append({
|
||||||
|
'name': name,
|
||||||
|
'translation': b['position'],
|
||||||
|
'rotation': b['rotation'], # x,y,z,w
|
||||||
|
'scale': b['scale'],
|
||||||
|
})
|
||||||
|
|
||||||
|
# Hierarchy (by name)
|
||||||
|
for name in order:
|
||||||
|
b = name_to_bone[name]
|
||||||
|
for child_name in b['children']:
|
||||||
|
if child_name in name_to_idx and name in name_to_idx:
|
||||||
|
pi = name_to_idx[name]
|
||||||
|
ci = name_to_idx[child_name]
|
||||||
|
nodes[pi].setdefault('children', []).append(ci)
|
||||||
|
|
||||||
|
# Inverse bind matrices
|
||||||
|
def world_transform(name, cache={}):
|
||||||
|
if name in cache:
|
||||||
|
return cache[name]
|
||||||
|
b = name_to_bone[name]
|
||||||
|
local = mat4_from_trs(b['position'], b['rotation'], b['scale'])
|
||||||
|
# Find parent
|
||||||
|
parent = None
|
||||||
|
for pn, pb in name_to_bone.items():
|
||||||
|
if name in pb['children']:
|
||||||
|
parent = pn
|
||||||
|
break
|
||||||
|
if parent and parent in name_to_bone:
|
||||||
|
w = mat4_mult(world_transform(parent, cache), local)
|
||||||
|
else:
|
||||||
|
w = local
|
||||||
|
cache[name] = w
|
||||||
|
return w
|
||||||
|
|
||||||
|
ibms = []
|
||||||
|
for name in order:
|
||||||
|
w = world_transform(name)
|
||||||
|
# Convert col-major to row-major, invert, back to col-major
|
||||||
|
row = [w[i + j*4] for j in range(4) for i in range(4)]
|
||||||
|
inv_row = invert_4x4_row(row)
|
||||||
|
inv_col = [inv_row[j + i*4] for i in range(4) for j in range(4)]
|
||||||
|
ibms.extend(inv_col)
|
||||||
|
|
||||||
|
# Joint indices (identity: bone list order == glTF node order)
|
||||||
|
joints = list(range(len(order)))
|
||||||
|
skin = {'joints': joints}
|
||||||
|
|
||||||
|
# Mesh node with skin
|
||||||
|
mesh_node_idx = len(nodes)
|
||||||
|
nodes.append({'name': 'mesh', 'mesh': 0, 'skin': 0})
|
||||||
|
|
||||||
|
# Skeleton root node
|
||||||
|
root_node_idx = len(nodes)
|
||||||
|
root_children = [name_to_idx[r] for r in roots if r in name_to_idx]
|
||||||
|
root_children.append(mesh_node_idx)
|
||||||
|
nodes.append({'name': 'skeleton_root', 'children': root_children})
|
||||||
|
|
||||||
|
# Scene root
|
||||||
|
scene_node_idx = len(nodes)
|
||||||
|
nodes.append({'name': 'scene_root', 'children': [root_node_idx]})
|
||||||
|
|
||||||
|
# ── Animations ──────────────────────────────────────────────────────
|
||||||
|
anim_bin = bytearray()
|
||||||
|
anim_buf_views = []
|
||||||
|
anim_accessors = []
|
||||||
|
|
||||||
|
for anim_name, anim in anims.items():
|
||||||
|
tracks = anim.get('tracks', [])
|
||||||
|
if not tracks:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Collect all keyframe times
|
||||||
|
all_times = set()
|
||||||
|
for t in tracks:
|
||||||
|
for tm in t.get('times', []):
|
||||||
|
all_times.add(round(tm, 6))
|
||||||
|
sorted_times = sorted(all_times)
|
||||||
|
if len(sorted_times) < 2:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Per-animation sampler/channel lists
|
||||||
|
anim_samplers_this = []
|
||||||
|
anim_channels_this = []
|
||||||
|
|
||||||
|
# Time buffer
|
||||||
|
time_bytes = pad4(struct.pack(f'<{len(sorted_times)}f', *sorted_times))
|
||||||
|
time_off = len(anim_bin)
|
||||||
|
anim_bin.extend(time_bytes)
|
||||||
|
tv_idx = len(anim_buf_views)
|
||||||
|
anim_buf_views.append({'byteOffset': time_off, 'byteLength': len(time_bytes)})
|
||||||
|
t_acc = len(anim_accessors)
|
||||||
|
anim_accessors.append({'type': 'SCALAR', 'count': len(sorted_times),
|
||||||
|
'min': [sorted_times[0]], 'max': [sorted_times[-1]]})
|
||||||
|
|
||||||
|
for track in tracks:
|
||||||
|
bone_idx = track.get('boneIndex', 0)
|
||||||
|
if bone_idx >= len(order):
|
||||||
|
continue
|
||||||
|
node_idx = name_to_idx.get(order[bone_idx], None)
|
||||||
|
if node_idx is None:
|
||||||
|
node_idx = bone_idx
|
||||||
|
|
||||||
|
# Get bind pose for this bone (JME3 tracks are relative to bind)
|
||||||
|
bone_name = order[bone_idx]
|
||||||
|
bind = name_to_bone[bone_name]
|
||||||
|
bind_pos = bind['position']
|
||||||
|
bind_rot = bind['rotation']
|
||||||
|
bind_scale = bind['scale']
|
||||||
|
|
||||||
|
kf_times = list(track.get('times', []))
|
||||||
|
kf_trans = list(track.get('translations', []))
|
||||||
|
kf_rot = list(track.get('rotations', []))
|
||||||
|
kf_scale = list(track.get('scales', []))
|
||||||
|
|
||||||
|
# Combine with bind pose: final = bind * relative
|
||||||
|
# rotation: bindRot * trackRot
|
||||||
|
combined_rot = []
|
||||||
|
for i in range(0, len(kf_rot), 4):
|
||||||
|
q = quat_mult(bind_rot, kf_rot[i:i+4])
|
||||||
|
combined_rot.extend(q)
|
||||||
|
kf_rot = combined_rot
|
||||||
|
|
||||||
|
# translation: bindPos + trackTrans
|
||||||
|
combined_trans = []
|
||||||
|
for i in range(0, len(kf_trans), 3):
|
||||||
|
combined_trans.extend([
|
||||||
|
bind_pos[0] + kf_trans[i],
|
||||||
|
bind_pos[1] + kf_trans[i+1],
|
||||||
|
bind_pos[2] + kf_trans[i+2],
|
||||||
|
])
|
||||||
|
kf_trans = combined_trans
|
||||||
|
|
||||||
|
# scale: bindScale * trackScale
|
||||||
|
combined_scale = []
|
||||||
|
for i in range(0, len(kf_scale), 3):
|
||||||
|
combined_scale.extend([
|
||||||
|
bind_scale[0] * kf_scale[i],
|
||||||
|
bind_scale[1] * kf_scale[i+1],
|
||||||
|
bind_scale[2] * kf_scale[i+2],
|
||||||
|
])
|
||||||
|
kf_scale = combined_scale
|
||||||
|
|
||||||
|
def lerp_to(times, values, val_size, targets):
|
||||||
|
if not values:
|
||||||
|
return []
|
||||||
|
result = []
|
||||||
|
n_kf = len(times)
|
||||||
|
for qt in targets:
|
||||||
|
if qt <= times[0]:
|
||||||
|
result.extend(values[0:val_size])
|
||||||
|
elif qt >= times[-1]:
|
||||||
|
result.extend(values[(n_kf-1)*val_size:(n_kf-1)*val_size+val_size])
|
||||||
|
else:
|
||||||
|
for i in range(n_kf - 1):
|
||||||
|
if times[i] <= qt <= times[i+1]:
|
||||||
|
a = (qt - times[i]) / (times[i+1] - times[i]) if times[i+1] != times[i] else 0
|
||||||
|
base_i = i * val_size
|
||||||
|
base_j = (i+1) * val_size
|
||||||
|
result.extend([
|
||||||
|
values[base_i + j] + a * (values[base_j + j] - values[base_i + j])
|
||||||
|
for j in range(val_size)
|
||||||
|
])
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
result.extend(values[(n_kf-1)*val_size:(n_kf-1)*val_size+val_size])
|
||||||
|
return result
|
||||||
|
|
||||||
|
trans_interp = lerp_to(kf_times, kf_trans, 3, sorted_times) if kf_trans else []
|
||||||
|
rot_interp = lerp_to(kf_times, kf_rot, 4, sorted_times) if kf_rot else []
|
||||||
|
scale_interp = lerp_to(kf_times, kf_scale, 3, sorted_times) if kf_scale else []
|
||||||
|
|
||||||
|
for vals, count, acc_type, path in [
|
||||||
|
(trans_interp, 3, 'VEC3', 'translation'),
|
||||||
|
(rot_interp, 4, 'VEC4', 'rotation'),
|
||||||
|
(scale_interp, 3, 'VEC3', 'scale'),
|
||||||
|
]:
|
||||||
|
if not vals:
|
||||||
|
continue
|
||||||
|
raw = pad4(struct.pack(f'<{len(vals)}f', *vals))
|
||||||
|
off2 = len(anim_bin)
|
||||||
|
anim_bin.extend(raw)
|
||||||
|
dv_idx = len(anim_buf_views)
|
||||||
|
anim_buf_views.append({'byteOffset': off2, 'byteLength': len(raw)})
|
||||||
|
acc_idx = len(anim_accessors)
|
||||||
|
anim_accessors.append({'type': acc_type, 'count': len(sorted_times)})
|
||||||
|
samp_idx = len(anim_samplers_this)
|
||||||
|
anim_samplers_this.append({'input': t_acc, 'output': acc_idx})
|
||||||
|
anim_channels_this.append({'sampler': samp_idx,
|
||||||
|
'target': {'node': node_idx, 'path': path}})
|
||||||
|
|
||||||
|
if anim_samplers_this:
|
||||||
|
animations_out.append({
|
||||||
|
'name': anim_name,
|
||||||
|
'samplers': anim_samplers_this,
|
||||||
|
'channels': anim_channels_this,
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
# No skeleton - simple mesh node
|
||||||
|
mesh_node_idx = len(nodes)
|
||||||
|
nodes.append({'name': 'mesh', 'mesh': 0})
|
||||||
|
scene_node_idx = len(nodes)
|
||||||
|
nodes.append({'name': 'scene_root', 'children': [mesh_node_idx]})
|
||||||
|
anim_bin = bytearray()
|
||||||
|
anim_buf_views = []
|
||||||
|
anim_accessors = []
|
||||||
|
anim_samplers = []
|
||||||
|
anim_channels = []
|
||||||
|
|
||||||
|
# ── Build binary buffer ─────────────────────────────────────────────────
|
||||||
|
pos_bytes = pad4(struct.pack(f'<{len(positions)}f', *positions))
|
||||||
|
norm_bytes = pad4(struct.pack(f'<{len(normals)}f', *normals))
|
||||||
|
idx_bytes = pad4(struct.pack(f'<{len(indices)}H', *indices))
|
||||||
|
uv_bytes = pad4(struct.pack(f'<{len(texcoords)}f', *texcoords)) if texcoords else b''
|
||||||
|
|
||||||
|
joint_bytes = b''
|
||||||
|
weight_bytes = b''
|
||||||
|
if skel and bone_weights:
|
||||||
|
# Joints: use existing bone_indices if available
|
||||||
|
joint_vals = [i & 0xFFFF for i in bone_indices] if bone_indices else [0] * (vertex_count * 4)
|
||||||
|
joint_bytes = pad4(struct.pack(f'<{len(joint_vals)}H', *joint_vals))
|
||||||
|
weight_bytes = pad4(struct.pack(f'<{len(bone_weights)}f', *bone_weights))
|
||||||
|
|
||||||
|
ibm_bytes = pad4(struct.pack(f'<{len(ibms)}f', *ibms)) if skel else b''
|
||||||
|
|
||||||
|
all_bin = pos_bytes + norm_bytes + idx_bytes + uv_bytes + joint_bytes + weight_bytes + ibm_bytes + bytes(anim_bin)
|
||||||
|
|
||||||
|
# ── Buffer views and accessors ──────────────────────────────────────────
|
||||||
|
off = 0
|
||||||
|
buffer_views = []
|
||||||
|
bv_pos = {'buffer': 0, 'byteOffset': off, 'byteLength': len(pos_bytes), 'target': 34962}; off += len(pos_bytes)
|
||||||
|
bv_nrm = {'buffer': 0, 'byteOffset': off, 'byteLength': len(norm_bytes), 'target': 34962}; off += len(norm_bytes)
|
||||||
|
bv_idx = {'buffer': 0, 'byteOffset': off, 'byteLength': len(idx_bytes), 'target': 34963}; off += len(idx_bytes)
|
||||||
|
buffer_views = [bv_pos, bv_nrm, bv_idx]
|
||||||
|
|
||||||
|
accessors = [
|
||||||
|
{'bufferView': 0, 'componentType': 5126, 'count': vertex_count, 'type': 'VEC3'},
|
||||||
|
{'bufferView': 1, 'componentType': 5126, 'count': vertex_count, 'type': 'VEC3'},
|
||||||
|
{'bufferView': 2, 'componentType': 5123, 'count': len(indices), 'type': 'SCALAR'},
|
||||||
|
]
|
||||||
|
|
||||||
|
attributes = {'POSITION': 0, 'NORMAL': 1}
|
||||||
|
|
||||||
|
if texcoords:
|
||||||
|
bv_uv = {'buffer': 0, 'byteOffset': off, 'byteLength': len(uv_bytes), 'target': 34962}; off += len(uv_bytes)
|
||||||
|
buffer_views.append(bv_uv)
|
||||||
|
accessors.append({'bufferView': 3, 'componentType': 5126, 'count': vertex_count, 'type': 'VEC2'})
|
||||||
|
attributes['TEXCOORD_0'] = 3
|
||||||
|
|
||||||
|
# Joint/weight accessor indices shift if UV present
|
||||||
|
jnt_acc = 4 if texcoords else 3
|
||||||
|
wgt_acc = 5 if texcoords else 4
|
||||||
|
|
||||||
|
if skel and bone_weights:
|
||||||
|
bv_jnt = {'buffer': 0, 'byteOffset': off, 'byteLength': len(joint_bytes), 'target': 34962}; off += len(joint_bytes)
|
||||||
|
bv_wgt = {'buffer': 0, 'byteOffset': off, 'byteLength': len(weight_bytes), 'target': 34962}; off += len(weight_bytes)
|
||||||
|
buffer_views.extend([bv_jnt, bv_wgt])
|
||||||
|
accessors.append({'bufferView': 3 if not texcoords else 4, 'componentType': 5123, 'count': vertex_count, 'type': 'VEC4'})
|
||||||
|
accessors.append({'bufferView': 4 if not texcoords else 5, 'componentType': 5126, 'count': vertex_count, 'type': 'VEC4'})
|
||||||
|
attributes['JOINTS_0'] = jnt_acc
|
||||||
|
attributes['WEIGHTS_0'] = wgt_acc
|
||||||
|
|
||||||
|
ibm_accessor_idx = None
|
||||||
|
if skel:
|
||||||
|
bv_ibm = {'buffer': 0, 'byteOffset': off, 'byteLength': len(ibm_bytes)}; off += len(ibm_bytes)
|
||||||
|
buffer_views.append(bv_ibm)
|
||||||
|
ibm_accessor_idx = len(accessors)
|
||||||
|
accessors.append({'bufferView': len(buffer_views)-1, 'componentType': 5126,
|
||||||
|
'count': len(skin['joints']), 'type': 'MAT4'})
|
||||||
|
skin['inverseBindMatrices'] = ibm_accessor_idx
|
||||||
|
|
||||||
|
anim_base = off
|
||||||
|
for bv in anim_buf_views:
|
||||||
|
buffer_views.append({'buffer': 0, 'byteOffset': anim_base + bv['byteOffset'], 'byteLength': bv['byteLength']})
|
||||||
|
|
||||||
|
acc_offset = len(accessors)
|
||||||
|
for acc in anim_accessors:
|
||||||
|
a = {'componentType': 5126, 'type': acc['type'], 'count': acc['count'],
|
||||||
|
'bufferView': len(buffer_views) - len(anim_accessors) + 0}
|
||||||
|
accessors.append(a)
|
||||||
|
|
||||||
|
# Fix animation accessor bufferView indices
|
||||||
|
for i, acc in enumerate(anim_accessors):
|
||||||
|
accessors[acc_offset + i]['bufferView'] = len(buffer_views) - len(anim_accessors) + i
|
||||||
|
|
||||||
|
# Fix sampler input/output indices (they reference anim_accessors relative)
|
||||||
|
for anim in animations_out:
|
||||||
|
for s in anim['samplers']:
|
||||||
|
s['input'] += acc_offset
|
||||||
|
s['output'] += acc_offset
|
||||||
|
|
||||||
|
# ── Assemble glTF JSON ──────────────────────────────────────────────────
|
||||||
|
meshes = [{'name': geo.get('name', 'mesh'), 'primitives': [{
|
||||||
|
'attributes': attributes,
|
||||||
|
'indices': 2,
|
||||||
|
'material': 0,
|
||||||
|
}]}]
|
||||||
|
|
||||||
|
gltf = {
|
||||||
|
'asset': {'version': '2.0', 'generator': 'jme3-json2gltf.py'},
|
||||||
|
'scene': 0,
|
||||||
|
'scenes': [{'nodes': [scene_node_idx]}],
|
||||||
|
'nodes': nodes,
|
||||||
|
'meshes': meshes,
|
||||||
|
'materials': [{'name': 'default', 'pbrMetallicRoughness': {
|
||||||
|
'baseColorFactor': [1, 1, 1, 1], 'metallicFactor': 0, 'roughnessFactor': 0.5}}],
|
||||||
|
'animations': animations_out,
|
||||||
|
'accessors': accessors,
|
||||||
|
'bufferViews': buffer_views,
|
||||||
|
'buffers': [{'byteLength': len(all_bin)}],
|
||||||
|
}
|
||||||
|
|
||||||
|
if skin:
|
||||||
|
gltf['skins'] = [skin]
|
||||||
|
|
||||||
|
gltf_json = json.dumps(gltf, separators=(',', ':')).encode('utf-8')
|
||||||
|
while len(gltf_json) % 4:
|
||||||
|
gltf_json += b' '
|
||||||
|
while len(all_bin) % 4:
|
||||||
|
all_bin += b'\x00'
|
||||||
|
|
||||||
|
header = struct.pack('<III', 0x46546C67, 2, 12 + 8 + len(gltf_json) + 8 + len(all_bin))
|
||||||
|
json_chunk = struct.pack('<II', len(gltf_json), 0x4E4F534A)
|
||||||
|
bin_chunk = struct.pack('<II', len(all_bin), 0x004E4942)
|
||||||
|
|
||||||
|
with open(output_path, 'wb') as f:
|
||||||
|
f.write(header + json_chunk + gltf_json + bin_chunk + all_bin)
|
||||||
|
|
||||||
|
print(f' -> {output_path} (verts={vertex_count}, bones={len(skin["joints"]) if skin else 0}, '
|
||||||
|
f'anims={len(animations_out)})')
|
||||||
|
|
||||||
|
def main():
|
||||||
|
in_dir = '/var/home/nico/Gamedev/Wackelpeter/web/public/models'
|
||||||
|
models = ['spider', 'staff', 'cross', 'helmet', 'wall1', 'tile1', 'tile2', 'blobknochen']
|
||||||
|
for m in models:
|
||||||
|
jp = os.path.join(in_dir, f'{m}.json')
|
||||||
|
gp = os.path.join(in_dir, f'{m}.glb')
|
||||||
|
if os.path.exists(jp):
|
||||||
|
print(f'Converting {m}...')
|
||||||
|
try:
|
||||||
|
convert(jp, gp)
|
||||||
|
except Exception as e:
|
||||||
|
print(f' ERROR: {e}')
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -0,0 +1,822 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Parse JMonkeyEngine .j3o binary files and extract mesh/skeleton/animation data.
|
||||||
|
|
||||||
|
Outputs glTF 2.0 .glb for meshes with skeletons, or OBJ-like JSON for static meshes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import struct
|
||||||
|
import math
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# ── Constants ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
NULL_OBJECT = -1
|
||||||
|
DEFAULT_OBJECT = -2
|
||||||
|
SIGNATURE = 0x4A4D4533
|
||||||
|
|
||||||
|
FIELD_TYPE = {
|
||||||
|
0: 'BYTE', 1: 'BYTE_1D', 2: 'BYTE_2D',
|
||||||
|
10: 'INT', 11: 'INT_1D', 12: 'INT_2D',
|
||||||
|
20: 'FLOAT', 21: 'FLOAT_1D', 22: 'FLOAT_2D',
|
||||||
|
30: 'DOUBLE', 31: 'DOUBLE_1D', 32: 'DOUBLE_2D',
|
||||||
|
40: 'LONG', 41: 'LONG_1D', 42: 'LONG_2D',
|
||||||
|
50: 'SHORT', 51: 'SHORT_1D', 52: 'SHORT_2D',
|
||||||
|
60: 'BOOLEAN', 61: 'BOOLEAN_1D', 62: 'BOOLEAN_2D',
|
||||||
|
70: 'STRING', 71: 'STRING_1D', 72: 'STRING_2D',
|
||||||
|
80: 'BITSET',
|
||||||
|
90: 'SAVABLE', 91: 'SAVABLE_1D', 92: 'SAVABLE_2D',
|
||||||
|
100: 'SAVABLE_ARRAYLIST', 101: 'SAVABLE_ARRAYLIST_1D', 102: 'SAVABLE_ARRAYLIST_2D',
|
||||||
|
105: 'SAVABLE_MAP', 106: 'STRING_SAVABLE_MAP', 107: 'INT_SAVABLE_MAP',
|
||||||
|
110: 'FLOATBUFFER_ARRAYLIST', 111: 'BYTEBUFFER_ARRAYLIST',
|
||||||
|
120: 'FLOATBUFFER', 121: 'INTBUFFER', 122: 'BYTEBUFFER', 123: 'SHORTBUFFER',
|
||||||
|
}
|
||||||
|
|
||||||
|
# VertexBuffer usage types (key for IntMap in Mesh.buffers)
|
||||||
|
VB_USAGE = {
|
||||||
|
'Position': 0, 'Normal': 1, 'TexCoord': 2, 'Color': 3,
|
||||||
|
'Tangent': 4, 'Binormal': 5,
|
||||||
|
'Size': 6, 'InterleavedData': 7, 'Misc': 8,
|
||||||
|
'BoneIndex': 9, 'BoneWeight': 10, 'HWBoneIndex': 11, 'HWBoneWeight': 12,
|
||||||
|
'BindPosePosition': 13, 'BindPoseNormal': 14, 'BindPoseTangent': 15,
|
||||||
|
}
|
||||||
|
|
||||||
|
VB_FORMAT = {'Float': 0, 'Short': 2, 'UnsignedShort': 4, 'UnsignedByte': 5, 'Byte': 6, 'Half': 7, 'Int': 8, 'UnsignedInt': 9}
|
||||||
|
|
||||||
|
|
||||||
|
# ── Binary reader ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
class J3OReader:
|
||||||
|
def __init__(self, data):
|
||||||
|
self.data = data
|
||||||
|
self.offset = 0
|
||||||
|
|
||||||
|
def tell(self):
|
||||||
|
return self.offset
|
||||||
|
|
||||||
|
def read_bytes(self, n):
|
||||||
|
r = self.data[self.offset:self.offset + n]
|
||||||
|
self.offset += n
|
||||||
|
return r
|
||||||
|
|
||||||
|
def read_be_int32(self):
|
||||||
|
return struct.unpack('>i', self.read_bytes(4))[0]
|
||||||
|
|
||||||
|
def read_be_uint32(self):
|
||||||
|
return struct.unpack('>I', self.read_bytes(4))[0]
|
||||||
|
|
||||||
|
def read_be_int16(self):
|
||||||
|
return struct.unpack('>h', self.read_bytes(2))[0]
|
||||||
|
|
||||||
|
def read_byte(self):
|
||||||
|
b = self.data[self.offset]
|
||||||
|
self.offset += 1
|
||||||
|
return b
|
||||||
|
|
||||||
|
def read_float(self):
|
||||||
|
i = struct.unpack('>i', self.read_bytes(4))[0]
|
||||||
|
return struct.unpack('>f', struct.pack('>i', i))[0]
|
||||||
|
|
||||||
|
def read_double(self):
|
||||||
|
i = struct.unpack('>q', self.read_bytes(8))[0]
|
||||||
|
return struct.unpack('>d', struct.pack('>q', i))[0]
|
||||||
|
|
||||||
|
def read_short(self):
|
||||||
|
return struct.unpack('>h', self.read_bytes(2))[0]
|
||||||
|
|
||||||
|
def read_bool(self):
|
||||||
|
return self.read_byte() != 0
|
||||||
|
|
||||||
|
def read_compressed_int(self):
|
||||||
|
first = self.read_byte()
|
||||||
|
if first == 0xFF:
|
||||||
|
return NULL_OBJECT
|
||||||
|
if first == 0xFE:
|
||||||
|
return DEFAULT_OBJECT
|
||||||
|
if first == 0x00:
|
||||||
|
return 0
|
||||||
|
length = first & 0xFF
|
||||||
|
raw = self.read_bytes(length)
|
||||||
|
# Right-align to 4 bytes
|
||||||
|
if length <= 4:
|
||||||
|
pad_len = 4 - length
|
||||||
|
value_bytes = b'\x00' * pad_len + raw
|
||||||
|
else:
|
||||||
|
# If longer than 4 bytes, take only the last 4
|
||||||
|
value_bytes = raw[length-4:]
|
||||||
|
try:
|
||||||
|
val = struct.unpack('>i', value_bytes)[0]
|
||||||
|
except struct.error:
|
||||||
|
return NULL_OBJECT
|
||||||
|
if val in (NULL_OBJECT, DEFAULT_OBJECT):
|
||||||
|
if length == 4:
|
||||||
|
self.offset -= 4
|
||||||
|
return val
|
||||||
|
|
||||||
|
def read_compressed_long(self):
|
||||||
|
first = self.read_byte()
|
||||||
|
if first == 0xFF:
|
||||||
|
return NULL_OBJECT
|
||||||
|
if first == 0xFE:
|
||||||
|
return DEFAULT_OBJECT
|
||||||
|
if first == 0x00:
|
||||||
|
return 0
|
||||||
|
length = first & 0xFF
|
||||||
|
raw = self.read_bytes(length)
|
||||||
|
pad = b'\x00' * (8 - length)
|
||||||
|
return struct.unpack('>q', pad + raw)[0]
|
||||||
|
|
||||||
|
def read_string(self):
|
||||||
|
length = self.read_compressed_int()
|
||||||
|
if length == NULL_OBJECT:
|
||||||
|
return None
|
||||||
|
return self.read_bytes(length).decode('utf-8', errors='replace')
|
||||||
|
|
||||||
|
|
||||||
|
# ── Field reading ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
class CapsuleReader:
|
||||||
|
"""Reads field data for a single capsule."""
|
||||||
|
def __init__(self, rdr: J3OReader, length):
|
||||||
|
self.rdr = rdr
|
||||||
|
self.end_offset = rdr.tell() + length
|
||||||
|
self.class_fields = None # set by BinaryClassObject
|
||||||
|
|
||||||
|
def read_field_value(self, field_type, expected_alias=None):
|
||||||
|
"""Read a value of the given type. Returns (value, field_alias)."""
|
||||||
|
if expected_alias is not None:
|
||||||
|
alias = self.rdr.read_byte()
|
||||||
|
else:
|
||||||
|
alias = 0
|
||||||
|
|
||||||
|
val = None
|
||||||
|
|
||||||
|
if field_type in (0,): # BYTE
|
||||||
|
val = self.rdr.read_byte()
|
||||||
|
|
||||||
|
elif field_type in (1,): # BYTE_1D
|
||||||
|
ln = self.rdr.read_compressed_int()
|
||||||
|
val = list(self.rdr.read_bytes(ln)) if ln > 0 else []
|
||||||
|
|
||||||
|
elif field_type in (2,): # BYTE_2D
|
||||||
|
outer = self.rdr.read_compressed_int()
|
||||||
|
val = []
|
||||||
|
for _ in range(outer):
|
||||||
|
ln = self.rdr.read_compressed_int()
|
||||||
|
val.append(list(self.rdr.read_bytes(ln)) if ln > 0 else [])
|
||||||
|
|
||||||
|
elif field_type in (10,): # INT
|
||||||
|
val = self.rdr.read_compressed_int()
|
||||||
|
|
||||||
|
elif field_type in (11,): # INT_1D
|
||||||
|
ln = self.rdr.read_compressed_int()
|
||||||
|
val = [self.rdr.read_compressed_int() for _ in range(ln)]
|
||||||
|
|
||||||
|
elif field_type in (12,): # INT_2D
|
||||||
|
outer = self.rdr.read_compressed_int()
|
||||||
|
val = [[self.rdr.read_compressed_int() for _ in range(self.rdr.read_compressed_int())] for _ in range(outer)]
|
||||||
|
|
||||||
|
elif field_type in (20,): # FLOAT
|
||||||
|
val = self.rdr.read_float()
|
||||||
|
|
||||||
|
elif field_type in (21,): # FLOAT_1D
|
||||||
|
ln = self.rdr.read_compressed_int()
|
||||||
|
val = [self.rdr.read_float() for _ in range(ln)]
|
||||||
|
|
||||||
|
elif field_type in (22,): # FLOAT_2D
|
||||||
|
outer = self.rdr.read_compressed_int()
|
||||||
|
val = [[self.rdr.read_float() for _ in range(self.rdr.read_compressed_int())] for _ in range(outer)]
|
||||||
|
|
||||||
|
elif field_type in (30,): # DOUBLE
|
||||||
|
val = self.rdr.read_double()
|
||||||
|
|
||||||
|
elif field_type in (40,): # LONG
|
||||||
|
val = self.rdr.read_compressed_long()
|
||||||
|
|
||||||
|
elif field_type in (50,): # SHORT
|
||||||
|
val = self.rdr.read_short()
|
||||||
|
|
||||||
|
elif field_type in (51,): # SHORT_1D
|
||||||
|
ln = self.rdr.read_compressed_int()
|
||||||
|
val = [self.rdr.read_short() for _ in range(ln)]
|
||||||
|
|
||||||
|
elif field_type in (60,): # BOOLEAN
|
||||||
|
val = self.rdr.read_bool()
|
||||||
|
|
||||||
|
elif field_type in (61,): # BOOLEAN_1D
|
||||||
|
ln = self.rdr.read_compressed_int()
|
||||||
|
val = [self.rdr.read_bool() for _ in range(ln)]
|
||||||
|
|
||||||
|
elif field_type in (70,): # STRING
|
||||||
|
val = self.rdr.read_string()
|
||||||
|
|
||||||
|
elif field_type in (71,): # STRING_1D
|
||||||
|
ln = self.rdr.read_compressed_int()
|
||||||
|
val = [self.rdr.read_string() for _ in range(ln)]
|
||||||
|
|
||||||
|
elif field_type in (80,): # BITSET
|
||||||
|
ln = self.rdr.read_compressed_int()
|
||||||
|
val = [self.rdr.read_bool() for _ in range(ln)]
|
||||||
|
|
||||||
|
elif field_type in (90,): # SAVABLE
|
||||||
|
val = self.rdr.read_compressed_int()
|
||||||
|
|
||||||
|
elif field_type in (91, 100): # SAVABLE_1D / SAVABLE_ARRAYLIST
|
||||||
|
ln = self.rdr.read_compressed_int()
|
||||||
|
val = [self.rdr.read_compressed_int() for _ in range(ln)]
|
||||||
|
|
||||||
|
elif field_type in (92, 101): # SAVABLE_2D
|
||||||
|
outer = self.rdr.read_compressed_int()
|
||||||
|
val = [[self.rdr.read_compressed_int() for _ in range(self.rdr.read_compressed_int())] for _ in range(outer)]
|
||||||
|
|
||||||
|
elif field_type in (105,): # SAVABLE_MAP
|
||||||
|
ln = self.rdr.read_compressed_int()
|
||||||
|
val = [[self.rdr.read_compressed_int(), self.rdr.read_compressed_int()] for _ in range(ln)]
|
||||||
|
|
||||||
|
elif field_type in (106,): # STRING_SAVABLE_MAP
|
||||||
|
ln = self.rdr.read_compressed_int()
|
||||||
|
keys = [self.rdr.read_string() for _ in range(ln)]
|
||||||
|
_ = self.rdr.read_compressed_int() # skip length
|
||||||
|
vals = [self.rdr.read_compressed_int() for _ in range(ln)]
|
||||||
|
val = dict(zip(keys, vals))
|
||||||
|
|
||||||
|
elif field_type in (107,): # INT_SAVABLE_MAP
|
||||||
|
ln = self.rdr.read_compressed_int()
|
||||||
|
keys = [self.rdr.read_compressed_int() for _ in range(ln)]
|
||||||
|
ln2 = self.rdr.read_compressed_int()
|
||||||
|
vals = [self.rdr.read_compressed_int() for _ in range(ln2)]
|
||||||
|
val = dict(zip(keys, vals))
|
||||||
|
|
||||||
|
elif field_type in (120, 121, 122, 123): # Buffer types
|
||||||
|
ln = self.rdr.read_compressed_int()
|
||||||
|
if ln > 0:
|
||||||
|
elem_size = {120: 4, 121: 4, 122: 1, 123: 2}[field_type]
|
||||||
|
raw = self.rdr.read_bytes(ln * elem_size)
|
||||||
|
val = raw
|
||||||
|
else:
|
||||||
|
val = b''
|
||||||
|
|
||||||
|
elif field_type in (110,): # FLOATBUFFER_ARRAYLIST
|
||||||
|
ln = self.rdr.read_compressed_int()
|
||||||
|
val = []
|
||||||
|
for _ in range(ln):
|
||||||
|
nb = self.rdr.read_compressed_int()
|
||||||
|
if nb > 0:
|
||||||
|
val.append(self.rdr.read_bytes(nb * 4))
|
||||||
|
else:
|
||||||
|
val.append(b'')
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Unknown type - skip ahead
|
||||||
|
val = None
|
||||||
|
|
||||||
|
return val, alias
|
||||||
|
|
||||||
|
def read_all_fields(self, bco):
|
||||||
|
"""Read all fields of a capsule using its class definition."""
|
||||||
|
fields_read = {}
|
||||||
|
while self.rdr.tell() < self.end_offset - 1:
|
||||||
|
try:
|
||||||
|
alias = self.rdr.read_byte()
|
||||||
|
except (IndexError, struct.error):
|
||||||
|
break
|
||||||
|
field = bco.alias_fields.get(alias)
|
||||||
|
if field is None:
|
||||||
|
# Unknown field alias - skip the rest
|
||||||
|
break
|
||||||
|
try:
|
||||||
|
val, _ = self.read_field_value(field['type'], expected_alias=None)
|
||||||
|
fields_read[field['name']] = val
|
||||||
|
except (IndexError, struct.error):
|
||||||
|
break
|
||||||
|
return fields_read
|
||||||
|
|
||||||
|
|
||||||
|
# ── Class table ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
class BinaryClassObject:
|
||||||
|
def __init__(self, alias, name, fields):
|
||||||
|
self.alias = alias
|
||||||
|
self.name = name
|
||||||
|
self.fields = fields
|
||||||
|
self.alias_fields = {f['alias']: f for f in fields}
|
||||||
|
|
||||||
|
|
||||||
|
def parse_j3o_header(filepath):
|
||||||
|
"""Parse J3O header, class table, and location table. Return loader function."""
|
||||||
|
with open(filepath, 'rb') as f:
|
||||||
|
data = f.read()
|
||||||
|
|
||||||
|
rdr = J3OReader(data)
|
||||||
|
|
||||||
|
# Header
|
||||||
|
sig = rdr.read_be_uint32()
|
||||||
|
if sig != SIGNATURE:
|
||||||
|
# Old format (version 0): no signature, first int is num_classes
|
||||||
|
rdr.offset = 0
|
||||||
|
version = 0
|
||||||
|
num_classes = rdr.read_be_int32()
|
||||||
|
else:
|
||||||
|
version = rdr.read_be_int32()
|
||||||
|
num_classes = rdr.read_be_int32()
|
||||||
|
|
||||||
|
alias_width = int(math.log(max(num_classes, 2), 256)) + 1
|
||||||
|
|
||||||
|
# Class table
|
||||||
|
classes = {}
|
||||||
|
for i in range(num_classes):
|
||||||
|
alias = rdr.read_bytes(alias_width)
|
||||||
|
|
||||||
|
if version >= 1:
|
||||||
|
hier_size = rdr.read_byte()
|
||||||
|
_ = [rdr.read_be_int32() for _ in range(hier_size)]
|
||||||
|
else:
|
||||||
|
_ = [0]
|
||||||
|
|
||||||
|
name_len = rdr.read_be_int32()
|
||||||
|
name = rdr.read_bytes(name_len).decode('ascii', errors='replace')
|
||||||
|
|
||||||
|
num_fields = rdr.read_be_int32()
|
||||||
|
fields = []
|
||||||
|
for _ in range(num_fields):
|
||||||
|
fa = rdr.read_byte()
|
||||||
|
ft = rdr.read_byte()
|
||||||
|
fn_len = rdr.read_be_int32()
|
||||||
|
fn = rdr.read_bytes(fn_len).decode('ascii', errors='replace')
|
||||||
|
fields.append({'alias': fa, 'type': ft, 'name': fn})
|
||||||
|
|
||||||
|
classes[alias] = BinaryClassObject(alias, name, fields)
|
||||||
|
|
||||||
|
# Location table
|
||||||
|
num_locs = rdr.read_be_int32()
|
||||||
|
location_table = {}
|
||||||
|
for _ in range(num_locs):
|
||||||
|
obj_id = rdr.read_be_int32()
|
||||||
|
loc = rdr.read_be_int32()
|
||||||
|
location_table[obj_id] = loc
|
||||||
|
|
||||||
|
# Root
|
||||||
|
_ = rdr.read_be_int32()
|
||||||
|
root_id = rdr.read_be_int32()
|
||||||
|
|
||||||
|
payload_start = rdr.tell()
|
||||||
|
payload = data[payload_start:]
|
||||||
|
|
||||||
|
return alias_width, classes, location_table, root_id, payload
|
||||||
|
|
||||||
|
|
||||||
|
def read_object(obj_id, alias_width, classes, location_table, payload, cache=None):
|
||||||
|
"""Read a single capsule and return its class name and field data."""
|
||||||
|
if cache is None:
|
||||||
|
cache = {}
|
||||||
|
|
||||||
|
if obj_id in cache:
|
||||||
|
return cache[obj_id]
|
||||||
|
|
||||||
|
if obj_id not in location_table:
|
||||||
|
cache[obj_id] = ('unknown', {})
|
||||||
|
return cache[obj_id]
|
||||||
|
|
||||||
|
loc = location_table[obj_id]
|
||||||
|
rdr = J3OReader(payload)
|
||||||
|
rdr.offset = loc
|
||||||
|
|
||||||
|
alias = rdr.read_bytes(alias_width)
|
||||||
|
bco = classes.get(alias)
|
||||||
|
|
||||||
|
data_len = rdr.read_be_int32()
|
||||||
|
|
||||||
|
if bco is None:
|
||||||
|
cache[obj_id] = ('unknown', {})
|
||||||
|
return cache[obj_id]
|
||||||
|
|
||||||
|
cap_rdr = CapsuleReader(rdr, data_len)
|
||||||
|
fields = cap_rdr.read_all_fields(bco)
|
||||||
|
|
||||||
|
result = (bco.name, fields)
|
||||||
|
cache[obj_id] = result
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_savable(obj_id, alias_width, classes, location_table, payload, cache):
|
||||||
|
"""Resolve a Savable reference, returning (class_name, fields)."""
|
||||||
|
if obj_id <= 0 or obj_id == NULL_OBJECT or obj_id == DEFAULT_OBJECT:
|
||||||
|
return None
|
||||||
|
return read_object(obj_id, alias_width, classes, location_table, payload, cache)
|
||||||
|
|
||||||
|
|
||||||
|
# ── Vertex buffer decoding ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def decode_floatbuffer(raw, count):
|
||||||
|
"""Decode LE float buffer. raw is bytes."""
|
||||||
|
if len(raw) != count * 4:
|
||||||
|
return [0.0] * count
|
||||||
|
return list(struct.unpack(f'<{count}f', raw))
|
||||||
|
|
||||||
|
def decode_shortbuffer(raw, count):
|
||||||
|
return list(struct.unpack(f'<{count}h', raw))
|
||||||
|
|
||||||
|
def decode_bytebuffer(raw, count):
|
||||||
|
return list(raw[:count])
|
||||||
|
|
||||||
|
def decode_intbuffer(raw, count):
|
||||||
|
return list(struct.unpack(f'<{count}i', raw))
|
||||||
|
|
||||||
|
|
||||||
|
# ── Mesh extraction ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def extract_mesh_geometry(obj_id, alias_width, classes, location_table, payload, cache):
|
||||||
|
obj = resolve_savable(obj_id, alias_width, classes, location_table, payload, cache)
|
||||||
|
if obj is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
cls_name, fields = obj
|
||||||
|
if 'Mesh' not in cls_name:
|
||||||
|
return None
|
||||||
|
|
||||||
|
vert_count = fields.get('vertCount', 0)
|
||||||
|
if vert_count == 0:
|
||||||
|
return None
|
||||||
|
|
||||||
|
buffers_map = fields.get('buffers', {})
|
||||||
|
element_count = fields.get('elementCount', 0)
|
||||||
|
|
||||||
|
positions = []
|
||||||
|
normals = []
|
||||||
|
texcoords = []
|
||||||
|
indices = []
|
||||||
|
bone_weights_raw = b''
|
||||||
|
bone_indices_raw = b''
|
||||||
|
bone_idx_bytes_per_elem = 2
|
||||||
|
weight_comps = 4
|
||||||
|
idx_comps = 4
|
||||||
|
|
||||||
|
for usage_code, vb_id in buffers_map.items():
|
||||||
|
vb_obj = resolve_savable(vb_id, alias_width, classes, location_table, payload, cache)
|
||||||
|
if vb_obj is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
_, vb_fields = vb_obj
|
||||||
|
comp = vb_fields.get('components', 0)
|
||||||
|
format_code = vb_fields.get('format', -1)
|
||||||
|
|
||||||
|
raw_data = None
|
||||||
|
is_float = False
|
||||||
|
is_short = False
|
||||||
|
is_byte = False
|
||||||
|
for dk, fl, sh, bt in [
|
||||||
|
('dataFloat', True, False, False),
|
||||||
|
('dataUnsignedShort', False, True, False),
|
||||||
|
('dataUnsignedByte', False, False, True),
|
||||||
|
('dataShort', False, True, False),
|
||||||
|
('dataByte', False, False, True),
|
||||||
|
('dataInt', False, False, False),
|
||||||
|
('data', False, False, False),
|
||||||
|
]:
|
||||||
|
if dk in vb_fields:
|
||||||
|
raw_data = vb_fields[dk]
|
||||||
|
is_float = fl
|
||||||
|
is_short = sh
|
||||||
|
is_byte = bt
|
||||||
|
break
|
||||||
|
|
||||||
|
if raw_data is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if usage_code == VB_USAGE['Position']:
|
||||||
|
elem_count = len(raw_data) // 4
|
||||||
|
positions = decode_floatbuffer(raw_data, elem_count)
|
||||||
|
actual_vert_count = elem_count // comp
|
||||||
|
elif usage_code == VB_USAGE['Normal']:
|
||||||
|
elem_count = len(raw_data) // 4
|
||||||
|
normals = decode_floatbuffer(raw_data, elem_count)
|
||||||
|
elif usage_code == VB_USAGE['TexCoord']:
|
||||||
|
elem_count = len(raw_data) // 4
|
||||||
|
texcoords = decode_floatbuffer(raw_data, elem_count)
|
||||||
|
elif usage_code == VB_USAGE['BoneWeight']: # 10
|
||||||
|
bone_weights_raw = raw_data
|
||||||
|
weight_comps = comp
|
||||||
|
elif usage_code == VB_USAGE['BoneIndex']: # 9
|
||||||
|
bone_indices_raw = raw_data
|
||||||
|
bone_idx_bytes_per_elem = 2 if is_short else 1
|
||||||
|
idx_comps = comp
|
||||||
|
elif usage_code == VB_USAGE['Misc']: # 8
|
||||||
|
if is_short:
|
||||||
|
indices = list(struct.unpack(f'<{len(raw_data)//2}h', raw_data))
|
||||||
|
elif len(raw_data) >= 2:
|
||||||
|
indices = list(struct.unpack(f'<{len(raw_data)//2}H', raw_data))
|
||||||
|
elif raw_data:
|
||||||
|
indices = list(raw_data)
|
||||||
|
|
||||||
|
if not positions:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Decode bone data now that we know actual_vert_count
|
||||||
|
actual_vert_count = len(positions) // 3 if positions else vert_count
|
||||||
|
|
||||||
|
bone_indices = []
|
||||||
|
if bone_indices_raw:
|
||||||
|
if bone_idx_bytes_per_elem == 2:
|
||||||
|
count = len(bone_indices_raw) // 2
|
||||||
|
total = min(count, actual_vert_count * idx_comps)
|
||||||
|
bone_indices = list(struct.unpack(f'<{total}H', bone_indices_raw[:total*2]))
|
||||||
|
else:
|
||||||
|
total = min(len(bone_indices_raw), actual_vert_count * idx_comps)
|
||||||
|
bone_indices = list(bone_indices_raw[:total])
|
||||||
|
|
||||||
|
bone_weights = []
|
||||||
|
if bone_weights_raw:
|
||||||
|
total = min(len(bone_weights_raw) // 4, actual_vert_count * weight_comps)
|
||||||
|
bone_weights = decode_floatbuffer(bone_weights_raw, total)
|
||||||
|
|
||||||
|
if not indices and element_count > 0:
|
||||||
|
indices = list(range(element_count))
|
||||||
|
|
||||||
|
return {
|
||||||
|
'vertexCount': actual_vert_count,
|
||||||
|
'positions': positions,
|
||||||
|
'normals': normals,
|
||||||
|
'texcoords': texcoords,
|
||||||
|
'indices': indices,
|
||||||
|
'boneWeights': bone_weights,
|
||||||
|
'boneIndices': bone_indices,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# ── Skeleton extraction ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def extract_skeleton(skeleton_id, alias_width, classes, location_table, payload, cache):
|
||||||
|
"""Extract skeleton data."""
|
||||||
|
obj = resolve_savable(skeleton_id, alias_width, classes, location_table, payload, cache)
|
||||||
|
if obj is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
cls_name, fields = obj
|
||||||
|
if 'Skeleton' not in cls_name:
|
||||||
|
return None
|
||||||
|
|
||||||
|
root_bones_ids = fields.get('rootBones', [])
|
||||||
|
bone_list_ids = fields.get('boneList', [])
|
||||||
|
|
||||||
|
bones = []
|
||||||
|
bid_to_idx = {}
|
||||||
|
|
||||||
|
for i, bid in enumerate(bone_list_ids):
|
||||||
|
bone_obj = resolve_savable(bid, alias_width, classes, location_table, payload, cache)
|
||||||
|
if bone_obj is None:
|
||||||
|
continue
|
||||||
|
_, bf = bone_obj
|
||||||
|
bones.append({
|
||||||
|
'id': i,
|
||||||
|
'name': bf.get('name', f'bone_{i}'),
|
||||||
|
'position': bf.get('bindPos', [0,0,0]) if 'bindPos' in bf else [0,0,0],
|
||||||
|
'rotation': bf.get('bindRot', [0,0,0,1]) if 'bindRot' in bf else [0,0,0,1],
|
||||||
|
'scale': bf.get('bindScale', [1,1,1]) if 'bindScale' in bf else [1,1,1],
|
||||||
|
})
|
||||||
|
bid_to_idx[bid] = i
|
||||||
|
|
||||||
|
# Hierarchy
|
||||||
|
hierarchy = {}
|
||||||
|
for bid in bone_list_ids:
|
||||||
|
bone_obj = resolve_savable(bid, alias_width, classes, location_table, payload, cache)
|
||||||
|
if bone_obj is None:
|
||||||
|
continue
|
||||||
|
_, bf = bone_obj
|
||||||
|
children_ids = bf.get('children', [])
|
||||||
|
parent_idx = bid_to_idx.get(bid)
|
||||||
|
for cid in children_ids:
|
||||||
|
child_idx = bid_to_idx.get(cid)
|
||||||
|
if child_idx is not None and parent_idx is not None:
|
||||||
|
hierarchy[bones[child_idx]['name']] = bones[parent_idx]['name']
|
||||||
|
|
||||||
|
roots = [bid_to_idx[rid] for rid in root_bones_ids if rid in bid_to_idx]
|
||||||
|
|
||||||
|
return {
|
||||||
|
'bones': bones,
|
||||||
|
'hierarchy': hierarchy,
|
||||||
|
'roots': roots,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# ── Animation extraction ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def extract_animations(anim_map, alias_width, classes, location_table, payload, cache):
|
||||||
|
"""Extract animation data from StringSavableMap of anim_name -> Animation."""
|
||||||
|
if anim_map is None:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
animations = {}
|
||||||
|
for anim_name, anim_id in anim_map.items():
|
||||||
|
anim_obj = resolve_savable(anim_id, alias_width, classes, location_table, payload, cache)
|
||||||
|
if anim_obj is None:
|
||||||
|
continue
|
||||||
|
_, af = anim_obj
|
||||||
|
tracks_ids = af.get('tracks', [])
|
||||||
|
anim_len = af.get('length', 1.0)
|
||||||
|
anim_name_str = af.get('name', anim_name) or anim_name
|
||||||
|
|
||||||
|
tracks = []
|
||||||
|
for tid in tracks_ids:
|
||||||
|
track_obj = resolve_savable(tid, alias_width, classes, location_table, payload, cache)
|
||||||
|
if track_obj is None:
|
||||||
|
continue
|
||||||
|
ct, tf = track_obj
|
||||||
|
bone_idx = tf.get('boneIndex', 0)
|
||||||
|
|
||||||
|
# times: float array
|
||||||
|
times = tf.get('times', [])
|
||||||
|
|
||||||
|
# translations: savable CompactVector3Array
|
||||||
|
trans_id = tf.get('translations', None)
|
||||||
|
translations = []
|
||||||
|
if trans_id is not None:
|
||||||
|
trans_obj = resolve_savable(trans_id, alias_width, classes, location_table, payload, cache)
|
||||||
|
if trans_obj:
|
||||||
|
_, trf = trans_obj
|
||||||
|
trans_data = trf.get('array', None)
|
||||||
|
if trans_data:
|
||||||
|
translations = list(struct.unpack(f'<{len(trans_data)//4}f', trans_data)) if trans_data else []
|
||||||
|
|
||||||
|
# rotations: savable CompactQuaternionArray
|
||||||
|
rot_id = tf.get('rotations', None)
|
||||||
|
rotations = []
|
||||||
|
if rot_id is not None:
|
||||||
|
rot_obj = resolve_savable(rot_id, alias_width, classes, location_table, payload, cache)
|
||||||
|
if rot_obj:
|
||||||
|
_, rf = rot_obj
|
||||||
|
rot_data = rf.get('array', None)
|
||||||
|
if rot_data:
|
||||||
|
rotations = list(struct.unpack(f'<{len(rot_data)//4}f', rot_data)) if rot_data else []
|
||||||
|
|
||||||
|
# scales: savable CompactVector3Array
|
||||||
|
scale_id = tf.get('scales', None)
|
||||||
|
scales = []
|
||||||
|
if scale_id is not None:
|
||||||
|
scale_obj = resolve_savable(scale_id, alias_width, classes, location_table, payload, cache)
|
||||||
|
if scale_obj:
|
||||||
|
_, sf = scale_obj
|
||||||
|
scale_data = sf.get('array', None)
|
||||||
|
if scale_data:
|
||||||
|
scales = list(struct.unpack(f'<{len(scale_data)//4}f', scale_data)) if scale_data else []
|
||||||
|
|
||||||
|
tracks.append({
|
||||||
|
'boneIndex': bone_idx,
|
||||||
|
'times': times,
|
||||||
|
'translations': translations,
|
||||||
|
'rotations': rotations,
|
||||||
|
'scales': scales,
|
||||||
|
})
|
||||||
|
|
||||||
|
animations[anim_name_str] = {
|
||||||
|
'length': anim_len,
|
||||||
|
'tracks': tracks,
|
||||||
|
}
|
||||||
|
|
||||||
|
return animations
|
||||||
|
|
||||||
|
|
||||||
|
# ── Main extraction ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def extract_j3o(filepath):
|
||||||
|
"""Extract mesh, skeleton, and animation data from a .j3o file."""
|
||||||
|
alias_width, classes, location_table, root_id, payload = parse_j3o_header(filepath)
|
||||||
|
cache = {}
|
||||||
|
result = {
|
||||||
|
'file': filepath,
|
||||||
|
'geometries': [],
|
||||||
|
'skeletons': [],
|
||||||
|
'animations': {},
|
||||||
|
}
|
||||||
|
|
||||||
|
# Walk object graph looking for mesh/skeleton/animation data
|
||||||
|
def walk_obj(obj_id, depth=0):
|
||||||
|
if depth > 50 or obj_id <= 0:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
obj = resolve_savable(obj_id, alias_width, classes, location_table, payload, cache)
|
||||||
|
except Exception:
|
||||||
|
return
|
||||||
|
if obj is None:
|
||||||
|
return
|
||||||
|
cls_name, fields = obj
|
||||||
|
|
||||||
|
# Check for Geometry / Mesh data
|
||||||
|
if 'Geometry' in cls_name:
|
||||||
|
mesh_id = fields.get('mesh', None)
|
||||||
|
if mesh_id and mesh_id > 0:
|
||||||
|
try:
|
||||||
|
geo = extract_mesh_geometry(mesh_id, alias_width, classes, location_table, payload, cache)
|
||||||
|
if geo:
|
||||||
|
result['geometries'].append({
|
||||||
|
'name': fields.get('name', 'unknown'),
|
||||||
|
'geometry': geo,
|
||||||
|
})
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Check for AnimControl
|
||||||
|
if 'AnimControl' in cls_name:
|
||||||
|
skeleton_id = fields.get('skeleton', None)
|
||||||
|
if skeleton_id and skeleton_id > 0:
|
||||||
|
skel = extract_skeleton(skeleton_id, alias_width, classes, location_table, payload, cache)
|
||||||
|
if skel:
|
||||||
|
result['skeletons'].append(skel)
|
||||||
|
|
||||||
|
anim_map = fields.get('animations', None)
|
||||||
|
if isinstance(anim_map, dict):
|
||||||
|
anims = extract_animations(anim_map, alias_width, classes, location_table, payload, cache)
|
||||||
|
result['animations'].update(anims)
|
||||||
|
|
||||||
|
# Check for SkeletonControl
|
||||||
|
if 'SkeletonControl' in cls_name:
|
||||||
|
skeleton_id = fields.get('skeleton', None)
|
||||||
|
if skeleton_id and skeleton_id > 0 and not result['skeletons']:
|
||||||
|
skel = extract_skeleton(skeleton_id, alias_width, classes, location_table, payload, cache)
|
||||||
|
if skel:
|
||||||
|
result['skeletons'].append(skel)
|
||||||
|
|
||||||
|
# Recurse into children and controls
|
||||||
|
for list_key in ['children', 'controlsList']:
|
||||||
|
items = fields.get(list_key, None)
|
||||||
|
if isinstance(items, list):
|
||||||
|
for item in items:
|
||||||
|
if isinstance(item, int) and item > 0:
|
||||||
|
walk_obj(item, depth + 1)
|
||||||
|
|
||||||
|
walk_obj(root_id)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
# ── Convert to glTF / JSON ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def j3o_to_gltf(j3o_data, output_path):
|
||||||
|
"""Convert extracted J3O data to glTF or OBJ-like JSON."""
|
||||||
|
geos = j3o_data['geometries']
|
||||||
|
skels = j3o_data['skeletons']
|
||||||
|
anims = j3o_data['animations']
|
||||||
|
|
||||||
|
if not geos:
|
||||||
|
print(f" No geometry found in {j3o_data['file']}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# For now, output as simple OBJ-like JSON (positions + indices)
|
||||||
|
for geo_entry in geos:
|
||||||
|
g = geo_entry['geometry']
|
||||||
|
name = geo_entry['name'] or os.path.basename(j3o_data['file']).replace('.j3o', '')
|
||||||
|
|
||||||
|
out = {
|
||||||
|
'name': name,
|
||||||
|
'vertexCount': g['vertexCount'],
|
||||||
|
'positions': g['positions'],
|
||||||
|
'normals': g['normals'],
|
||||||
|
'indices': g['indices'],
|
||||||
|
'hasBones': len(g['boneWeights']) > 0,
|
||||||
|
'boneWeights': g['boneWeights'],
|
||||||
|
'boneIndices': g['boneIndices'],
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add skeleton if exists
|
||||||
|
if skels:
|
||||||
|
out['skeleton'] = skels[0]
|
||||||
|
if anims:
|
||||||
|
out['animations'] = anims
|
||||||
|
|
||||||
|
out_path = os.path.join(output_path, f'{name}.json')
|
||||||
|
with open(out_path, 'w') as f:
|
||||||
|
json.dump(out, f, separators=(',', ':'))
|
||||||
|
|
||||||
|
print(f" -> {out_path} ({g['vertexCount']} verts, {len(g['indices'])//3} faces)")
|
||||||
|
|
||||||
|
|
||||||
|
# ── Main ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def main():
|
||||||
|
base = '/var/home/nico/Gamedev/Wackelpeter/extracted/Models'
|
||||||
|
out_dir = '/var/home/nico/Gamedev/Wackelpeter/web/public/models'
|
||||||
|
|
||||||
|
j3o_files = [
|
||||||
|
('spider', 'spider/spider.mesh.j3o'),
|
||||||
|
('staff', 'staff/staff.mesh.j3o'),
|
||||||
|
('cross', 'kross/kross.mesh.j3o'),
|
||||||
|
]
|
||||||
|
|
||||||
|
for name, path in j3o_files:
|
||||||
|
full = os.path.join(base, path)
|
||||||
|
if not os.path.exists(full):
|
||||||
|
print(f'Skipping {name}: not found')
|
||||||
|
continue
|
||||||
|
print(f'Extracting {name}...')
|
||||||
|
try:
|
||||||
|
data = extract_j3o(full)
|
||||||
|
j3o_to_gltf(data, out_dir)
|
||||||
|
print(f" Skeletons: {len(data['skeletons'])}, Animations: {len(data['animations'])}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f' ERROR: {e}')
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -0,0 +1,485 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Convert Ogre3D .mesh.xml + .skeleton.xml to glTF 2.0 (.glb) with all animations."""
|
||||||
|
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
import json
|
||||||
|
import struct
|
||||||
|
import math
|
||||||
|
import os
|
||||||
|
|
||||||
|
# ── Math ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def axis_angle_to_quat(ax, ay, az, angle):
|
||||||
|
s = math.sin(angle / 2)
|
||||||
|
return (ax * s, ay * s, az * s, math.cos(angle / 2))
|
||||||
|
|
||||||
|
def mat4_from_trs(tx, ty, tz, qx, qy, qz, qw, sx, sy, sz):
|
||||||
|
x, y, z, w = qx, qy, qz, qw
|
||||||
|
xx, yy, zz = x*x, y*y, z*z
|
||||||
|
xy, xz, yz = x*y, x*z, y*z
|
||||||
|
wx, wy, wz = w*x, w*y, w*z
|
||||||
|
return [
|
||||||
|
(1 - 2*(yy + zz))*sx, (2*(xy + wz))*sx, (2*(xz - wy))*sx, 0,
|
||||||
|
(2*(xy - wz))*sy, (1 - 2*(xx + zz))*sy, (2*(yz + wx))*sy, 0,
|
||||||
|
(2*(xz + wy))*sz, (2*(yz - wx))*sz, (1 - 2*(xx + yy))*sz, 0,
|
||||||
|
tx, ty, tz, 1,
|
||||||
|
]
|
||||||
|
|
||||||
|
def mat4_mult(a, b):
|
||||||
|
r = [0]*16
|
||||||
|
for i in range(4):
|
||||||
|
for j in range(4):
|
||||||
|
for k in range(4):
|
||||||
|
r[j*4 + i] += a[k*4 + i] * b[j*4 + k]
|
||||||
|
return r
|
||||||
|
|
||||||
|
def lerp_keyframes(kf_times, kf_vals, query_times):
|
||||||
|
"""Linearly interpolate keyframe values to query times."""
|
||||||
|
if len(kf_times) == 1:
|
||||||
|
v = kf_vals[0]
|
||||||
|
result = []
|
||||||
|
for _ in query_times:
|
||||||
|
result.extend(v)
|
||||||
|
return result
|
||||||
|
result = []
|
||||||
|
for qt in query_times:
|
||||||
|
if qt <= kf_times[0]:
|
||||||
|
result.extend(kf_vals[0])
|
||||||
|
elif qt >= kf_times[-1]:
|
||||||
|
result.extend(kf_vals[-1])
|
||||||
|
else:
|
||||||
|
for i in range(len(kf_times) - 1):
|
||||||
|
if kf_times[i] <= qt <= kf_times[i+1]:
|
||||||
|
a = (qt - kf_times[i]) / (kf_times[i+1] - kf_times[i])
|
||||||
|
v = [kf_vals[i][j] + a*(kf_vals[i+1][j] - kf_vals[i][j])
|
||||||
|
for j in range(len(kf_vals[0]))]
|
||||||
|
result.extend(v)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
result.extend(kf_vals[-1])
|
||||||
|
return result
|
||||||
|
|
||||||
|
def pad(data, alignment=4):
|
||||||
|
"""Pad binary data with null bytes to alignment (for BIN chunk)."""
|
||||||
|
while len(data) % alignment:
|
||||||
|
data += b'\x00'
|
||||||
|
return data
|
||||||
|
|
||||||
|
# ── Parsing ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def parse_skeleton(filepath):
|
||||||
|
tree = ET.parse(filepath)
|
||||||
|
root = tree.getroot()
|
||||||
|
bones = []
|
||||||
|
for b in root.find('bones').findall('bone'):
|
||||||
|
bid = int(b.get('id', '0'))
|
||||||
|
name = b.get('name', '')
|
||||||
|
pos = b.find('position')
|
||||||
|
px, py, pz = float(pos.get('x','0')), float(pos.get('y','0')), float(pos.get('z','0'))
|
||||||
|
rot = b.find('rotation')
|
||||||
|
angle = float(rot.get('angle','0'))
|
||||||
|
axis = rot.find('axis')
|
||||||
|
ax, ay, az = float(axis.get('x','1')), float(axis.get('y','0')), float(axis.get('z','0'))
|
||||||
|
bones.append({'id': bid, 'name': name, 'pos': (px, py, pz),
|
||||||
|
'rot_axis': (ax, ay, az), 'rot_angle': angle, 'scale': (1,1,1)})
|
||||||
|
hierarchy = {}
|
||||||
|
hier = root.find('bonehierarchy')
|
||||||
|
if hier is not None:
|
||||||
|
for bp in hier.findall('boneparent'):
|
||||||
|
hierarchy[bp.get('bone')] = bp.get('parent')
|
||||||
|
animations = {}
|
||||||
|
anims = root.find('animations')
|
||||||
|
if anims is not None:
|
||||||
|
for anim in anims.findall('animation'):
|
||||||
|
name = anim.get('name')
|
||||||
|
tracks = {}
|
||||||
|
for track in anim.find('tracks').findall('track'):
|
||||||
|
bn = track.get('bone')
|
||||||
|
kfs = []
|
||||||
|
for kf in track.find('keyframes').findall('keyframe'):
|
||||||
|
t = float(kf.get('time'))
|
||||||
|
tr = kf.find('translate')
|
||||||
|
tx, ty, tz = float(tr.get('x','0')), float(tr.get('y','0')), float(tr.get('z','0'))
|
||||||
|
ro = kf.find('rotate')
|
||||||
|
ang = float(ro.get('angle','0'))
|
||||||
|
ax_e = ro.find('axis')
|
||||||
|
ax, ay, az = float(ax_e.get('x','1')), float(ax_e.get('y','0')), float(ax_e.get('z','0'))
|
||||||
|
q = axis_angle_to_quat(ax, ay, az, ang)
|
||||||
|
sc = kf.find('scale')
|
||||||
|
sx = float(sc.get('x','1')) if sc is not None else 1.0
|
||||||
|
sy = float(sc.get('y','1')) if sc is not None else 1.0
|
||||||
|
sz = float(sc.get('z','1')) if sc is not None else 1.0
|
||||||
|
kfs.append((t, tx, ty, tz, q[0], q[1], q[2], q[3], sx, sy, sz))
|
||||||
|
tracks[bn] = kfs
|
||||||
|
animations[name] = tracks
|
||||||
|
return bones, hierarchy, animations
|
||||||
|
|
||||||
|
def parse_mesh(filepath):
|
||||||
|
tree = ET.parse(filepath)
|
||||||
|
root = tree.getroot()
|
||||||
|
verts = []
|
||||||
|
sg = root.find('sharedgeometry')
|
||||||
|
if sg is not None:
|
||||||
|
for vb in sg.findall('vertexbuffer'):
|
||||||
|
for v in vb.findall('vertex'):
|
||||||
|
pos = v.find('position')
|
||||||
|
norm = v.find('normal')
|
||||||
|
vt = {'x': float(pos.get('x','0')), 'y': float(pos.get('y','0')), 'z': float(pos.get('z','0')),
|
||||||
|
'nx': 0, 'ny': 0, 'nz': 1}
|
||||||
|
if norm is not None:
|
||||||
|
vt['nx'] = float(norm.get('x','0')); vt['ny'] = float(norm.get('y','0')); vt['nz'] = float(norm.get('z','0'))
|
||||||
|
verts.append(vt)
|
||||||
|
faces = []
|
||||||
|
for sm in root.findall('.//submesh'):
|
||||||
|
for fe in sm.findall('faces'):
|
||||||
|
for f in fe.findall('face'):
|
||||||
|
faces.append((int(f.get('v1','0')), int(f.get('v2','0')), int(f.get('v3','0'))))
|
||||||
|
bw = [{} for _ in range(len(verts))]
|
||||||
|
ba = root.find('boneassignments')
|
||||||
|
if ba is not None:
|
||||||
|
for a in ba.findall('vertexboneassignment'):
|
||||||
|
vi = int(a.get('vertexindex','0'))
|
||||||
|
bi = int(a.get('boneindex','0'))
|
||||||
|
w = float(a.get('weight','1.0'))
|
||||||
|
if vi < len(bw):
|
||||||
|
bw[vi][bi] = w
|
||||||
|
return verts, faces, bw
|
||||||
|
|
||||||
|
# ── glTF builder ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def build_gltf(mesh_path, skeleton_path, output_path):
|
||||||
|
verts, faces, bw = parse_mesh(mesh_path)
|
||||||
|
bones, hierarchy, animations = parse_skeleton(skeleton_path)
|
||||||
|
|
||||||
|
# Topological sort of bones
|
||||||
|
bone_by_name = {b['name']: b for b in bones}
|
||||||
|
bone_by_id = {b['id']: b for b in bones}
|
||||||
|
|
||||||
|
roots = [b for b in bones if b['name'] not in hierarchy]
|
||||||
|
order = []
|
||||||
|
visited = set()
|
||||||
|
def dfs(name):
|
||||||
|
if name in visited: return
|
||||||
|
visited.add(name)
|
||||||
|
order.append(name)
|
||||||
|
for child, parent in hierarchy.items():
|
||||||
|
if parent == name:
|
||||||
|
dfs(child)
|
||||||
|
for root in roots:
|
||||||
|
dfs(root['name'])
|
||||||
|
|
||||||
|
name_to_idx = {name: i for i, name in enumerate(order)}
|
||||||
|
|
||||||
|
# glTF nodes (bones only for now)
|
||||||
|
nodes = []
|
||||||
|
for name in order:
|
||||||
|
b = bone_by_name[name]
|
||||||
|
q = axis_angle_to_quat(*b['rot_axis'], b['rot_angle'])
|
||||||
|
nodes.append({
|
||||||
|
'name': name,
|
||||||
|
'translation': list(b['pos']),
|
||||||
|
'rotation': [q[0], q[1], q[2], q[3]],
|
||||||
|
'scale': list(b['scale']),
|
||||||
|
})
|
||||||
|
for child, parent in hierarchy.items():
|
||||||
|
if child in name_to_idx and parent in name_to_idx:
|
||||||
|
ci, pi = name_to_idx[child], name_to_idx[parent]
|
||||||
|
nodes[pi].setdefault('children', []).append(ci)
|
||||||
|
|
||||||
|
# Add a skeleton root node (identity matrix, all bones as children)
|
||||||
|
skeleton_root_idx = len(nodes)
|
||||||
|
nodes.append({'name': '__skeleton_root__', 'children': []})
|
||||||
|
# Only add bones that are NOT children of other bones in hierarchy
|
||||||
|
for i, name in enumerate(order):
|
||||||
|
is_child = any(i in nodes[idx].get('children', []) for idx in range(len(nodes)))
|
||||||
|
if not is_child:
|
||||||
|
nodes[skeleton_root_idx]['children'].append(i)
|
||||||
|
|
||||||
|
# Inverse bind matrices
|
||||||
|
def world_transform(name):
|
||||||
|
b = bone_by_name[name]
|
||||||
|
q = axis_angle_to_quat(*b['rot_axis'], b['rot_angle'])
|
||||||
|
local = mat4_from_trs(b['pos'][0], b['pos'][1], b['pos'][2],
|
||||||
|
q[0], q[1], q[2], q[3], 1, 1, 1)
|
||||||
|
parent = hierarchy.get(name)
|
||||||
|
if parent and parent in bone_by_name:
|
||||||
|
return mat4_mult(world_transform(parent), local)
|
||||||
|
return local
|
||||||
|
|
||||||
|
ibm_flat = []
|
||||||
|
for name in order:
|
||||||
|
w = world_transform(name)
|
||||||
|
# Invert 4x4 matrix
|
||||||
|
m = [w[i + j*4] for j in range(4) for i in range(4)] # transpose to row-major for inversion
|
||||||
|
inv = invert_4x4(m)
|
||||||
|
ibm_flat.extend(inv) # back in row-major? No - glTF uses column-major
|
||||||
|
|
||||||
|
# Actually glTF stores matrices column-major in the linear buffer
|
||||||
|
# The inverse bind matrix in column-major form: each 4 floats = 1 column
|
||||||
|
ibm_col_major = []
|
||||||
|
for name in order:
|
||||||
|
w = world_transform(name)
|
||||||
|
# w is column-major: [c0x, c0y, c0z, c0w, c1x, ..., c3w]
|
||||||
|
# Invert it
|
||||||
|
inv = invert_4x4_colmajor(w)
|
||||||
|
ibm_col_major.extend(inv)
|
||||||
|
|
||||||
|
# Joint indices for skin
|
||||||
|
skin_joints = [name_to_idx[name] for name in order]
|
||||||
|
|
||||||
|
# Vertex data
|
||||||
|
positions = []
|
||||||
|
nrm = []
|
||||||
|
joints0 = []
|
||||||
|
weights0 = []
|
||||||
|
for i, v in enumerate(verts):
|
||||||
|
positions.extend([v['x'], v['y'], v['z']])
|
||||||
|
nrm.extend([v['nx'], v['ny'], v['nz']])
|
||||||
|
wmap = bw[i] if i < len(bw) else {}
|
||||||
|
sw = sorted(wmap.items(), key=lambda x: x[1], reverse=True)[:4]
|
||||||
|
j = [0, 0, 0, 0]
|
||||||
|
wgt = [0.0, 0.0, 0.0, 0.0]
|
||||||
|
for k, (bid, weight) in enumerate(sw):
|
||||||
|
if bid in bone_by_id:
|
||||||
|
jname = bone_by_id[bid]['name']
|
||||||
|
j[k] = name_to_idx.get(jname, 0)
|
||||||
|
wgt[k] = weight
|
||||||
|
total = sum(wgt)
|
||||||
|
if total > 0:
|
||||||
|
wgt = [x / total for x in wgt]
|
||||||
|
joints0.extend(j)
|
||||||
|
weights0.extend(wgt)
|
||||||
|
|
||||||
|
indices = []
|
||||||
|
for f in faces:
|
||||||
|
indices.extend(list(f))
|
||||||
|
|
||||||
|
# Bounding box
|
||||||
|
all_x = [v['x'] for v in verts]
|
||||||
|
all_y = [v['y'] for v in verts]
|
||||||
|
all_z = [v['z'] for v in verts]
|
||||||
|
bbox_min = [min(all_x), min(all_y), min(all_z)]
|
||||||
|
bbox_max = [max(all_x), max(all_y), max(all_z)]
|
||||||
|
|
||||||
|
# Model node (has mesh and skin)
|
||||||
|
model_node_idx = len(nodes)
|
||||||
|
nodes.append({'name': os.path.basename(mesh_path).replace('.mesh.xml', '') + '_mesh',
|
||||||
|
'mesh': 0, 'skin': 0})
|
||||||
|
|
||||||
|
# Scene root
|
||||||
|
scene_node_idx = len(nodes)
|
||||||
|
nodes.append({'name': 'scene_root', 'children': [skeleton_root_idx, model_node_idx]})
|
||||||
|
|
||||||
|
# ── Build buffer ────────────────────────────────────────────────────────
|
||||||
|
pos_bytes = pad(struct.pack(f'<{len(positions)}f', *positions))
|
||||||
|
nrm_bytes = pad(struct.pack(f'<{len(nrm)}f', *nrm))
|
||||||
|
idx_bytes = pad(struct.pack(f'<{len(indices)}H', *indices))
|
||||||
|
jnt_bytes = pad(struct.pack(f'<{len(joints0)}H', *joints0))
|
||||||
|
wgt_bytes = pad(struct.pack(f'<{len(weights0)}f', *weights0))
|
||||||
|
ibm_bytes = pad(struct.pack(f'<{len(ibm_col_major)}f', *ibm_col_major))
|
||||||
|
|
||||||
|
# Base data end
|
||||||
|
base_end = len(pos_bytes) + len(nrm_bytes) + len(idx_bytes) + len(jnt_bytes) + len(wgt_bytes) + len(ibm_bytes)
|
||||||
|
|
||||||
|
# ── Animations ──────────────────────────────────────────────────────────
|
||||||
|
anim_data = bytearray()
|
||||||
|
gltf_animations = []
|
||||||
|
buf_views = []
|
||||||
|
accessors_list = []
|
||||||
|
|
||||||
|
# Fill static buffer views and accessors first
|
||||||
|
off = 0
|
||||||
|
bv_pos = {'buffer': 0, 'byteOffset': off, 'byteLength': len(pos_bytes), 'target': 34962}; off += len(pos_bytes)
|
||||||
|
bv_nrm = {'buffer': 0, 'byteOffset': off, 'byteLength': len(nrm_bytes), 'target': 34962}; off += len(nrm_bytes)
|
||||||
|
bv_idx = {'buffer': 0, 'byteOffset': off, 'byteLength': len(idx_bytes), 'target': 34963}; off += len(idx_bytes)
|
||||||
|
bv_jnt = {'buffer': 0, 'byteOffset': off, 'byteLength': len(jnt_bytes), 'target': 34962}; off += len(jnt_bytes)
|
||||||
|
bv_wgt = {'buffer': 0, 'byteOffset': off, 'byteLength': len(wgt_bytes), 'target': 34962}; off += len(wgt_bytes)
|
||||||
|
bv_ibm = {'buffer': 0, 'byteOffset': off, 'byteLength': len(ibm_bytes)}; off += len(ibm_bytes)
|
||||||
|
|
||||||
|
buffer_views = [bv_pos, bv_nrm, bv_idx, bv_jnt, bv_wgt, bv_ibm]
|
||||||
|
|
||||||
|
accessors = [
|
||||||
|
{'bufferView': 0, 'componentType': 5126, 'count': len(verts), 'type': 'VEC3',
|
||||||
|
'min': bbox_min, 'max': bbox_max},
|
||||||
|
{'bufferView': 1, 'componentType': 5126, 'count': len(verts), 'type': 'VEC3'},
|
||||||
|
{'bufferView': 2, 'componentType': 5123, 'count': len(indices), 'type': 'SCALAR'},
|
||||||
|
{'bufferView': 3, 'componentType': 5123, 'count': len(verts), 'type': 'VEC4'},
|
||||||
|
{'bufferView': 4, 'componentType': 5126, 'count': len(verts), 'type': 'VEC4'},
|
||||||
|
{'bufferView': 5, 'componentType': 5126, 'count': len(order), 'type': 'MAT4'},
|
||||||
|
]
|
||||||
|
|
||||||
|
anim_base = off # animations start here
|
||||||
|
|
||||||
|
for anim_name, tracks in animations.items():
|
||||||
|
all_times = set()
|
||||||
|
for bn, kfs in tracks.items():
|
||||||
|
for t, *_ in kfs:
|
||||||
|
all_times.add(t)
|
||||||
|
sorted_times = sorted(all_times)
|
||||||
|
if len(sorted_times) < 2:
|
||||||
|
continue
|
||||||
|
|
||||||
|
time_data = pad(struct.pack(f'<{len(sorted_times)}f', *sorted_times))
|
||||||
|
time_off = len(anim_data)
|
||||||
|
anim_data.extend(time_data)
|
||||||
|
|
||||||
|
# Buffer view for time
|
||||||
|
tv_idx = len(buffer_views)
|
||||||
|
buffer_views.append({'buffer': 0, 'byteOffset': anim_base + time_off, 'byteLength': len(time_data)})
|
||||||
|
|
||||||
|
# Accessor for time (shared per animation)
|
||||||
|
t_acc_idx = len(accessors)
|
||||||
|
accessors.append({'bufferView': tv_idx, 'componentType': 5126, 'count': len(sorted_times),
|
||||||
|
'type': 'SCALAR', 'min': [sorted_times[0]], 'max': [sorted_times[-1]]})
|
||||||
|
|
||||||
|
samplers = []
|
||||||
|
channels = []
|
||||||
|
|
||||||
|
for bone_name, keyframes in tracks.items():
|
||||||
|
if bone_name not in name_to_idx:
|
||||||
|
continue
|
||||||
|
node_idx = name_to_idx[bone_name]
|
||||||
|
|
||||||
|
kf_times = [t for t, *_ in keyframes]
|
||||||
|
kf_trans = [list(kf[1:4]) for kf in keyframes]
|
||||||
|
kf_rot = [list(kf[4:8]) for kf in keyframes]
|
||||||
|
kf_scale = [list(kf[8:11]) for kf in keyframes]
|
||||||
|
|
||||||
|
for data, acc_type, path in [
|
||||||
|
(lerp_keyframes(kf_times, kf_trans, sorted_times), 'VEC3', 'translation'),
|
||||||
|
(lerp_keyframes(kf_times, kf_rot, sorted_times), 'VEC4', 'rotation'),
|
||||||
|
(lerp_keyframes(kf_times, kf_scale, sorted_times), 'VEC3', 'scale'),
|
||||||
|
]:
|
||||||
|
raw = pad(struct.pack(f'<{len(data)}f', *data))
|
||||||
|
off2 = len(anim_data)
|
||||||
|
anim_data.extend(raw)
|
||||||
|
|
||||||
|
dv_idx = len(buffer_views)
|
||||||
|
buffer_views.append({'buffer': 0, 'byteOffset': anim_base + off2, 'byteLength': len(raw)})
|
||||||
|
|
||||||
|
acc_idx = len(accessors)
|
||||||
|
accessors.append({'bufferView': dv_idx, 'componentType': 5126,
|
||||||
|
'count': len(sorted_times), 'type': acc_type})
|
||||||
|
|
||||||
|
samp_idx = len(samplers)
|
||||||
|
samplers.append({'input': t_acc_idx, 'interpolation': 'LINEAR', 'output': acc_idx})
|
||||||
|
|
||||||
|
channels.append({'sampler': samp_idx, 'target': {'node': node_idx, 'path': path}})
|
||||||
|
|
||||||
|
if samplers and channels:
|
||||||
|
gltf_animations.append({'name': anim_name, 'samplers': samplers, 'channels': channels})
|
||||||
|
|
||||||
|
anim_bin = bytes(anim_data)
|
||||||
|
|
||||||
|
# Combine
|
||||||
|
all_bin = pos_bytes + nrm_bytes + idx_bytes + jnt_bytes + wgt_bytes + ibm_bytes + anim_bin
|
||||||
|
|
||||||
|
# ── Build JSON ──────────────────────────────────────────────────────────
|
||||||
|
meshes = [{'name': 'mesh', 'primitives': [{
|
||||||
|
'attributes': {'POSITION': 0, 'NORMAL': 1, 'JOINTS_0': 3, 'WEIGHTS_0': 4},
|
||||||
|
'indices': 2,
|
||||||
|
'material': 0,
|
||||||
|
}]}]
|
||||||
|
|
||||||
|
materials = [{
|
||||||
|
'name': 'default',
|
||||||
|
'pbrMetallicRoughness': {
|
||||||
|
'baseColorFactor': [1.0, 1.0, 1.0, 1.0],
|
||||||
|
'metallicFactor': 0.0,
|
||||||
|
'roughnessFactor': 0.5,
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
|
||||||
|
skin = [{'inverseBindMatrices': 5, 'joints': skin_joints, 'name': 'skin'}]
|
||||||
|
|
||||||
|
gltf = {
|
||||||
|
'asset': {'version': '2.0', 'generator': 'ogre2gltf.py'},
|
||||||
|
'scene': 0,
|
||||||
|
'scenes': [{'nodes': [scene_node_idx]}],
|
||||||
|
'nodes': nodes,
|
||||||
|
'meshes': meshes,
|
||||||
|
'materials': materials,
|
||||||
|
'skins': skin,
|
||||||
|
'animations': gltf_animations,
|
||||||
|
'accessors': accessors,
|
||||||
|
'bufferViews': buffer_views,
|
||||||
|
'buffers': [{'byteLength': len(all_bin)}],
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Write GLB ───────────────────────────────────────────────────────────
|
||||||
|
gltf_json = json.dumps(gltf, separators=(',', ':'), allow_nan=False).encode('utf-8')
|
||||||
|
# GLB spec: JSON chunk padded with spaces (0x20), BIN chunk padded with nulls (0x00)
|
||||||
|
while len(gltf_json) % 4:
|
||||||
|
gltf_json += b' '
|
||||||
|
while len(all_bin) % 4:
|
||||||
|
all_bin += b'\x00'
|
||||||
|
|
||||||
|
header = struct.pack('<III', 0x46546C67, 2, 12 + 8 + len(gltf_json) + 8 + len(all_bin))
|
||||||
|
json_chunk = struct.pack('<II', len(gltf_json), 0x4E4F534A)
|
||||||
|
bin_chunk = struct.pack('<II', len(all_bin), 0x004E4942)
|
||||||
|
|
||||||
|
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
||||||
|
with open(output_path, 'wb') as f:
|
||||||
|
f.write(header + json_chunk + gltf_json + bin_chunk + all_bin)
|
||||||
|
|
||||||
|
print(f' -> {output_path} ({len(verts)}v {len(faces)}f {len(bones)}b {len(gltf_animations)}a)')
|
||||||
|
|
||||||
|
# ── Matrix inversion ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def invert_4x4(m):
|
||||||
|
"""Invert 4x4 row-major matrix, return row-major."""
|
||||||
|
a, b, c, d, e, f, g, h, i, j, k, l, m_, n, o, p = m
|
||||||
|
det = (a * (f*k*p + g*l*n + h*j*o - h*k*n - f*l*o - g*j*p) -
|
||||||
|
b * (e*k*p + g*l*m_ + h*i*o - h*k*m_ - e*l*o - g*i*p) +
|
||||||
|
c * (e*j*p + f*l*m_ + h*i*n - h*j*m_ - e*l*n - f*i*p) -
|
||||||
|
d * (e*j*o + f*k*m_ + g*i*n - g*j*m_ - e*k*n - f*i*o))
|
||||||
|
if abs(det) < 1e-12:
|
||||||
|
return [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]
|
||||||
|
inv_det = 1.0 / det
|
||||||
|
return [
|
||||||
|
(f*k*p + g*l*n + h*j*o - h*k*n - f*l*o - g*j*p) * inv_det,
|
||||||
|
(b*k*p + c*l*n + d*j*o - d*k*n - b*l*o - c*j*p) * inv_det,
|
||||||
|
(b*g*p + c*h*n + d*f*o - d*g*n - b*h*o - c*f*p) * inv_det,
|
||||||
|
(b*g*l + c*h*j + d*f*k - d*g*j - b*h*k - c*f*l) * inv_det,
|
||||||
|
(e*k*p + g*l*m_ + h*i*o - h*k*m_ - e*l*o - g*i*p) * inv_det,
|
||||||
|
(a*k*p + c*l*m_ + d*i*o - d*k*m_ - a*l*o - c*i*p) * inv_det,
|
||||||
|
(a*g*p + c*h*m_ + d*e*o - d*g*m_ - a*h*o - c*e*p) * inv_det,
|
||||||
|
(a*g*l + c*h*i + d*e*k - d*g*i - a*h*k - c*e*l) * inv_det,
|
||||||
|
(e*j*p + f*l*m_ + h*i*n - h*j*m_ - e*l*n - f*i*p) * inv_det,
|
||||||
|
(a*j*p + b*l*m_ + d*i*n - d*j*m_ - a*l*n - b*i*p) * inv_det,
|
||||||
|
(a*f*p + b*h*m_ + d*e*n - d*f*m_ - a*h*n - b*e*p) * inv_det,
|
||||||
|
(a*f*l + b*h*i + d*e*j - d*f*i - a*h*j - b*e*l) * inv_det,
|
||||||
|
(e*j*o + f*k*m_ + g*i*n - g*j*m_ - e*k*n - f*i*o) * inv_det,
|
||||||
|
(a*j*o + b*k*m_ + c*i*n - c*j*m_ - a*k*n - b*i*o) * inv_det,
|
||||||
|
(a*f*o + b*g*m_ + c*e*n - c*f*m_ - a*g*n - b*e*o) * inv_det,
|
||||||
|
(a*f*k + b*g*i + c*e*j - c*f*i - a*g*j - b*e*k) * inv_det,
|
||||||
|
]
|
||||||
|
|
||||||
|
def invert_4x4_colmajor(m):
|
||||||
|
"""Invert 4x4 column-major matrix, return column-major."""
|
||||||
|
# Convert col-major to row-major, invert, convert back
|
||||||
|
rowm = [m[i + j*4] for j in range(4) for i in range(4)]
|
||||||
|
inv_rowm = invert_4x4(rowm)
|
||||||
|
return [inv_rowm[j + i*4] for i in range(4) for j in range(4)]
|
||||||
|
|
||||||
|
# ── Main ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def main():
|
||||||
|
base = '/var/home/nico/Gamedev/Wackelpeter/extracted/Models'
|
||||||
|
out = '/var/home/nico/Gamedev/Wackelpeter/web/public/models'
|
||||||
|
|
||||||
|
for name, mesh, skel in [
|
||||||
|
('blob', 'blob/Blob', 'blob/Blob'),
|
||||||
|
('sword', 'sword/sword', 'sword/sword'),
|
||||||
|
('shield', 'shield/shield', 'shield/shield'),
|
||||||
|
]:
|
||||||
|
mf = os.path.join(base, f'{mesh}.mesh.xml')
|
||||||
|
sf = os.path.join(base, f'{skel}.skeleton.xml')
|
||||||
|
if os.path.exists(mf) and os.path.exists(sf):
|
||||||
|
print(f'Converting {name}...')
|
||||||
|
build_gltf(mf, sf, os.path.join(out, f'{name}.glb'))
|
||||||
|
else:
|
||||||
|
print(f'Skipping {name} (missing XML)')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Wackelpeter</title>
|
||||||
|
<style>
|
||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
html, body { width: 100%; height: 100%; overflow: hidden; background: #000; }
|
||||||
|
canvas { display: block; }
|
||||||
|
#ui-overlay {
|
||||||
|
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
|
||||||
|
pointer-events: none; font-family: monospace; z-index: 10;
|
||||||
|
}
|
||||||
|
#health-bar-bg {
|
||||||
|
position: absolute; top: 16px; left: 16px; width: 250px; height: 20px;
|
||||||
|
background: rgba(0,0,0,0.6); border: 2px solid #555;
|
||||||
|
}
|
||||||
|
#health-bar {
|
||||||
|
position: absolute; top: 16px; left: 16px; width: 250px; height: 20px;
|
||||||
|
background: #cc0000;
|
||||||
|
transition: width 0.1s linear;
|
||||||
|
}
|
||||||
|
#health-text {
|
||||||
|
position: absolute; top: 16px; left: 16px; width: 250px; height: 20px;
|
||||||
|
text-align: center; line-height: 20px; color: #fff; font-size: 12px;
|
||||||
|
}
|
||||||
|
#wave-label {
|
||||||
|
position: absolute; top: 48px; left: 16px; color: #ffcc00; font-size: 18px;
|
||||||
|
font-family: monospace; text-shadow: 2px 2px 4px #000;
|
||||||
|
}
|
||||||
|
#kills-label {
|
||||||
|
position: absolute; top: 72px; left: 16px; color: #ffcc00; font-size: 16px;
|
||||||
|
font-family: monospace; text-shadow: 2px 2px 4px #000;
|
||||||
|
}
|
||||||
|
.cooldown-container {
|
||||||
|
position: absolute; left: 16px; width: 200px; height: 16px;
|
||||||
|
}
|
||||||
|
.cooldown-bg {
|
||||||
|
width: 100%; height: 100%; background: rgba(0,0,0,0.6); border: 1px solid #555;
|
||||||
|
}
|
||||||
|
.cooldown-fill {
|
||||||
|
height: 100%; background: #2266dd; transition: width 0.05s linear;
|
||||||
|
}
|
||||||
|
.cooldown-label {
|
||||||
|
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
|
||||||
|
text-align: left; line-height: 16px; color: #fff; font-size: 11px;
|
||||||
|
padding-left: 4px; font-family: monospace;
|
||||||
|
}
|
||||||
|
#fireball-cooldown { bottom: 72px; }
|
||||||
|
#sword-cooldown { bottom: 48px; }
|
||||||
|
#teleport-cooldown { bottom: 24px; }
|
||||||
|
#wave-bar-container {
|
||||||
|
position: absolute; top: 16px; right: 16px; width: 250px; height: 16px;
|
||||||
|
}
|
||||||
|
#wave-bar-bg {
|
||||||
|
width: 100%; height: 100%; background: rgba(0,0,0,0.6); border: 1px solid #555;
|
||||||
|
}
|
||||||
|
#wave-bar-fill {
|
||||||
|
height: 100%; background: #ccaa00; transition: width 0.1s linear;
|
||||||
|
}
|
||||||
|
#wave-bar-label {
|
||||||
|
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
|
||||||
|
text-align: center; line-height: 16px; color: #fff; font-size: 11px;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
#game-over {
|
||||||
|
position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
|
||||||
|
font-size: 100px; color: #fff; font-family: monospace; opacity: 0;
|
||||||
|
text-shadow: 4px 4px 8px #000;
|
||||||
|
transition: opacity 2s ease-in;
|
||||||
|
}
|
||||||
|
#instructions {
|
||||||
|
position: absolute; bottom: 16px; left: 50%; transform: translateX(-50%);
|
||||||
|
color: rgba(255,255,255,0.5); font-size: 11px; font-family: monospace;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="ui-overlay">
|
||||||
|
<div id="health-bar-bg"></div>
|
||||||
|
<div id="health-bar"></div>
|
||||||
|
<div id="health-text">100%</div>
|
||||||
|
<div id="wave-label">Wave 0</div>
|
||||||
|
<div id="kills-label">Kills: 0</div>
|
||||||
|
|
||||||
|
<div id="fireball-cooldown" class="cooldown-container">
|
||||||
|
<div class="cooldown-bg"></div>
|
||||||
|
<div class="cooldown-fill"></div>
|
||||||
|
<div class="cooldown-label">Fireball</div>
|
||||||
|
</div>
|
||||||
|
<div id="sword-cooldown" class="cooldown-container">
|
||||||
|
<div class="cooldown-bg"></div>
|
||||||
|
<div class="cooldown-fill"></div>
|
||||||
|
<div class="cooldown-label">Sword</div>
|
||||||
|
</div>
|
||||||
|
<div id="teleport-cooldown" class="cooldown-container">
|
||||||
|
<div class="cooldown-bg"></div>
|
||||||
|
<div class="cooldown-fill"></div>
|
||||||
|
<div class="cooldown-label">Teleport</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="wave-bar-container">
|
||||||
|
<div id="wave-bar-bg"></div>
|
||||||
|
<div id="wave-bar-fill"></div>
|
||||||
|
<div id="wave-bar-label">Next Wave...</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="game-over">Verloren!</div>
|
||||||
|
<div id="instructions">
|
||||||
|
WASD: Move · Space: Jump · Mouse: Aim · Left Click: Fireball · Right Click: Sword · M3: Swap hands · E: Teleport
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "wackelpeter",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "tsc && vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"three": "^0.170.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/three": "^0.170.0",
|
||||||
|
"typescript": "^5.7.0",
|
||||||
|
"vite": "^6.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"skeletons":[],"geometries":[{"texcoords":[0.120075,0.879188,0.360224,0.879925,0.360961,0.639776,0.120812,0.639039,0.120812,0.158003,0.120075,0.398152,0.360224,0.398889,0.360961,0.15874,0.620443,0.399512,0.619706,0.159363,0.601111,0.15942,0.601848,0.399569,0.879188,0.399569,0.879925,0.15942,0.86133,0.159363,0.860593,0.399512,0.619706,0.879925,0.620443,0.639776,0.601848,0.639719,0.601111,0.879868,0.879188,0.879925,0.879925,0.639776,0.86133,0.639719,0.860593,0.879868],"indices":[0,1,2,0,2,3,4,5,6,4,6,7,8,9,10,8,10,11,12,13,14,12,14,15,16,17,18,16,18,19,20,21,22,20,22,23],"name":"tile-geom-1","elementCount":36,"positions":[1.0,0.0,-1.0,1.0,0.0,1.0,-1.0,0.0,1.0,-1.0,0.0,-1.0,-1.0,0.154864,1.0,1.0,0.154864,1.0,1.0,0.154864,-1.0,-1.0,0.154864,-1.0,1.0,0.0,-1.0,-1.0,0.0,-1.0,-1.0,0.154864,-1.0,1.0,0.154864,-1.0,1.0,0.0,1.0,1.0,0.0,-1.0,1.0,0.154864,-1.0,1.0,0.154864,1.0,-1.0,0.0,-1.0,-1.0,0.0,1.0,-1.0,0.154864,1.0,-1.0,0.154864,-1.0,-1.0,0.0,1.0,1.0,0.0,1.0,1.0,0.154864,1.0,-1.0,0.154864,1.0],"triangleCount":12,"normals":[0.0,-1.0,-0.0,0.0,-1.0,-0.0,0.0,-1.0,-0.0,0.0,-1.0,-0.0,0.0,1.0,-0.0,0.0,1.0,-0.0,0.0,1.0,-0.0,0.0,1.0,-0.0,0.0,0.0,-1.0,0.0,0.0,-1.0,0.0,0.0,-1.0,0.0,0.0,-1.0,1.0,0.0,-0.0,1.0,0.0,-0.0,1.0,0.0,-0.0,1.0,0.0,-0.0,-1.0,-0.0,-0.0,-1.0,-0.0,-0.0,-1.0,-0.0,-0.0,-1.0,-0.0,-0.0,0.0,-0.0,1.0,0.0,-0.0,1.0,0.0,-0.0,1.0,0.0,-0.0,1.0],"vertexCount":24}],"animations":{}}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"skeletons":[],"geometries":[{"texcoords":[0.120075,0.879188,0.360224,0.879925,0.360961,0.639776,0.120812,0.639039,0.120812,0.158003,0.120075,0.398152,0.360224,0.398889,0.360961,0.15874,0.620443,0.399512,0.619706,0.159363,0.601111,0.15942,0.601848,0.399569,0.879188,0.399569,0.879925,0.15942,0.86133,0.159363,0.860593,0.399512,0.619706,0.879925,0.620443,0.639776,0.601848,0.639719,0.601111,0.879868,0.879188,0.879925,0.879925,0.639776,0.86133,0.639719,0.860593,0.879868],"indices":[0,1,2,0,2,3,4,5,6,4,6,7,8,9,10,8,10,11,12,13,14,12,14,15,16,17,18,16,18,19,20,21,22,20,22,23],"name":"tile-geom-1","elementCount":36,"positions":[1.0,0.0,-1.0,1.0,0.0,1.0,-1.0,0.0,1.0,-1.0,0.0,-1.0,-1.0,0.154864,1.0,1.0,0.154864,1.0,1.0,0.154864,-1.0,-1.0,0.154864,-1.0,1.0,0.0,-1.0,-1.0,0.0,-1.0,-1.0,0.154864,-1.0,1.0,0.154864,-1.0,1.0,0.0,1.0,1.0,0.0,-1.0,1.0,0.154864,-1.0,1.0,0.154864,1.0,-1.0,0.0,-1.0,-1.0,0.0,1.0,-1.0,0.154864,1.0,-1.0,0.154864,-1.0,-1.0,0.0,1.0,1.0,0.0,1.0,1.0,0.154864,1.0,-1.0,0.154864,1.0],"triangleCount":12,"normals":[0.0,-1.0,-0.0,0.0,-1.0,-0.0,0.0,-1.0,-0.0,0.0,-1.0,-0.0,0.0,1.0,-0.0,0.0,1.0,-0.0,0.0,1.0,-0.0,0.0,1.0,-0.0,0.0,0.0,-1.0,0.0,0.0,-1.0,0.0,0.0,-1.0,0.0,0.0,-1.0,1.0,0.0,-0.0,1.0,0.0,-0.0,1.0,0.0,-0.0,1.0,0.0,-0.0,-1.0,-0.0,-0.0,-1.0,-0.0,-0.0,-1.0,-0.0,-0.0,-1.0,-0.0,-0.0,0.0,-0.0,1.0,0.0,-0.0,1.0,0.0,-0.0,1.0,0.0,-0.0,1.0],"vertexCount":24}],"animations":{}}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"skeletons":[],"geometries":[{"texcoords":[0.998999,0.998999,0.667668,0.998999,0.667668,0.667668,0.998999,0.667668,0.332332,0.998999,0.001001,0.998999,0.001001,0.667668,0.332332,0.667668,0.332332,0.665666,0.001001,0.665666,0.001001,0.334334,0.332332,0.334334,0.665666,0.998999,0.334334,0.998999,0.334334,0.667668,0.665666,0.667668,0.665666,0.665666,0.334334,0.665666,0.334334,0.334334,0.665666,0.334334,0.668438,0.666433,0.666901,0.335105,0.998229,0.333567,0.999766,0.664895],"indices":[0,1,2,0,2,3,4,5,6,4,6,7,8,9,10,8,10,11,12,13,14,12,14,15,16,17,18,16,18,19,20,21,22,20,22,23],"name":"wall1","elementCount":36,"positions":[-0.999999,1.991615,1.000001,-0.999999,-0.008385,1.000002,1.000001,-0.008385,0.999999,1.000001,1.991615,0.999998,-1.000002,1.991613,-0.999999,0.999999,1.991613,-1.000002,0.999999,-0.008386,-1.000001,-1.000001,-0.008387,-0.999998,-0.999999,1.991615,1.000001,-1.000002,1.991613,-0.999999,-1.000001,-0.008387,-0.999998,-0.999999,-0.008385,1.000002,-0.999999,-0.008385,1.000002,-1.000001,-0.008387,-0.999998,0.999999,-0.008386,-1.000001,1.000001,-0.008385,0.999999,1.000001,-0.008385,0.999999,0.999999,-0.008386,-1.000001,0.999999,1.991613,-1.000002,1.000001,1.991615,0.999998,-1.000002,1.991613,-0.999999,-0.999999,1.991615,1.000001,1.000001,1.991615,0.999998,0.999999,1.991613,-1.000002],"triangleCount":12,"normals":[1.0E-6,1.0E-6,1.0,1.0E-6,1.0E-6,1.0,1.0E-6,1.0E-6,1.0,1.0E-6,1.0E-6,1.0,-1.0E-6,-1.0E-6,-1.0,-1.0E-6,-1.0E-6,-1.0,-1.0E-6,-1.0E-6,-1.0,-1.0E-6,-1.0E-6,-1.0,-1.0,-0.0,1.0E-6,-1.0,-0.0,1.0E-6,-1.0,-0.0,1.0E-6,-1.0,-0.0,1.0E-6,0.0,-1.0,1.0E-6,0.0,-1.0,1.0E-6,0.0,-1.0,1.0E-6,0.0,-1.0,1.0E-6,1.0,0.0,-1.0E-6,1.0,0.0,-1.0E-6,1.0,0.0,-1.0E-6,1.0,0.0,-1.0E-6,-0.0,1.0,-1.0E-6,-0.0,1.0,-1.0E-6,-0.0,1.0,-1.0E-6,-0.0,1.0,-1.0E-6],"vertexCount":24}],"animations":{}}
|
||||||
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 140 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 102 KiB |
|
After Width: | Height: | Size: 102 KiB |
|
After Width: | Height: | Size: 122 KiB |
|
After Width: | Height: | Size: 206 KiB |
|
After Width: | Height: | Size: 333 KiB |
@@ -0,0 +1,50 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
|
||||||
|
export class Arena extends THREE.Group {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
const tileSize = 10;
|
||||||
|
const half = tileSize / 2;
|
||||||
|
|
||||||
|
for (let x = -45; x < 50; x += tileSize) {
|
||||||
|
for (let z = -45; z < 50; z += tileSize) {
|
||||||
|
const isDark = ((x / tileSize + z / tileSize) & 1) === 0;
|
||||||
|
const color = isDark ? 0x668866 : 0x557755;
|
||||||
|
|
||||||
|
const tileGeom = new THREE.BoxGeometry(9.5, 0.3, 9.5);
|
||||||
|
const tileMat = new THREE.MeshStandardMaterial({
|
||||||
|
color,
|
||||||
|
roughness: 0.7,
|
||||||
|
metalness: 0.0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const tile = new THREE.Mesh(tileGeom, tileMat);
|
||||||
|
tile.position.set(x, -0.15, z);
|
||||||
|
tile.receiveShadow = true;
|
||||||
|
this.add(tile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walls
|
||||||
|
const wallGeom = new THREE.BoxGeometry(tileSize - 0.5, 2, 1);
|
||||||
|
const wallMat = new THREE.MeshToonMaterial({ color: 0x776677 });
|
||||||
|
|
||||||
|
for (let coord = -50; coord <= 50; coord += tileSize) {
|
||||||
|
this.createWall(coord, 49.5, wallGeom, wallMat);
|
||||||
|
this.createWall(coord, -49.5, wallGeom, wallMat);
|
||||||
|
}
|
||||||
|
for (let coord = -50; coord <= 50; coord += tileSize) {
|
||||||
|
this.createWall(49.5, coord, new THREE.BoxGeometry(1, 2, tileSize - 0.5), wallMat);
|
||||||
|
this.createWall(-49.5, coord, new THREE.BoxGeometry(1, 2, tileSize - 0.5), wallMat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private createWall(x: number, z: number, geom: THREE.BoxGeometry, mat: THREE.Material) {
|
||||||
|
const wall = new THREE.Mesh(geom, mat);
|
||||||
|
wall.position.set(x, 1, z);
|
||||||
|
wall.castShadow = true;
|
||||||
|
wall.receiveShadow = true;
|
||||||
|
this.add(wall);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
import { loadGLB } from './MeshLoader';
|
||||||
|
|
||||||
|
let crossGeometry: THREE.BufferGeometry | null = null;
|
||||||
|
let crossParticlesTemplate: THREE.Points | null = null;
|
||||||
|
|
||||||
|
export async function initCrossModel() {
|
||||||
|
try {
|
||||||
|
const gltf = await loadGLB('cross');
|
||||||
|
gltf.scene.traverse((child) => {
|
||||||
|
if (child instanceof THREE.Mesh) {
|
||||||
|
crossGeometry = child.geometry;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
console.warn('Failed to load cross model');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createGreenParticles(): THREE.Points {
|
||||||
|
const count = 50;
|
||||||
|
const tex = new THREE.TextureLoader().load('./textures/flame.png');
|
||||||
|
tex.colorSpace = THREE.SRGBColorSpace;
|
||||||
|
const geom = new THREE.BufferGeometry();
|
||||||
|
const positions = new Float32Array(count * 3);
|
||||||
|
geom.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
||||||
|
const mat = new THREE.PointsMaterial({
|
||||||
|
map: tex,
|
||||||
|
color: 0x66ff66,
|
||||||
|
size: 0.12,
|
||||||
|
blending: THREE.AdditiveBlending,
|
||||||
|
depthWrite: false,
|
||||||
|
transparent: true,
|
||||||
|
});
|
||||||
|
const points = new THREE.Points(geom, mat);
|
||||||
|
points.renderOrder = 999;
|
||||||
|
points.userData = {
|
||||||
|
velocities: [] as THREE.Vector3[],
|
||||||
|
lifetimes: [] as number[],
|
||||||
|
ages: [] as number[],
|
||||||
|
timer: 0,
|
||||||
|
};
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
positions[i * 3] = (Math.random() - 0.5) * 0.8;
|
||||||
|
positions[i * 3 + 1] = Math.random() * 1.2;
|
||||||
|
positions[i * 3 + 2] = (Math.random() - 0.5) * 0.8;
|
||||||
|
points.userData.velocities.push(new THREE.Vector3(
|
||||||
|
(Math.random() - 0.5) * 0.5,
|
||||||
|
1.5 + Math.random() * 1.5,
|
||||||
|
(Math.random() - 0.5) * 0.5
|
||||||
|
));
|
||||||
|
points.userData.lifetimes.push(1 + Math.random());
|
||||||
|
points.userData.ages.push(Math.random() * 2);
|
||||||
|
}
|
||||||
|
return points;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Cross {
|
||||||
|
body: THREE.Group;
|
||||||
|
private particles: THREE.Points;
|
||||||
|
|
||||||
|
constructor(position: THREE.Vector3) {
|
||||||
|
this.body = new THREE.Group();
|
||||||
|
this.body.position.copy(position);
|
||||||
|
this.body.position.y = 0.3;
|
||||||
|
|
||||||
|
if (crossGeometry) {
|
||||||
|
// Original material: red cross (from j3o material extraction)
|
||||||
|
const mesh = new THREE.Mesh(crossGeometry, new THREE.MeshToonMaterial({
|
||||||
|
color: 0xff0000, emissive: 0x440000,
|
||||||
|
}));
|
||||||
|
mesh.castShadow = true;
|
||||||
|
mesh.scale.set(0.25, 0.25, 0.25);
|
||||||
|
this.body.add(mesh);
|
||||||
|
} else {
|
||||||
|
// Fallback cross
|
||||||
|
const crossMaterial = new THREE.MeshToonMaterial({
|
||||||
|
color: 0xff0000, emissive: 0x440000,
|
||||||
|
});
|
||||||
|
const vertical = new THREE.Mesh(new THREE.BoxGeometry(0.15, 0.8, 0.15), crossMaterial);
|
||||||
|
vertical.position.y = 0.4;
|
||||||
|
this.body.add(vertical);
|
||||||
|
const horizontal = new THREE.Mesh(new THREE.BoxGeometry(0.6, 0.15, 0.15), crossMaterial);
|
||||||
|
horizontal.position.y = 0.5;
|
||||||
|
this.body.add(horizontal);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Green glow
|
||||||
|
const glowGeom = new THREE.SphereGeometry(0.6, 16, 16);
|
||||||
|
const glowMat = new THREE.MeshBasicMaterial({
|
||||||
|
color: 0x00ff00, transparent: true, opacity: 0.15,
|
||||||
|
});
|
||||||
|
const glow = new THREE.Mesh(glowGeom, glowMat);
|
||||||
|
this.body.add(glow);
|
||||||
|
|
||||||
|
// Upward-flying light green particles (like the original)
|
||||||
|
this.particles = createGreenParticles();
|
||||||
|
this.body.add(this.particles);
|
||||||
|
}
|
||||||
|
|
||||||
|
update(dt: number) {
|
||||||
|
const attr = this.particles.geometry.getAttribute('position') as THREE.BufferAttribute;
|
||||||
|
const arr = attr.array as Float32Array;
|
||||||
|
const ud = this.particles.userData;
|
||||||
|
const count = ud.velocities.length;
|
||||||
|
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
ud.ages[i] += dt;
|
||||||
|
const life = ud.lifetimes[i];
|
||||||
|
if (ud.ages[i] >= life) {
|
||||||
|
// Respawn at bottom
|
||||||
|
ud.ages[i] = 0;
|
||||||
|
arr[i * 3] = (Math.random() - 0.5) * 0.8;
|
||||||
|
arr[i * 3 + 1] = 0;
|
||||||
|
arr[i * 3 + 2] = (Math.random() - 0.5) * 0.8;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const v = ud.velocities[i];
|
||||||
|
arr[i * 3] += v.x * dt;
|
||||||
|
arr[i * 3 + 1] += v.y * dt;
|
||||||
|
arr[i * 3 + 2] += v.z * dt;
|
||||||
|
}
|
||||||
|
attr.needsUpdate = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
import type { Game } from './Game';
|
||||||
|
import { playSound, playLoopingSound } from './SoundManager';
|
||||||
|
import { loadGLB } from './MeshLoader';
|
||||||
|
import type { GLTF } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
||||||
|
import { clone as cloneSkeleton } from 'three/examples/jsm/utils/SkeletonUtils.js';
|
||||||
|
|
||||||
|
let spiderGLTF: GLTF | null = null;
|
||||||
|
const ANIMATIONS_ENABLED = true;
|
||||||
|
export async function initSpiderModel() {
|
||||||
|
try {
|
||||||
|
spiderGLTF = await loadGLB('spider');
|
||||||
|
} catch {
|
||||||
|
console.warn('Failed to load spider model');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Enemy {
|
||||||
|
body: THREE.Group;
|
||||||
|
dead = false;
|
||||||
|
deathTimer = 0;
|
||||||
|
knockback = 0;
|
||||||
|
health = 100;
|
||||||
|
mixer: THREE.AnimationMixer | null = null;
|
||||||
|
private currentAnim = '';
|
||||||
|
private clips: Map<string, THREE.AnimationAction> = new Map();
|
||||||
|
private stopWalkSound: (() => void) | null = null;
|
||||||
|
|
||||||
|
constructor(_playerPos: THREE.Vector3) {
|
||||||
|
this.body = new THREE.Group();
|
||||||
|
this.stopWalkSound = playLoopingSound('spiderwalking', 0.3);
|
||||||
|
|
||||||
|
if (spiderGLTF) {
|
||||||
|
const model = cloneSkeleton(spiderGLTF.scene);
|
||||||
|
// Original game scaled the spider model by 0.5 (WalkingEnemy.setupModel)
|
||||||
|
model.scale.set(0.5, 0.5, 0.5);
|
||||||
|
const spiderTexture = new THREE.TextureLoader().load('./textures/spider_animation2.png');
|
||||||
|
spiderTexture.flipY = false;
|
||||||
|
spiderTexture.colorSpace = THREE.SRGBColorSpace;
|
||||||
|
model.traverse((child) => {
|
||||||
|
if (child instanceof THREE.Mesh) {
|
||||||
|
child.castShadow = true;
|
||||||
|
child.material = new THREE.MeshToonMaterial({ map: spiderTexture });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.body.add(model);
|
||||||
|
|
||||||
|
if (spiderGLTF.animations.length > 0 && ANIMATIONS_ENABLED) {
|
||||||
|
this.mixer = new THREE.AnimationMixer(model);
|
||||||
|
for (const clip of spiderGLTF.animations) {
|
||||||
|
const action = this.mixer.clipAction(clip);
|
||||||
|
this.clips.set(clip.name, action);
|
||||||
|
}
|
||||||
|
this.playAnim('stand');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Fallback
|
||||||
|
const abdomenGeom = new THREE.SphereGeometry(0.5, 8, 8);
|
||||||
|
const bodyMat = new THREE.MeshToonMaterial({ color: 0x333333 });
|
||||||
|
const abdomen = new THREE.Mesh(abdomenGeom, bodyMat);
|
||||||
|
abdomen.scale.set(1, 0.6, 1.3);
|
||||||
|
abdomen.position.y = 0.6;
|
||||||
|
abdomen.castShadow = true;
|
||||||
|
this.body.add(abdomen);
|
||||||
|
|
||||||
|
const cephalothoraxGeom = new THREE.SphereGeometry(0.3, 8, 8);
|
||||||
|
const cephalothorax = new THREE.Mesh(cephalothoraxGeom, bodyMat);
|
||||||
|
cephalothorax.position.y = 0.6;
|
||||||
|
cephalothorax.position.z = 0.5;
|
||||||
|
cephalothorax.castShadow = true;
|
||||||
|
this.body.add(cephalothorax);
|
||||||
|
|
||||||
|
const eyeGeom = new THREE.SphereGeometry(0.06, 6, 6);
|
||||||
|
const eyeMat = new THREE.MeshBasicMaterial({ color: 0xff0000 });
|
||||||
|
for (const sign of [-1, 1]) {
|
||||||
|
const eye = new THREE.Mesh(eyeGeom, eyeMat);
|
||||||
|
eye.position.set(sign * 0.12, 0.85, 0.7);
|
||||||
|
this.body.add(eye);
|
||||||
|
}
|
||||||
|
|
||||||
|
const legGeom = new THREE.CylinderGeometry(0.04, 0.04, 0.8, 4);
|
||||||
|
for (let i = 0; i < 8; i++) {
|
||||||
|
const leg = new THREE.Mesh(legGeom, new THREE.MeshToonMaterial({ color: 0x222222 }));
|
||||||
|
const angle = (i / 8) * Math.PI * 2;
|
||||||
|
const side = i < 4 ? 1 : -1;
|
||||||
|
leg.position.set(Math.cos(angle) * 0.35, 0.3, Math.sin(angle) * 0.35);
|
||||||
|
leg.rotation.z = side * 0.6;
|
||||||
|
leg.rotation.x = angle;
|
||||||
|
leg.castShadow = true;
|
||||||
|
this.body.add(leg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
playAnim(name: string, loop = true, speed = 1) {
|
||||||
|
if (!this.clips.has(name) || this.currentAnim === name) return;
|
||||||
|
// Stop the previous animation so it doesn't blend with the new one
|
||||||
|
if (this.currentAnim) {
|
||||||
|
const prev = this.clips.get(this.currentAnim);
|
||||||
|
if (prev) prev.stop();
|
||||||
|
}
|
||||||
|
this.currentAnim = name;
|
||||||
|
const action = this.clips.get(name)!;
|
||||||
|
action.reset();
|
||||||
|
action.setLoop(loop ? THREE.LoopRepeat : THREE.LoopOnce, loop ? Infinity : 1);
|
||||||
|
action.clampWhenFinished = !loop;
|
||||||
|
action.setEffectiveTimeScale(speed);
|
||||||
|
action.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateAnimation(dt: number) {
|
||||||
|
if (this.mixer) this.mixer.update(dt);
|
||||||
|
}
|
||||||
|
|
||||||
|
takeDamage(damage: number, game: Game) {
|
||||||
|
if (this.dead) return;
|
||||||
|
this.health -= damage;
|
||||||
|
this.knockback = 0.5;
|
||||||
|
|
||||||
|
playSound('damage', 0.7);
|
||||||
|
|
||||||
|
if (this.health <= 0) {
|
||||||
|
this.die(game);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private die(game: Game) {
|
||||||
|
if (this.dead) return;
|
||||||
|
this.dead = true;
|
||||||
|
this.playAnim('die', false);
|
||||||
|
if (this.stopWalkSound) {
|
||||||
|
this.stopWalkSound();
|
||||||
|
this.stopWalkSound = null;
|
||||||
|
}
|
||||||
|
game.removeEnemy(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,253 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
import { playSound } from './SoundManager';
|
||||||
|
|
||||||
|
const flameTexture = new THREE.TextureLoader().load('./textures/flame.png');
|
||||||
|
flameTexture.colorSpace = THREE.SRGBColorSpace;
|
||||||
|
|
||||||
|
export class Fireball {
|
||||||
|
body: THREE.Group;
|
||||||
|
light: THREE.PointLight;
|
||||||
|
direction: THREE.Vector3;
|
||||||
|
lifetime = 1;
|
||||||
|
exploding = false;
|
||||||
|
timer = 0;
|
||||||
|
damageDone = false;
|
||||||
|
private trailParticles: THREE.Points;
|
||||||
|
private trailVelocities: Float32Array;
|
||||||
|
private trailLives: Float32Array;
|
||||||
|
private trailMaxLife = 0.6;
|
||||||
|
|
||||||
|
constructor(start: THREE.Vector3, direction: THREE.Vector3) {
|
||||||
|
this.direction = direction.clone();
|
||||||
|
this.body = new THREE.Group();
|
||||||
|
this.body.position.copy(start);
|
||||||
|
|
||||||
|
// Fireball core (additive glow)
|
||||||
|
const coreGeom = new THREE.SphereGeometry(0.45, 16, 16);
|
||||||
|
const coreMat = new THREE.MeshBasicMaterial({
|
||||||
|
color: 0xffcc44,
|
||||||
|
blending: THREE.AdditiveBlending,
|
||||||
|
depthWrite: false,
|
||||||
|
transparent: true,
|
||||||
|
});
|
||||||
|
const core = new THREE.Mesh(coreGeom, coreMat);
|
||||||
|
this.body.add(core);
|
||||||
|
|
||||||
|
const glowGeom = new THREE.SphereGeometry(0.75, 16, 16);
|
||||||
|
const glowMat = new THREE.MeshBasicMaterial({
|
||||||
|
color: 0xff6600, transparent: true, opacity: 0.45,
|
||||||
|
blending: THREE.AdditiveBlending,
|
||||||
|
depthWrite: false,
|
||||||
|
});
|
||||||
|
const glow = new THREE.Mesh(glowGeom, glowMat);
|
||||||
|
this.body.add(glow);
|
||||||
|
|
||||||
|
// Sprite with flame texture
|
||||||
|
const spriteMat = new THREE.SpriteMaterial({
|
||||||
|
map: flameTexture,
|
||||||
|
color: 0xffaa33,
|
||||||
|
blending: THREE.AdditiveBlending,
|
||||||
|
depthWrite: false,
|
||||||
|
transparent: true,
|
||||||
|
});
|
||||||
|
const sprite = new THREE.Sprite(spriteMat);
|
||||||
|
sprite.scale.set(2.2, 2.2, 1);
|
||||||
|
this.body.add(sprite);
|
||||||
|
|
||||||
|
// Trail particles (flame sprites behind the fireball)
|
||||||
|
const trailCount = 30;
|
||||||
|
const trailGeom = new THREE.BufferGeometry();
|
||||||
|
const positions = new Float32Array(trailCount * 3);
|
||||||
|
trailGeom.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
||||||
|
const trailMat = new THREE.PointsMaterial({
|
||||||
|
map: flameTexture,
|
||||||
|
color: 0xff8833,
|
||||||
|
size: 1.0,
|
||||||
|
blending: THREE.AdditiveBlending,
|
||||||
|
depthWrite: false,
|
||||||
|
transparent: true,
|
||||||
|
opacity: 0.7,
|
||||||
|
});
|
||||||
|
this.trailParticles = new THREE.Points(trailGeom, trailMat);
|
||||||
|
this.body.add(this.trailParticles);
|
||||||
|
this.trailVelocities = new Float32Array(trailCount * 3);
|
||||||
|
this.trailLives = new Float32Array(trailCount).fill(0);
|
||||||
|
|
||||||
|
// Strong dynamic light (original: radius 100, orange, with shadow renderer)
|
||||||
|
this.light = new THREE.PointLight(0xff7722, 80, 45, 1.5);
|
||||||
|
this.light.position.set(0, 0.5, 0);
|
||||||
|
this.light.castShadow = true;
|
||||||
|
this.light.shadow.mapSize.width = 256;
|
||||||
|
this.light.shadow.mapSize.height = 256;
|
||||||
|
this.light.shadow.camera.near = 0.5;
|
||||||
|
this.light.shadow.camera.far = 45;
|
||||||
|
this.light.shadow.bias = -0.005;
|
||||||
|
this.body.add(this.light);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTrail(dt: number, pos: THREE.Vector3) {
|
||||||
|
const attr = this.trailParticles.geometry.getAttribute('position') as THREE.BufferAttribute;
|
||||||
|
const arr = attr.array as Float32Array;
|
||||||
|
|
||||||
|
for (let i = 0; i < this.trailLives.length; i++) {
|
||||||
|
if (this.trailLives[i] > 0) {
|
||||||
|
this.trailLives[i] -= dt;
|
||||||
|
arr[i * 3] += this.trailVelocities[i * 3] * dt;
|
||||||
|
arr[i * 3 + 1] += this.trailVelocities[i * 3 + 1] * dt;
|
||||||
|
arr[i * 3 + 2] += this.trailVelocities[i * 3 + 2] * dt;
|
||||||
|
if (this.trailLives[i] <= 0) {
|
||||||
|
arr[i * 3] = pos.x;
|
||||||
|
arr[i * 3 + 1] = pos.y;
|
||||||
|
arr[i * 3 + 2] = pos.z;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Spawn new trail particle
|
||||||
|
arr[i * 3] = pos.x;
|
||||||
|
arr[i * 3 + 1] = pos.y;
|
||||||
|
arr[i * 3 + 2] = pos.z;
|
||||||
|
this.trailVelocities[i * 3] = (Math.random() - 0.5) * 1.5;
|
||||||
|
this.trailVelocities[i * 3 + 1] = (Math.random() - 0.5) * 1.5;
|
||||||
|
this.trailVelocities[i * 3 + 2] = (Math.random() - 0.5) * 1.5;
|
||||||
|
this.trailLives[i] = this.trailMaxLife * (0.5 + Math.random());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
attr.needsUpdate = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
explode(scene: THREE.Scene) {
|
||||||
|
if (this.exploding) return;
|
||||||
|
this.exploding = true;
|
||||||
|
this.timer = 0;
|
||||||
|
this.damageDone = false;
|
||||||
|
|
||||||
|
playSound('explosion', 0.6);
|
||||||
|
|
||||||
|
// Hide the fireball core and trail
|
||||||
|
for (const child of [...this.body.children]) {
|
||||||
|
if (child instanceof THREE.Mesh || child instanceof THREE.Sprite || child instanceof THREE.Points) {
|
||||||
|
child.visible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bright flash
|
||||||
|
const flashGeom = new THREE.SphereGeometry(3, 16, 16);
|
||||||
|
const flashMat = new THREE.MeshBasicMaterial({
|
||||||
|
color: 0xffdd66, transparent: true, opacity: 0.9,
|
||||||
|
blending: THREE.AdditiveBlending,
|
||||||
|
depthWrite: false,
|
||||||
|
});
|
||||||
|
const flash = new THREE.Mesh(flashGeom, flashMat);
|
||||||
|
flash.position.copy(this.body.position);
|
||||||
|
scene.add(flash);
|
||||||
|
|
||||||
|
// Big explosion light pulse
|
||||||
|
const flashLight = new THREE.PointLight(0xff8833, 200, 35, 1.5);
|
||||||
|
flashLight.position.copy(this.body.position);
|
||||||
|
scene.add(flashLight);
|
||||||
|
|
||||||
|
const startTime = performance.now();
|
||||||
|
const updateFlash = () => {
|
||||||
|
const elapsed = (performance.now() - startTime) / 1000;
|
||||||
|
if (elapsed > 0.6) {
|
||||||
|
flash.removeFromParent();
|
||||||
|
flash.geometry.dispose();
|
||||||
|
flashMat.dispose();
|
||||||
|
scene.remove(flashLight);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const scale = 1 + elapsed * 8;
|
||||||
|
flash.scale.set(scale, scale, scale);
|
||||||
|
flashMat.opacity = 0.9 * (1 - elapsed / 0.6);
|
||||||
|
flashLight.intensity = 200 * (1 - elapsed / 0.6);
|
||||||
|
requestAnimationFrame(updateFlash);
|
||||||
|
};
|
||||||
|
updateFlash();
|
||||||
|
|
||||||
|
// Fire particle burst (original: 50 flame particles)
|
||||||
|
this.spawnExplosionParticles(scene, './textures/flame.png', 0xffaa33, 60, 8, 1.5);
|
||||||
|
|
||||||
|
// Debris stones (original: stone particles with gravity)
|
||||||
|
this.spawnExplosionParticles(scene, './textures/flame.png', 0x555544, 25, 6, 1.0, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private spawnExplosionParticles(
|
||||||
|
scene: THREE.Scene,
|
||||||
|
texPath: string,
|
||||||
|
color: number,
|
||||||
|
count: number,
|
||||||
|
speed: number,
|
||||||
|
size: number,
|
||||||
|
withGravity = false
|
||||||
|
) {
|
||||||
|
const tex = new THREE.TextureLoader().load(texPath);
|
||||||
|
tex.colorSpace = THREE.SRGBColorSpace;
|
||||||
|
const geom = new THREE.BufferGeometry();
|
||||||
|
const positions = new Float32Array(count * 3);
|
||||||
|
const velocities: THREE.Vector3[] = [];
|
||||||
|
const lives: number[] = [];
|
||||||
|
const maxLives: number[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
positions[i * 3] = this.body.position.x;
|
||||||
|
positions[i * 3 + 1] = this.body.position.y;
|
||||||
|
positions[i * 3 + 2] = this.body.position.z;
|
||||||
|
velocities.push(new THREE.Vector3(
|
||||||
|
(Math.random() - 0.5) * speed * 2,
|
||||||
|
Math.random() * speed,
|
||||||
|
(Math.random() - 0.5) * speed * 2
|
||||||
|
));
|
||||||
|
const life = 0.5 + Math.random() * 1.2;
|
||||||
|
lives.push(life);
|
||||||
|
maxLives.push(life);
|
||||||
|
}
|
||||||
|
|
||||||
|
geom.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
||||||
|
const mat = new THREE.PointsMaterial({
|
||||||
|
map: tex,
|
||||||
|
color,
|
||||||
|
size,
|
||||||
|
blending: THREE.AdditiveBlending,
|
||||||
|
depthWrite: false,
|
||||||
|
transparent: true,
|
||||||
|
});
|
||||||
|
const points = new THREE.Points(geom, mat);
|
||||||
|
points.renderOrder = 999;
|
||||||
|
scene.add(points);
|
||||||
|
|
||||||
|
const startTime = performance.now();
|
||||||
|
const update = () => {
|
||||||
|
const elapsed = (performance.now() - startTime) / 1000;
|
||||||
|
if (elapsed > 2) {
|
||||||
|
points.removeFromParent();
|
||||||
|
geom.dispose();
|
||||||
|
mat.dispose();
|
||||||
|
tex.dispose();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const attr = geom.getAttribute('position') as THREE.BufferAttribute;
|
||||||
|
const arr = attr.array as Float32Array;
|
||||||
|
let allDead = true;
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
lives[i] -= 0.016;
|
||||||
|
if (lives[i] <= 0) continue;
|
||||||
|
allDead = false;
|
||||||
|
const v = velocities[i];
|
||||||
|
if (withGravity) v.y -= 9.8 * 0.016;
|
||||||
|
arr[i * 3] += v.x * 0.016;
|
||||||
|
arr[i * 3 + 1] += v.y * 0.016;
|
||||||
|
arr[i * 3 + 2] += v.z * 0.016;
|
||||||
|
}
|
||||||
|
attr.needsUpdate = true;
|
||||||
|
mat.opacity = Math.min(1, elapsed / 2) * 0.9;
|
||||||
|
if (allDead) {
|
||||||
|
points.removeFromParent();
|
||||||
|
geom.dispose();
|
||||||
|
mat.dispose();
|
||||||
|
tex.dispose();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
requestAnimationFrame(update);
|
||||||
|
};
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,585 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
import { Player } from './Player';
|
||||||
|
import { Enemy, initSpiderModel } from './Enemy';
|
||||||
|
import { Sword } from './Sword';
|
||||||
|
import { Staff } from './Staff';
|
||||||
|
import { Fireball } from './Fireball';
|
||||||
|
import { Cross, initCrossModel } from './Cross';
|
||||||
|
import { Arena } from './Arena';
|
||||||
|
import { initSounds, playSound, resumeContext } from './SoundManager';
|
||||||
|
|
||||||
|
const SPAWN_TIME = 10;
|
||||||
|
|
||||||
|
export class Game {
|
||||||
|
scene: THREE.Scene;
|
||||||
|
camera: THREE.PerspectiveCamera;
|
||||||
|
renderer: THREE.WebGLRenderer;
|
||||||
|
clock: THREE.Clock;
|
||||||
|
|
||||||
|
player!: Player;
|
||||||
|
enemies: Enemy[] = [];
|
||||||
|
fireballs: Fireball[] = [];
|
||||||
|
crosses: Cross[] = [];
|
||||||
|
|
||||||
|
keys: Set<string> = new Set();
|
||||||
|
mouseButtons: Set<number> = new Set();
|
||||||
|
private prevMouseButtons: Set<number> = new Set();
|
||||||
|
mouseX = 0;
|
||||||
|
mouseY = 0;
|
||||||
|
groundPoint = new THREE.Vector3();
|
||||||
|
|
||||||
|
spawnTimer = 0;
|
||||||
|
spawns = 0;
|
||||||
|
killed = 0;
|
||||||
|
gameOver = false;
|
||||||
|
|
||||||
|
// Debug hitzone visualization
|
||||||
|
private showHitzones = false;
|
||||||
|
private playerHitRing!: THREE.Mesh;
|
||||||
|
private enemyHitRings: Map<Enemy, THREE.Mesh> = new Map();
|
||||||
|
private fireballHitRings: Map<Fireball, THREE.Mesh> = new Map();
|
||||||
|
private crossHitRings: Map<Cross, THREE.Mesh> = new Map();
|
||||||
|
|
||||||
|
// UI elements
|
||||||
|
private uiHealthBar!: HTMLElement;
|
||||||
|
private uiHealthText!: HTMLElement;
|
||||||
|
private uiWaveLabel!: HTMLElement;
|
||||||
|
private uiKillsLabel!: HTMLElement;
|
||||||
|
private uiFireballCD!: HTMLElement;
|
||||||
|
private uiSwordCD!: HTMLElement;
|
||||||
|
private uiTeleportCD!: HTMLElement;
|
||||||
|
private uiWaveBarFill!: HTMLElement;
|
||||||
|
private uiWaveBarLabel!: HTMLElement;
|
||||||
|
private uiGameOver!: HTMLElement;
|
||||||
|
private mouseOnCanvas = false;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.scene = new THREE.Scene();
|
||||||
|
this.scene.background = new THREE.Color(0x0d1117);
|
||||||
|
this.scene.fog = new THREE.Fog(0x0d1117, 25, 70);
|
||||||
|
|
||||||
|
this.camera = new THREE.PerspectiveCamera(
|
||||||
|
60, window.innerWidth / window.innerHeight, 1, 150
|
||||||
|
);
|
||||||
|
this.camera.position.set(0, 25, -15);
|
||||||
|
this.camera.lookAt(0, 0, 0);
|
||||||
|
|
||||||
|
this.renderer = new THREE.WebGLRenderer({ antialias: true });
|
||||||
|
this.renderer.setSize(window.innerWidth, window.innerHeight);
|
||||||
|
this.renderer.shadowMap.enabled = true;
|
||||||
|
this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
||||||
|
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
||||||
|
document.body.prepend(this.renderer.domElement);
|
||||||
|
|
||||||
|
this.clock = new THREE.Clock();
|
||||||
|
|
||||||
|
this.setupUI();
|
||||||
|
this.setupInput();
|
||||||
|
this.setupLighting();
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
this.setupArena();
|
||||||
|
this.setupPlayer();
|
||||||
|
this.player.setGame(this);
|
||||||
|
|
||||||
|
initSounds();
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
this.player.init(),
|
||||||
|
this.player.rightHandWeapon.init(),
|
||||||
|
this.player.leftHandWeapon.init(),
|
||||||
|
initSpiderModel(),
|
||||||
|
initCrossModel(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.camera.aspect = window.innerWidth / window.innerHeight;
|
||||||
|
this.camera.updateProjectionMatrix();
|
||||||
|
this.renderer.setSize(window.innerWidth, window.innerHeight);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.animate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupUI() {
|
||||||
|
this.uiHealthBar = document.getElementById('health-bar')!;
|
||||||
|
this.uiHealthText = document.getElementById('health-text')!;
|
||||||
|
this.uiWaveLabel = document.getElementById('wave-label')!;
|
||||||
|
this.uiKillsLabel = document.getElementById('kills-label')!;
|
||||||
|
this.uiFireballCD = document.querySelector('#fireball-cooldown .cooldown-fill')!;
|
||||||
|
this.uiSwordCD = document.querySelector('#sword-cooldown .cooldown-fill')!;
|
||||||
|
this.uiTeleportCD = document.querySelector('#teleport-cooldown .cooldown-fill')!;
|
||||||
|
this.uiWaveBarFill = document.getElementById('wave-bar-fill')!;
|
||||||
|
this.uiWaveBarLabel = document.getElementById('wave-bar-label')!;
|
||||||
|
this.uiGameOver = document.getElementById('game-over')!;
|
||||||
|
|
||||||
|
const canvas = this.renderer.domElement;
|
||||||
|
canvas.addEventListener('mouseenter', () => this.mouseOnCanvas = true);
|
||||||
|
canvas.addEventListener('mouseleave', () => this.mouseOnCanvas = false);
|
||||||
|
canvas.addEventListener('contextmenu', e => e.preventDefault());
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupInput() {
|
||||||
|
window.addEventListener('keydown', (e) => {
|
||||||
|
this.keys.add(e.code);
|
||||||
|
if (e.code === 'Space') {
|
||||||
|
this.player.jump();
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
if (e.code === 'KeyH') {
|
||||||
|
this.showHitzones = !this.showHitzones;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.addEventListener('keyup', (e) => {
|
||||||
|
this.keys.delete(e.code);
|
||||||
|
if (e.code === 'KeyE' && !this.gameOver) {
|
||||||
|
this.player.leftHandWeapon.skill2(this.groundPoint, this.player);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('mousedown', (e) => {
|
||||||
|
resumeContext();
|
||||||
|
this.mouseButtons.add(e.button);
|
||||||
|
this.prevMouseButtons.add(e.button);
|
||||||
|
});
|
||||||
|
window.addEventListener('mouseup', (e) => {
|
||||||
|
resumeContext();
|
||||||
|
this.mouseButtons.delete(e.button);
|
||||||
|
this.handleMouseRelease(e.button);
|
||||||
|
});
|
||||||
|
window.addEventListener('mousemove', (e) => {
|
||||||
|
this.mouseX = (e.clientX / window.innerWidth) * 2 - 1;
|
||||||
|
this.mouseY = -(e.clientY / window.innerHeight) * 2 + 1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleMouseRelease(button: number) {
|
||||||
|
if (this.gameOver) return;
|
||||||
|
if (button === 0) {
|
||||||
|
this.player.leftHandWeapon.skill1(this.groundPoint, this.player);
|
||||||
|
} else if (button === 1) {
|
||||||
|
this.player.switchHands();
|
||||||
|
} else if (button === 2) {
|
||||||
|
this.player.rightHandWeapon.skill1(this.groundPoint, this.player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupLighting() {
|
||||||
|
const ambient = new THREE.AmbientLight(0x556688, 0.35);
|
||||||
|
this.scene.add(ambient);
|
||||||
|
|
||||||
|
const dirLight = new THREE.DirectionalLight(0xccddff, 0.55);
|
||||||
|
dirLight.position.set(30, 40, 20);
|
||||||
|
dirLight.castShadow = true;
|
||||||
|
dirLight.shadow.mapSize.width = 1024;
|
||||||
|
dirLight.shadow.mapSize.height = 1024;
|
||||||
|
dirLight.shadow.camera.near = 1;
|
||||||
|
dirLight.shadow.camera.far = 150;
|
||||||
|
dirLight.shadow.camera.left = -60;
|
||||||
|
dirLight.shadow.camera.right = 60;
|
||||||
|
dirLight.shadow.camera.top = 60;
|
||||||
|
dirLight.shadow.camera.bottom = -60;
|
||||||
|
this.scene.add(dirLight);
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupArena() {
|
||||||
|
const arena = new Arena();
|
||||||
|
this.scene.add(arena);
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupPlayer() {
|
||||||
|
this.player = new Player();
|
||||||
|
this.player.body.position.set(0, 0.5, 0);
|
||||||
|
this.scene.add(this.player.body);
|
||||||
|
|
||||||
|
// Player hitzone ring (contact damage radius = 2)
|
||||||
|
this.playerHitRing = this.createHitRing(2, 0xff0000);
|
||||||
|
this.scene.add(this.playerHitRing);
|
||||||
|
}
|
||||||
|
|
||||||
|
private createHitRing(radius: number, color: number): THREE.Mesh {
|
||||||
|
const geom = new THREE.RingGeometry(radius - 0.05, radius, 48);
|
||||||
|
const mat = new THREE.MeshBasicMaterial({
|
||||||
|
color,
|
||||||
|
transparent: true,
|
||||||
|
opacity: 0.5,
|
||||||
|
side: THREE.DoubleSide,
|
||||||
|
depthWrite: false,
|
||||||
|
});
|
||||||
|
const ring = new THREE.Mesh(geom, mat);
|
||||||
|
ring.rotation.x = -Math.PI / 2;
|
||||||
|
ring.position.y = 0.05;
|
||||||
|
ring.renderOrder = 999;
|
||||||
|
return ring;
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateHitzones() {
|
||||||
|
this.playerHitRing.position.x = this.player.body.position.x;
|
||||||
|
this.playerHitRing.position.z = this.player.body.position.z;
|
||||||
|
this.playerHitRing.visible = this.showHitzones;
|
||||||
|
|
||||||
|
// Enemy rings
|
||||||
|
for (const enemy of this.enemies) {
|
||||||
|
let ring = this.enemyHitRings.get(enemy);
|
||||||
|
if (!ring) {
|
||||||
|
ring = this.createHitRing(2, 0xff8800);
|
||||||
|
this.scene.add(ring);
|
||||||
|
this.enemyHitRings.set(enemy, ring);
|
||||||
|
}
|
||||||
|
ring.position.x = enemy.body.position.x;
|
||||||
|
ring.position.z = enemy.body.position.z;
|
||||||
|
ring.visible = this.showHitzones && !enemy.dead;
|
||||||
|
}
|
||||||
|
// Clean up removed enemies
|
||||||
|
for (const [enemy, ring] of this.enemyHitRings) {
|
||||||
|
if (!this.enemies.includes(enemy)) {
|
||||||
|
this.scene.remove(ring);
|
||||||
|
ring.geometry.dispose();
|
||||||
|
(ring.material as THREE.Material).dispose();
|
||||||
|
this.enemyHitRings.delete(enemy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fireball rings (impact radius 1.5 / AoE 5)
|
||||||
|
for (const fb of this.fireballs) {
|
||||||
|
let ring = this.fireballHitRings.get(fb);
|
||||||
|
if (!ring) {
|
||||||
|
ring = this.createHitRing(fb.exploding ? 5 : 1.5, fb.exploding ? 0xffff00 : 0xff6600);
|
||||||
|
this.scene.add(ring);
|
||||||
|
this.fireballHitRings.set(fb, ring);
|
||||||
|
}
|
||||||
|
ring.position.x = fb.body.position.x;
|
||||||
|
ring.position.z = fb.body.position.z;
|
||||||
|
ring.visible = this.showHitzones;
|
||||||
|
}
|
||||||
|
for (const [fb, ring] of this.fireballHitRings) {
|
||||||
|
if (!this.fireballs.includes(fb)) {
|
||||||
|
this.scene.remove(ring);
|
||||||
|
ring.geometry.dispose();
|
||||||
|
(ring.material as THREE.Material).dispose();
|
||||||
|
this.fireballHitRings.delete(fb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cross rings (pickup radius 2)
|
||||||
|
for (const cross of this.crosses) {
|
||||||
|
let ring = this.crossHitRings.get(cross);
|
||||||
|
if (!ring) {
|
||||||
|
ring = this.createHitRing(2, 0x00ff00);
|
||||||
|
this.scene.add(ring);
|
||||||
|
this.crossHitRings.set(cross, ring);
|
||||||
|
}
|
||||||
|
ring.position.x = cross.body.position.x;
|
||||||
|
ring.position.z = cross.body.position.z;
|
||||||
|
ring.visible = this.showHitzones;
|
||||||
|
}
|
||||||
|
for (const [cross, ring] of this.crossHitRings) {
|
||||||
|
if (!this.crosses.includes(cross)) {
|
||||||
|
this.scene.remove(ring);
|
||||||
|
ring.geometry.dispose();
|
||||||
|
(ring.material as THREE.Material).dispose();
|
||||||
|
this.crossHitRings.delete(cross);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private spawnEnemy() {
|
||||||
|
const x = (Math.random() - 0.5) * 80;
|
||||||
|
const z = (Math.random() - 0.5) * 80;
|
||||||
|
const enemy = new Enemy(this.player.body.position);
|
||||||
|
enemy.body.position.set(x, 0.1, z);
|
||||||
|
this.scene.add(enemy.body);
|
||||||
|
this.enemies.push(enemy);
|
||||||
|
}
|
||||||
|
|
||||||
|
private spawnCross(position: THREE.Vector3) {
|
||||||
|
if (Math.random() > 0.9) {
|
||||||
|
const cross = new Cross(position.clone());
|
||||||
|
this.scene.add(cross.body);
|
||||||
|
this.crosses.push(cross);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateGroundPoint() {
|
||||||
|
const raycaster = new THREE.Raycaster();
|
||||||
|
raycaster.setFromCamera(new THREE.Vector2(this.mouseX, this.mouseY), this.camera);
|
||||||
|
const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0);
|
||||||
|
const intersection = new THREE.Vector3();
|
||||||
|
if (raycaster.ray.intersectPlane(plane, intersection)) {
|
||||||
|
intersection.y = 0;
|
||||||
|
intersection.clamp(
|
||||||
|
new THREE.Vector3(-48, 0, -48),
|
||||||
|
new THREE.Vector3(48, 0, 48)
|
||||||
|
);
|
||||||
|
this.groundPoint.copy(intersection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private updatePlayerMovement(dt: number) {
|
||||||
|
if (this.gameOver) {
|
||||||
|
this.player.body.position.y = 0.5;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const camFwd = new THREE.Vector3();
|
||||||
|
this.camera.getWorldDirection(camFwd);
|
||||||
|
camFwd.y = 0;
|
||||||
|
camFwd.normalize();
|
||||||
|
|
||||||
|
const camLeft = new THREE.Vector3();
|
||||||
|
camLeft.crossVectors(new THREE.Vector3(0, 1, 0), camFwd).normalize();
|
||||||
|
|
||||||
|
const moveDir = new THREE.Vector3();
|
||||||
|
if (this.keys.has('KeyW') || this.keys.has('ArrowUp')) moveDir.add(camFwd);
|
||||||
|
if (this.keys.has('KeyS') || this.keys.has('ArrowDown')) moveDir.sub(camFwd);
|
||||||
|
if (this.keys.has('KeyA') || this.keys.has('ArrowLeft')) moveDir.add(camLeft);
|
||||||
|
if (this.keys.has('KeyD') || this.keys.has('ArrowRight')) moveDir.sub(camLeft);
|
||||||
|
|
||||||
|
let speed = 7.5;
|
||||||
|
let isMoving = false;
|
||||||
|
if (moveDir.length() > 0) {
|
||||||
|
moveDir.normalize();
|
||||||
|
this.player.velocity.set(moveDir.x * speed, 0, moveDir.z * speed);
|
||||||
|
isMoving = true;
|
||||||
|
} else {
|
||||||
|
this.player.velocity.set(0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Play walk/stand animations
|
||||||
|
if (isMoving && this.player.getCurrentAnimation() !== 'walk') {
|
||||||
|
this.player.playAnimation('walk');
|
||||||
|
} else if (!isMoving && this.player.getCurrentAnimation() !== 'stand') {
|
||||||
|
this.player.playAnimation('stand');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jump / gravity
|
||||||
|
if (!this.player.onGround) {
|
||||||
|
this.player.jumpVelocity -= 20 * dt;
|
||||||
|
this.player.body.position.y += this.player.jumpVelocity * dt;
|
||||||
|
if (this.player.body.position.y <= 0.5) {
|
||||||
|
this.player.body.position.y = 0.5;
|
||||||
|
this.player.jumpVelocity = 0;
|
||||||
|
this.player.onGround = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply horizontal movement
|
||||||
|
this.player.body.position.x += this.player.velocity.x * dt;
|
||||||
|
this.player.body.position.z += this.player.velocity.z * dt;
|
||||||
|
this.clampToArena(this.player.body.position);
|
||||||
|
|
||||||
|
// Look at ground point
|
||||||
|
this.player.body.lookAt(
|
||||||
|
this.groundPoint.x,
|
||||||
|
this.player.body.position.y,
|
||||||
|
this.groundPoint.z
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private clampToArena(pos: THREE.Vector3) {
|
||||||
|
pos.x = Math.max(-47, Math.min(47, pos.x));
|
||||||
|
pos.z = Math.max(-47, Math.min(47, pos.z));
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateEnemies(dt: number) {
|
||||||
|
for (const enemy of this.enemies) {
|
||||||
|
if (enemy.dead) {
|
||||||
|
enemy.updateAnimation(dt);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const toPlayer = new THREE.Vector3()
|
||||||
|
.subVectors(this.player.body.position, enemy.body.position);
|
||||||
|
toPlayer.y = 0;
|
||||||
|
|
||||||
|
const dist = toPlayer.length();
|
||||||
|
if (dist > 0.1) {
|
||||||
|
const dir = toPlayer.normalize();
|
||||||
|
enemy.body.lookAt(
|
||||||
|
enemy.body.position.x + dir.x,
|
||||||
|
enemy.body.position.y,
|
||||||
|
enemy.body.position.z + dir.z
|
||||||
|
);
|
||||||
|
|
||||||
|
enemy.playAnim('walk');
|
||||||
|
|
||||||
|
let moveDir = dir.multiplyScalar(6);
|
||||||
|
if (enemy.knockback > 0) {
|
||||||
|
moveDir = dir.multiplyScalar(-6);
|
||||||
|
enemy.knockback -= dt;
|
||||||
|
}
|
||||||
|
enemy.body.position.x += moveDir.x * dt;
|
||||||
|
enemy.body.position.z += moveDir.z * dt;
|
||||||
|
this.clampToArena(enemy.body.position);
|
||||||
|
} else {
|
||||||
|
enemy.playAnim('stand');
|
||||||
|
}
|
||||||
|
|
||||||
|
enemy.updateAnimation(dt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateFireballs(dt: number) {
|
||||||
|
for (let i = this.fireballs.length - 1; i >= 0; i--) {
|
||||||
|
const fb = this.fireballs[i];
|
||||||
|
fb.lifetime -= dt;
|
||||||
|
|
||||||
|
if (fb.exploding) {
|
||||||
|
fb.timer += dt;
|
||||||
|
if (fb.timer > 0.2 && !fb.damageDone) {
|
||||||
|
fb.damageDone = true;
|
||||||
|
for (const enemy of this.enemies) {
|
||||||
|
if (enemy.dead) continue;
|
||||||
|
const dist = enemy.body.position.distanceTo(fb.body.position);
|
||||||
|
if (dist < 8) {
|
||||||
|
enemy.takeDamage(100, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fb.timer > 2) {
|
||||||
|
this.scene.remove(fb.body);
|
||||||
|
this.fireballs.splice(i, 1);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
fb.body.position.x += fb.direction.x * dt;
|
||||||
|
fb.body.position.z += fb.direction.z * dt;
|
||||||
|
fb.updateTrail(dt, fb.body.position);
|
||||||
|
|
||||||
|
for (const enemy of this.enemies) {
|
||||||
|
if (enemy.dead) continue;
|
||||||
|
const dist = enemy.body.position.distanceTo(fb.body.position);
|
||||||
|
if (dist < 1.8) {
|
||||||
|
fb.explode(this.scene);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fb.lifetime <= 0) {
|
||||||
|
fb.explode(this.scene);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateUI() {
|
||||||
|
const healthPct = this.player.health / this.player.MAX_HEALTH;
|
||||||
|
this.uiHealthBar.style.width = `${Math.max(0, healthPct * 250)}px`;
|
||||||
|
this.uiHealthText.textContent = `${Math.ceil(this.player.health)}%`;
|
||||||
|
|
||||||
|
this.uiWaveLabel.textContent = `Wave ${this.spawns}`;
|
||||||
|
this.uiKillsLabel.textContent = `Kills: ${this.killed}`;
|
||||||
|
|
||||||
|
const sword = this.player.rightHandWeapon as Sword;
|
||||||
|
const staff = this.player.leftHandWeapon as Staff;
|
||||||
|
|
||||||
|
const fbPct = Math.max(0, (staff.COOLDOWN1_TIME - staff.cooldown1) / staff.COOLDOWN1_TIME);
|
||||||
|
this.uiFireballCD.style.width = `${fbPct * 100}%`;
|
||||||
|
|
||||||
|
const swordPct = Math.max(0, (sword.COOLDOWN1_TIME - sword.cooldown1) / sword.COOLDOWN1_TIME);
|
||||||
|
this.uiSwordCD.style.width = `${swordPct * 100}%`;
|
||||||
|
|
||||||
|
const tpPct = Math.max(0, (staff.COOLDOWN2_TIME - staff.cooldown2) / staff.COOLDOWN2_TIME);
|
||||||
|
this.uiTeleportCD.style.width = `${tpPct * 100}%`;
|
||||||
|
|
||||||
|
const wavePct = this.spawnTimer / SPAWN_TIME;
|
||||||
|
this.uiWaveBarFill.style.width = `${wavePct * 100}%`;
|
||||||
|
this.uiWaveBarLabel.textContent = `Next Wave: ${Math.ceil(SPAWN_TIME - this.spawnTimer)}s`;
|
||||||
|
|
||||||
|
if (this.player.health <= 0 && !this.gameOver) {
|
||||||
|
this.gameOver = true;
|
||||||
|
this.player.die();
|
||||||
|
this.uiGameOver.style.opacity = '1';
|
||||||
|
playSound('gameover', 0.8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private animate() {
|
||||||
|
requestAnimationFrame(() => this.animate());
|
||||||
|
|
||||||
|
const dt = Math.min(this.clock.getDelta(), 0.1);
|
||||||
|
|
||||||
|
this.updateGroundPoint();
|
||||||
|
this.updatePlayerMovement(dt);
|
||||||
|
this.updateEnemies(dt);
|
||||||
|
this.updateFireballs(dt);
|
||||||
|
this.updateHitzones();
|
||||||
|
|
||||||
|
// Update animations
|
||||||
|
this.player.updateAnimations(dt);
|
||||||
|
this.player.rightHandWeapon.updateAnimation(dt);
|
||||||
|
|
||||||
|
// Spawn enemies in waves
|
||||||
|
if (!this.gameOver) {
|
||||||
|
this.spawnTimer += dt;
|
||||||
|
if (this.spawnTimer >= SPAWN_TIME) {
|
||||||
|
this.spawns++;
|
||||||
|
for (let i = 0; i < this.spawns; i++) {
|
||||||
|
this.spawnEnemy();
|
||||||
|
}
|
||||||
|
this.spawnTimer = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check close encounters (spider touching player)
|
||||||
|
for (const enemy of this.enemies) {
|
||||||
|
if (enemy.dead) continue;
|
||||||
|
const dist = enemy.body.position.distanceTo(this.player.body.position);
|
||||||
|
if (dist < 2) {
|
||||||
|
this.player.health -= 25 * dt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cross collisions
|
||||||
|
for (let i = this.crosses.length - 1; i >= 0; i--) {
|
||||||
|
const cross = this.crosses[i];
|
||||||
|
cross.update(dt);
|
||||||
|
const dist = cross.body.position.distanceTo(this.player.body.position);
|
||||||
|
if (dist < 2) {
|
||||||
|
this.player.heal(25);
|
||||||
|
this.scene.remove(cross.body);
|
||||||
|
this.crosses.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update cooldowns
|
||||||
|
this.player.rightHandWeapon.reduceCooldowns(dt);
|
||||||
|
this.player.leftHandWeapon.reduceCooldowns(dt);
|
||||||
|
|
||||||
|
// Camera follows player
|
||||||
|
this.camera.position.set(
|
||||||
|
this.player.body.position.x,
|
||||||
|
25,
|
||||||
|
this.player.body.position.z - 15
|
||||||
|
);
|
||||||
|
this.camera.lookAt(
|
||||||
|
this.player.body.position.x,
|
||||||
|
0,
|
||||||
|
this.player.body.position.z
|
||||||
|
);
|
||||||
|
|
||||||
|
// Clean dead enemies
|
||||||
|
for (let i = this.enemies.length - 1; i >= 0; i--) {
|
||||||
|
const enemy = this.enemies[i];
|
||||||
|
if (enemy.dead) {
|
||||||
|
enemy.deathTimer += dt;
|
||||||
|
if (enemy.deathTimer > 10) {
|
||||||
|
this.scene.remove(enemy.body);
|
||||||
|
this.enemies.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateUI();
|
||||||
|
this.renderer.render(this.scene, this.camera);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeEnemy(enemy: Enemy) {
|
||||||
|
enemy.dead = true;
|
||||||
|
this.killed++;
|
||||||
|
this.spawnCross(enemy.body.position);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
import { GLTFLoader, type GLTF } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
||||||
|
|
||||||
|
const loader = new GLTFLoader();
|
||||||
|
|
||||||
|
export async function loadGLB(name: string): Promise<GLTF> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
loader.load(
|
||||||
|
`./models/${name}.glb`,
|
||||||
|
(gltf) => resolve(gltf),
|
||||||
|
undefined,
|
||||||
|
(err) => reject(err)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
import { Sword } from './Sword';
|
||||||
|
import { Staff } from './Staff';
|
||||||
|
import { loadGLB } from './MeshLoader';
|
||||||
|
import type { Game } from './Game';
|
||||||
|
|
||||||
|
export class Player {
|
||||||
|
body: THREE.Group;
|
||||||
|
leftHandWeapon: Staff;
|
||||||
|
rightHandWeapon: Sword;
|
||||||
|
velocity = new THREE.Vector3();
|
||||||
|
health = 100;
|
||||||
|
MAX_HEALTH = 100;
|
||||||
|
game!: Game;
|
||||||
|
jumpVelocity = 0;
|
||||||
|
onGround = true;
|
||||||
|
mixer!: THREE.AnimationMixer;
|
||||||
|
private clips: Map<string, THREE.AnimationAction> = new Map();
|
||||||
|
private currentAnim = '';
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.body = new THREE.Group();
|
||||||
|
|
||||||
|
// Helmet
|
||||||
|
const helmetGeom = new THREE.SphereGeometry(0.45, 8, 8, 0, Math.PI * 2, 0, Math.PI / 2);
|
||||||
|
const helmetMat = new THREE.MeshToonMaterial({ color: 0x888888 });
|
||||||
|
const helmet = new THREE.Mesh(helmetGeom, helmetMat);
|
||||||
|
helmet.position.y = 0.9;
|
||||||
|
helmet.castShadow = true;
|
||||||
|
this.body.add(helmet);
|
||||||
|
|
||||||
|
// Try loading the real helmet model (replaces the placeholder sphere)
|
||||||
|
loadGLB('helmet').then((gltf) => {
|
||||||
|
const realHelmet = gltf.scene;
|
||||||
|
const helmetTexture = new THREE.TextureLoader().load('./textures/helmet.png');
|
||||||
|
helmetTexture.flipY = false;
|
||||||
|
helmetTexture.colorSpace = THREE.SRGBColorSpace;
|
||||||
|
realHelmet.traverse((child) => {
|
||||||
|
if (child instanceof THREE.Mesh) {
|
||||||
|
child.castShadow = true;
|
||||||
|
child.material = new THREE.MeshToonMaterial({ map: helmetTexture });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
realHelmet.scale.set(0.35, 0.35, 0.35);
|
||||||
|
realHelmet.position.set(0, 0.9, -0.2);
|
||||||
|
realHelmet.rotation.set(-0.4, 0, 0);
|
||||||
|
this.body.remove(helmet);
|
||||||
|
this.body.add(realHelmet);
|
||||||
|
}).catch(() => {
|
||||||
|
// Keep placeholder helmet
|
||||||
|
});
|
||||||
|
|
||||||
|
// Shadow disc
|
||||||
|
const shadowGeom = new THREE.CircleGeometry(0.4, 16);
|
||||||
|
const shadowMat = new THREE.MeshBasicMaterial({
|
||||||
|
color: 0x000000, transparent: true, opacity: 0.3,
|
||||||
|
});
|
||||||
|
const shadow = new THREE.Mesh(shadowGeom, shadowMat);
|
||||||
|
shadow.rotation.x = -Math.PI / 2;
|
||||||
|
shadow.position.y = -0.48;
|
||||||
|
shadow.renderOrder = 999;
|
||||||
|
this.body.add(shadow);
|
||||||
|
|
||||||
|
// Weapons
|
||||||
|
this.leftHandWeapon = new Staff();
|
||||||
|
this.rightHandWeapon = new Sword();
|
||||||
|
this.body.add(this.leftHandWeapon.mesh);
|
||||||
|
this.body.add(this.rightHandWeapon.mesh);
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
try {
|
||||||
|
const gltf = await loadGLB('blob');
|
||||||
|
const model = gltf.scene;
|
||||||
|
model.scale.set(0.5, 0.5, 0.5);
|
||||||
|
model.traverse((child) => {
|
||||||
|
if (child instanceof THREE.Mesh) {
|
||||||
|
child.castShadow = true;
|
||||||
|
child.receiveShadow = true;
|
||||||
|
const mat = new THREE.MeshToonMaterial({
|
||||||
|
color: 0x44aa44,
|
||||||
|
});
|
||||||
|
child.material = mat;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.body.add(model);
|
||||||
|
|
||||||
|
if (gltf.animations.length > 0) {
|
||||||
|
this.mixer = new THREE.AnimationMixer(model);
|
||||||
|
for (const clip of gltf.animations) {
|
||||||
|
const action = this.mixer.clipAction(clip);
|
||||||
|
this.clips.set(clip.name, action);
|
||||||
|
}
|
||||||
|
this.playAnimation('stand');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Failed to load blob model, using fallback', e);
|
||||||
|
// Fallback capsule
|
||||||
|
const bodyGeom = new THREE.CapsuleGeometry(0.5, 0.5, 8, 16);
|
||||||
|
const bodyMat = new THREE.MeshToonMaterial({ color: 0x44aa44 });
|
||||||
|
const bodyMesh = new THREE.Mesh(bodyGeom, bodyMat);
|
||||||
|
bodyMesh.position.y = 0.75;
|
||||||
|
bodyMesh.castShadow = true;
|
||||||
|
this.body.add(bodyMesh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
playAnimation(name: string, loop = true, speed = 1) {
|
||||||
|
if (!this.clips.has(name)) return;
|
||||||
|
if (this.currentAnim === name) return;
|
||||||
|
// Stop the previous animation so it doesn't blend with the new one
|
||||||
|
if (this.currentAnim) {
|
||||||
|
const prev = this.clips.get(this.currentAnim);
|
||||||
|
if (prev) prev.stop();
|
||||||
|
}
|
||||||
|
this.currentAnim = name;
|
||||||
|
const action = this.clips.get(name)!;
|
||||||
|
action.reset();
|
||||||
|
action.setLoop(loop ? THREE.LoopRepeat : THREE.LoopOnce, loop ? Infinity : 1);
|
||||||
|
action.clampWhenFinished = !loop;
|
||||||
|
action.setEffectiveTimeScale(speed);
|
||||||
|
action.weight = 1;
|
||||||
|
action.play();
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
|
||||||
|
stopAnimation(name: string) {
|
||||||
|
const action = this.clips.get(name);
|
||||||
|
if (action) action.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentAnimation(): string {
|
||||||
|
return this.currentAnim;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateAnimations(dt: number) {
|
||||||
|
if (this.mixer) this.mixer.update(dt);
|
||||||
|
}
|
||||||
|
|
||||||
|
setGame(game: Game) {
|
||||||
|
this.game = game;
|
||||||
|
}
|
||||||
|
|
||||||
|
jump() {
|
||||||
|
if (this.onGround) {
|
||||||
|
this.jumpVelocity = 8;
|
||||||
|
this.onGround = false;
|
||||||
|
this.playAnimation('jump', false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switchHands() {
|
||||||
|
const temp = this.leftHandWeapon;
|
||||||
|
this.leftHandWeapon = this.rightHandWeapon as unknown as Staff;
|
||||||
|
this.rightHandWeapon = temp as unknown as Sword;
|
||||||
|
|
||||||
|
const leftPos = this.leftHandWeapon.mesh.position.clone();
|
||||||
|
const rightPos = this.rightHandWeapon.mesh.position.clone();
|
||||||
|
this.leftHandWeapon.mesh.position.copy(rightPos);
|
||||||
|
this.rightHandWeapon.mesh.position.copy(leftPos);
|
||||||
|
|
||||||
|
const leftRot = this.leftHandWeapon.mesh.rotation.clone();
|
||||||
|
const rightRot = this.rightHandWeapon.mesh.rotation.clone();
|
||||||
|
this.leftHandWeapon.mesh.rotation.copy(rightRot);
|
||||||
|
this.rightHandWeapon.mesh.rotation.copy(leftRot);
|
||||||
|
}
|
||||||
|
|
||||||
|
heal(amount: number) {
|
||||||
|
this.health = Math.min(this.health + amount, this.MAX_HEALTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
die() {
|
||||||
|
this.playAnimation('die', false);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
const audioCtx = new (window.AudioContext || (window as any).webkitAudioContext)();
|
||||||
|
|
||||||
|
const sounds: Record<string, AudioBuffer> = {};
|
||||||
|
|
||||||
|
async function loadSound(name: string): Promise<AudioBuffer> {
|
||||||
|
const response = await fetch(`./sounds/${name}.ogg`);
|
||||||
|
const arrayBuffer = await response.arrayBuffer();
|
||||||
|
return audioCtx.decodeAudioData(arrayBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function initSounds() {
|
||||||
|
const names = ['fireball', 'explosion', 'sword_hit1', 'teleport', 'damage', 'spiderwalking', 'spawn', 'gameover'];
|
||||||
|
for (const name of names) {
|
||||||
|
try {
|
||||||
|
sounds[name] = await loadSound(name);
|
||||||
|
} catch {
|
||||||
|
console.warn(`Sound ${name} not found`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resumeContext() {
|
||||||
|
if (audioCtx.state === 'suspended') {
|
||||||
|
audioCtx.resume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function playSound(name: string, volume = 1) {
|
||||||
|
const buffer = sounds[name];
|
||||||
|
if (!buffer) return;
|
||||||
|
if (audioCtx.state === 'suspended') return;
|
||||||
|
const source = audioCtx.createBufferSource();
|
||||||
|
source.buffer = buffer;
|
||||||
|
const gain = audioCtx.createGain();
|
||||||
|
gain.gain.value = volume;
|
||||||
|
source.connect(gain);
|
||||||
|
gain.connect(audioCtx.destination);
|
||||||
|
source.start(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function playLoopingSound(name: string, volume = 1): (() => void) | null {
|
||||||
|
const buffer = sounds[name];
|
||||||
|
if (!buffer) return null;
|
||||||
|
if (audioCtx.state === 'suspended') return null;
|
||||||
|
const source = audioCtx.createBufferSource();
|
||||||
|
source.buffer = buffer;
|
||||||
|
source.loop = true;
|
||||||
|
const gain = audioCtx.createGain();
|
||||||
|
gain.gain.value = volume;
|
||||||
|
source.connect(gain);
|
||||||
|
gain.connect(audioCtx.destination);
|
||||||
|
source.start(0);
|
||||||
|
return () => {
|
||||||
|
try {
|
||||||
|
source.stop();
|
||||||
|
} catch {
|
||||||
|
// already stopped
|
||||||
|
}
|
||||||
|
source.disconnect();
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
import { Weapon } from './Weapon';
|
||||||
|
import { Player } from './Player';
|
||||||
|
import { Fireball } from './Fireball';
|
||||||
|
import { playSound } from './SoundManager';
|
||||||
|
import { loadGLB } from './MeshLoader';
|
||||||
|
|
||||||
|
export class Staff extends Weapon {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.COOLDOWN1_TIME = 5;
|
||||||
|
this.COOLDOWN2_TIME = 50;
|
||||||
|
|
||||||
|
// Fallback geometry
|
||||||
|
const shaftGeom = new THREE.CylinderGeometry(0.04, 0.05, 1.0, 8);
|
||||||
|
const crystalGeom = new THREE.OctahedronGeometry(0.08);
|
||||||
|
const woodMat = new THREE.MeshToonMaterial({ color: 0x8B4513 });
|
||||||
|
const crystalMat = new THREE.MeshToonMaterial({ color: 0x4488ff });
|
||||||
|
const shaft = new THREE.Mesh(shaftGeom, woodMat);
|
||||||
|
shaft.position.y = 0.3;
|
||||||
|
shaft.castShadow = true;
|
||||||
|
const crystal = new THREE.Mesh(crystalGeom, crystalMat);
|
||||||
|
crystal.position.y = 0.9;
|
||||||
|
this.mesh.add(shaft);
|
||||||
|
this.mesh.add(crystal);
|
||||||
|
|
||||||
|
this.mesh.position.set(0.35, 0.3, 0.35);
|
||||||
|
this.mesh.scale.set(0.35, 0.35, 0.35);
|
||||||
|
this.mesh.rotation.set(-0.2, 0, -0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
try {
|
||||||
|
const gltf = await loadGLB('staff');
|
||||||
|
const model = gltf.scene;
|
||||||
|
const staffTexture = new THREE.TextureLoader().load('./textures/staff1.png');
|
||||||
|
staffTexture.flipY = false;
|
||||||
|
staffTexture.colorSpace = THREE.SRGBColorSpace;
|
||||||
|
model.traverse((child) => {
|
||||||
|
if (child instanceof THREE.Mesh) {
|
||||||
|
child.castShadow = true;
|
||||||
|
child.material = new THREE.MeshToonMaterial({ map: staffTexture });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
while (this.mesh.children.length > 0) {
|
||||||
|
this.mesh.remove(this.mesh.children[0]);
|
||||||
|
}
|
||||||
|
this.mesh.add(model);
|
||||||
|
} catch {
|
||||||
|
// Keep fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
skill1(groundPoint: THREE.Vector3, player: Player): void {
|
||||||
|
if (this.cooldown1 > 0) return;
|
||||||
|
this.cooldown1 = this.COOLDOWN1_TIME;
|
||||||
|
|
||||||
|
// Spawn at staff height, fly toward the cursor point
|
||||||
|
const spawnPos = player.body.position.clone();
|
||||||
|
spawnPos.y += 0.8;
|
||||||
|
|
||||||
|
const dir = new THREE.Vector3()
|
||||||
|
.subVectors(groundPoint, spawnPos)
|
||||||
|
.normalize()
|
||||||
|
.multiplyScalar(25);
|
||||||
|
dir.setY(0);
|
||||||
|
|
||||||
|
const fb = new Fireball(spawnPos, dir);
|
||||||
|
player.game.scene.add(fb.body);
|
||||||
|
player.game.fireballs.push(fb);
|
||||||
|
playSound('fireball', 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
skill2(groundPoint: THREE.Vector3, player: Player): void {
|
||||||
|
if (this.cooldown2 > 0) return;
|
||||||
|
this.cooldown2 = this.COOLDOWN2_TIME;
|
||||||
|
|
||||||
|
const newPos = groundPoint.clone();
|
||||||
|
newPos.y = 0.5;
|
||||||
|
newPos.x = Math.max(-47, Math.min(47, newPos.x));
|
||||||
|
newPos.z = Math.max(-47, Math.min(47, newPos.z));
|
||||||
|
player.body.position.copy(newPos);
|
||||||
|
playSound('teleport', 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
import { Weapon } from './Weapon';
|
||||||
|
import { Player } from './Player';
|
||||||
|
import { loadGLB } from './MeshLoader';
|
||||||
|
import { playSound } from './SoundManager';
|
||||||
|
|
||||||
|
export class Sword extends Weapon {
|
||||||
|
private mixer: THREE.AnimationMixer | null = null;
|
||||||
|
private clips: Map<string, THREE.AnimationAction> = new Map();
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.COOLDOWN1_TIME = 0.5;
|
||||||
|
|
||||||
|
this.mesh.position.set(-0.35, 0.3, 0.35);
|
||||||
|
this.mesh.scale.set(0.2, 0.2, 0.2);
|
||||||
|
this.mesh.rotation.set(0.3, 0, 0.3);
|
||||||
|
|
||||||
|
// Fallback geometry
|
||||||
|
const bladeGeom = new THREE.BoxGeometry(0.08, 1.0, 0.15);
|
||||||
|
const handleGeom = new THREE.CylinderGeometry(0.05, 0.05, 0.2, 8);
|
||||||
|
const guardGeom = new THREE.BoxGeometry(0.25, 0.06, 0.06);
|
||||||
|
const metalMat = new THREE.MeshStandardMaterial({ color: 0xcccccc, metalness: 0.9, roughness: 0.3 });
|
||||||
|
const darkMat = new THREE.MeshStandardMaterial({ color: 0x444444, roughness: 0.6 });
|
||||||
|
|
||||||
|
const blade = new THREE.Mesh(bladeGeom, metalMat);
|
||||||
|
blade.position.y = 0.4;
|
||||||
|
blade.castShadow = true;
|
||||||
|
const guard = new THREE.Mesh(guardGeom, metalMat);
|
||||||
|
guard.position.y = -0.1;
|
||||||
|
const handle = new THREE.Mesh(handleGeom, darkMat);
|
||||||
|
handle.position.y = -0.25;
|
||||||
|
this.mesh.add(blade);
|
||||||
|
this.mesh.add(guard);
|
||||||
|
this.mesh.add(handle);
|
||||||
|
this._fallback = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
try {
|
||||||
|
const gltf = await loadGLB('sword');
|
||||||
|
const model = gltf.scene;
|
||||||
|
model.scale.set(1, 1, 1);
|
||||||
|
const swordTexture = new THREE.TextureLoader().load('./textures/sword.png');
|
||||||
|
swordTexture.flipY = false;
|
||||||
|
swordTexture.colorSpace = THREE.SRGBColorSpace;
|
||||||
|
model.traverse((child) => {
|
||||||
|
if (child instanceof THREE.Mesh) {
|
||||||
|
child.castShadow = true;
|
||||||
|
child.material = new THREE.MeshToonMaterial({ map: swordTexture });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove fallback geometry
|
||||||
|
while (this.mesh.children.length > 0) {
|
||||||
|
this.mesh.remove(this.mesh.children[0]);
|
||||||
|
}
|
||||||
|
this._fallback = false;
|
||||||
|
this.mesh.add(model);
|
||||||
|
|
||||||
|
if (gltf.animations.length > 0) {
|
||||||
|
this.mixer = new THREE.AnimationMixer(model);
|
||||||
|
for (const clip of gltf.animations) {
|
||||||
|
const action = this.mixer.clipAction(clip);
|
||||||
|
this.clips.set(clip.name, action);
|
||||||
|
}
|
||||||
|
const normal = this.clips.get('normal');
|
||||||
|
if (normal) {
|
||||||
|
normal.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Failed to load sword model', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _fallback = false;
|
||||||
|
|
||||||
|
updateAnimation(dt: number) {
|
||||||
|
if (this.mixer) this.mixer.update(dt);
|
||||||
|
}
|
||||||
|
|
||||||
|
skill1(_groundPoint: THREE.Vector3, player: Player): void {
|
||||||
|
if (this.cooldown1 > 0) return;
|
||||||
|
this.cooldown1 = this.COOLDOWN1_TIME;
|
||||||
|
|
||||||
|
playSound('sword_hit1', 0.5);
|
||||||
|
|
||||||
|
// Play skill1 animation
|
||||||
|
const action = this.clips.get('skill1');
|
||||||
|
if (action) {
|
||||||
|
action.reset();
|
||||||
|
action.setLoop(THREE.LoopOnce, 1);
|
||||||
|
action.setEffectiveTimeScale(2);
|
||||||
|
action.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
const { game } = player;
|
||||||
|
|
||||||
|
for (const enemy of game.enemies) {
|
||||||
|
if (enemy.dead) continue;
|
||||||
|
const dist = enemy.body.position.distanceTo(player.body.position);
|
||||||
|
if (dist < 3) {
|
||||||
|
const toEnemy = new THREE.Vector3()
|
||||||
|
.subVectors(enemy.body.position, player.body.position).normalize();
|
||||||
|
const playerFwd = new THREE.Vector3(0, 0, 1);
|
||||||
|
playerFwd.applyQuaternion(player.body.quaternion);
|
||||||
|
const dot = playerFwd.dot(toEnemy);
|
||||||
|
if (dot > 0.3) {
|
||||||
|
enemy.takeDamage(50, game);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
skill2(): void {
|
||||||
|
// Not implemented
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
import { Player } from './Player';
|
||||||
|
|
||||||
|
export abstract class Weapon {
|
||||||
|
mesh: THREE.Group;
|
||||||
|
cooldown1 = 0;
|
||||||
|
cooldown2 = 0;
|
||||||
|
cooldown3 = 0;
|
||||||
|
COOLDOWN1_TIME = 0;
|
||||||
|
COOLDOWN2_TIME = 0;
|
||||||
|
COOLDOWN3_TIME = 0;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.mesh = new THREE.Group();
|
||||||
|
}
|
||||||
|
|
||||||
|
reduceCooldowns(tpf: number) {
|
||||||
|
if (this.cooldown1 > 0) this.cooldown1 -= tpf;
|
||||||
|
if (this.cooldown2 > 0) this.cooldown2 -= tpf;
|
||||||
|
if (this.cooldown3 > 0) this.cooldown3 -= tpf;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract skill1(groundPoint: THREE.Vector3, player: Player): void;
|
||||||
|
abstract skill2(groundPoint: THREE.Vector3, player: Player): void;
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { Game } from './Game';
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const game = new Game();
|
||||||
|
await game.init();
|
||||||
|
game.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
declare module '*.glb' {
|
||||||
|
const src: string;
|
||||||
|
export default src;
|
||||||
|
}
|
||||||
|
declare module '*.gltf' {
|
||||||
|
const src: string;
|
||||||
|
export default src;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"outDir": "./dist",
|
||||||
|
"rootDir": "./src",
|
||||||
|
"declaration": true,
|
||||||
|
"sourceMap": true
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { defineConfig } from 'vite';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
base: './',
|
||||||
|
build: {
|
||||||
|
outDir: 'dist',
|
||||||
|
assetsDir: 'assets',
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
host: true,
|
||||||
|
port: 3000,
|
||||||
|
},
|
||||||
|
});
|
||||||