Add game source, converter pipeline, and web port

This commit is contained in:
2026-08-12 18:59:06 +02:00
parent 3e6635eac6
commit bd13ad67cc
97 changed files with 8781 additions and 0 deletions
+86
View File
@@ -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()
+112
View File
@@ -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"});
}
}
+68
View File
@@ -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);
}
}
+89
View File
@@ -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());
}
}
+150
View File
@@ -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());
}
}
+62
View File
@@ -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();
}
}
+122
View File
@@ -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"});
}
}
+78
View File
@@ -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);
}
}
+175
View File
@@ -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));
}
}
+199
View File
@@ -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);
}
}
+67
View File
@@ -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);
}
}
+52
View File
@@ -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);
}
}
+48
View File
@@ -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);
}
}
+522
View File
@@ -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
}
}
}
+117
View File
@@ -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);
}
}
+206
View File
@@ -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 {
}
+31
View File
@@ -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();
}
+106
View File
@@ -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);
}
}
+31
View File
@@ -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) {
}
}
+101
View File
@@ -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);
}
}
}
+181
View File
@@ -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;
}
}
+89
View File
@@ -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) {
}
}
+38
View File
@@ -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();
}
+15
View File
@@ -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();
}
}
+150
View File
@@ -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);
}
}
+92
View File
@@ -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();
}
}
+32
View File
@@ -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) {
}
}
+76
View File
@@ -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);
}
}
}
+91
View File
@@ -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() {
}
}
+152
View File
@@ -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) {
}
}
+73
View File
@@ -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();
}
+44
View File
@@ -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);
}
}
+49
View File
@@ -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);
}
}
+392
View File
@@ -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();
}
}
+462
View File
@@ -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()
+822
View File
@@ -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()
+485
View File
@@ -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()
+117
View File
@@ -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>
+1246
View File
File diff suppressed because it is too large Load Diff
+19
View File
@@ -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"
}
}
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
+1
View File
@@ -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":{}}
Binary file not shown.
+1
View File
@@ -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":{}}
Binary file not shown.
+1
View File
@@ -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":{}}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 333 KiB

+50
View File
@@ -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);
}
}
+125
View File
@@ -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;
}
}
+137
View File
@@ -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);
}
}
+253
View File
@@ -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();
}
}
+585
View File
@@ -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);
}
}
+15
View File
@@ -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)
);
});
}
+175
View File
@@ -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);
}
}
+61
View File
@@ -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();
};
}
+85
View File
@@ -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);
}
}
+119
View File
@@ -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
}
}
+25
View File
@@ -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;
}
+9
View File
@@ -0,0 +1,9 @@
import { Game } from './Game';
async function main() {
const game = new Game();
await game.init();
game.start();
}
main();
+8
View File
@@ -0,0 +1,8 @@
declare module '*.glb' {
const src: string;
export default src;
}
declare module '*.gltf' {
const src: string;
export default src;
}
+16
View File
@@ -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"]
}
+13
View File
@@ -0,0 +1,13 @@
import { defineConfig } from 'vite';
export default defineConfig({
base: './',
build: {
outDir: 'dist',
assetsDir: 'assets',
},
server: {
host: true,
port: 3000,
},
});