Add game source, converter pipeline, and web port
@@ -0,0 +1,86 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Convert Ogre3D .mesh.xml to Three.js geometry modules."""
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
def parse_mesh_xml(filepath):
|
||||
tree = ET.parse(filepath)
|
||||
root = tree.getroot()
|
||||
|
||||
sharedgeom = root.find('sharedgeometry')
|
||||
vertices = []
|
||||
if sharedgeom is not None:
|
||||
for vb in sharedgeom.findall('vertexbuffer'):
|
||||
for vertex in vb.findall('vertex'):
|
||||
pos = vertex.find('position')
|
||||
normal = vertex.find('normal')
|
||||
v = {}
|
||||
if pos is not None:
|
||||
v['x'] = float(pos.get('x', 0))
|
||||
v['y'] = float(pos.get('y', 0))
|
||||
v['z'] = float(pos.get('z', 0))
|
||||
if normal is not None:
|
||||
v['nx'] = float(normal.get('x', 0))
|
||||
v['ny'] = float(normal.get('y', 0))
|
||||
v['nz'] = float(normal.get('z', 0))
|
||||
vertices.append(v)
|
||||
|
||||
faces = []
|
||||
for submeshes in root.findall('submeshes'):
|
||||
for submesh in submeshes.findall('submesh'):
|
||||
for faces_elem in submesh.findall('faces'):
|
||||
for face in faces_elem.findall('face'):
|
||||
v1 = int(face.get('v1', 0))
|
||||
v2 = int(face.get('v2', 0))
|
||||
v3 = int(face.get('v3', 0))
|
||||
faces.append((v1, v2, v3))
|
||||
|
||||
return vertices, faces
|
||||
|
||||
|
||||
def main():
|
||||
base_dir = '/var/home/nico/Gamedev/Wackelpeter/extracted/Models'
|
||||
output_dir = '/var/home/nico/Gamedev/Wackelpeter/web/public/models'
|
||||
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
for model_name in ['blob/Blob', 'sword/sword', 'shield/shield']:
|
||||
filepath = os.path.join(base_dir, f'{model_name}.mesh.xml')
|
||||
if not os.path.exists(filepath):
|
||||
print(f'Skipping {filepath} - not found')
|
||||
continue
|
||||
|
||||
print(f'Converting {model_name}...')
|
||||
vertices, faces = parse_mesh_xml(filepath)
|
||||
|
||||
positions = []
|
||||
normals = []
|
||||
indices = []
|
||||
|
||||
for v in vertices:
|
||||
positions.extend([v.get('x', 0), v.get('y', 0), v.get('z', 0)])
|
||||
normals.extend([v.get('nx', 0), v.get('ny', 0), v.get('nz', 0)])
|
||||
|
||||
for f in faces:
|
||||
indices.extend(list(f))
|
||||
|
||||
model_data = {
|
||||
'positions': positions,
|
||||
'normals': normals,
|
||||
'indices': indices,
|
||||
'vertexCount': len(vertices),
|
||||
'faceCount': len(faces),
|
||||
}
|
||||
|
||||
name = os.path.basename(model_name).lower()
|
||||
outpath = os.path.join(output_dir, f'{name}.json')
|
||||
with open(outpath, 'w') as f:
|
||||
json.dump(model_data, f)
|
||||
|
||||
print(f' -> {outpath} ({len(vertices)} vertices, {len(faces)} faces)')
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* com.jme3.animation.AnimChannel
|
||||
* com.jme3.animation.AnimControl
|
||||
* com.jme3.animation.AnimEventListener
|
||||
* com.jme3.animation.LoopMode
|
||||
* com.jme3.app.SimpleApplication
|
||||
* com.jme3.input.controls.ActionListener
|
||||
* com.jme3.input.controls.InputListener
|
||||
* com.jme3.input.controls.KeyTrigger
|
||||
* com.jme3.input.controls.MouseButtonTrigger
|
||||
* com.jme3.input.controls.Trigger
|
||||
* com.jme3.light.DirectionalLight
|
||||
* com.jme3.light.Light
|
||||
* com.jme3.material.Material
|
||||
* com.jme3.math.ColorRGBA
|
||||
* com.jme3.math.Vector3f
|
||||
* com.jme3.scene.Node
|
||||
* com.jme3.scene.Spatial
|
||||
* com.jme3.scene.debug.SkeletonDebugger
|
||||
*/
|
||||
package jme3.hello;
|
||||
|
||||
import com.jme3.animation.AnimChannel;
|
||||
import com.jme3.animation.AnimControl;
|
||||
import com.jme3.animation.AnimEventListener;
|
||||
import com.jme3.animation.LoopMode;
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.input.controls.ActionListener;
|
||||
import com.jme3.input.controls.InputListener;
|
||||
import com.jme3.input.controls.KeyTrigger;
|
||||
import com.jme3.input.controls.MouseButtonTrigger;
|
||||
import com.jme3.input.controls.Trigger;
|
||||
import com.jme3.light.DirectionalLight;
|
||||
import com.jme3.light.Light;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.debug.SkeletonDebugger;
|
||||
|
||||
public class HelloAnimation
|
||||
extends SimpleApplication
|
||||
implements AnimEventListener {
|
||||
private AnimChannel channel;
|
||||
private AnimControl control;
|
||||
Node player;
|
||||
private ActionListener actionListener = new ActionListener(){
|
||||
|
||||
public void onAction(String name, boolean keyPressed, float tpf) {
|
||||
if (name.equals("Walk") && !keyPressed && !HelloAnimation.this.channel.getAnimationName().equals("Walk")) {
|
||||
HelloAnimation.this.channel.setAnim("Walk", 0.5f);
|
||||
HelloAnimation.this.channel.setLoopMode(LoopMode.Loop);
|
||||
}
|
||||
if (name.equals("Dodge") && !keyPressed && !HelloAnimation.this.channel.getAnimationName().equals("Push")) {
|
||||
HelloAnimation.this.channel.setAnim("push", 1.0f);
|
||||
HelloAnimation.this.channel.setLoopMode(LoopMode.DontLoop);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public static void main(String[] agrs) {
|
||||
HelloAnimation app = new HelloAnimation();
|
||||
app.start();
|
||||
}
|
||||
|
||||
public void simpleInitApp() {
|
||||
this.viewPort.setBackgroundColor(ColorRGBA.LightGray);
|
||||
this.initKeys();
|
||||
DirectionalLight dl = new DirectionalLight();
|
||||
dl.setDirection(new Vector3f(-0.1f, -1.0f, -1.0f).normalizeLocal());
|
||||
this.rootNode.addLight((Light)dl);
|
||||
this.player = (Node)this.assetManager.loadModel("Models/Oto/Oto.mesh.xml");
|
||||
this.player.setLocalScale(0.5f);
|
||||
this.rootNode.attachChild((Spatial)this.player);
|
||||
this.control = (AnimControl)this.player.getControl(AnimControl.class);
|
||||
this.control.addListener((AnimEventListener)this);
|
||||
this.channel = this.control.createChannel();
|
||||
this.channel.setAnim("stand");
|
||||
SkeletonDebugger skeletonDebug = new SkeletonDebugger("skeleton", this.control.getSkeleton());
|
||||
Material mat = new Material(this.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat.setColor("Color", ColorRGBA.Green);
|
||||
mat.getAdditionalRenderState().setDepthTest(false);
|
||||
skeletonDebug.setMaterial(mat);
|
||||
this.player.attachChild((Spatial)skeletonDebug);
|
||||
}
|
||||
|
||||
public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) {
|
||||
if (animName.equals("Walk")) {
|
||||
channel.setAnim("stand", 0.5f);
|
||||
channel.setLoopMode(LoopMode.DontLoop);
|
||||
channel.setSpeed(1.0f);
|
||||
}
|
||||
if (animName.equals("push")) {
|
||||
channel.setAnim("stand", 1.0f);
|
||||
channel.setLoopMode(LoopMode.DontLoop);
|
||||
channel.setSpeed(1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public void onAnimChange(AnimControl control, AnimChannel channel, String animName) {
|
||||
}
|
||||
|
||||
private void initKeys() {
|
||||
this.inputManager.addMapping("Walk", new Trigger[]{new KeyTrigger(57)});
|
||||
this.inputManager.addMapping("Dodge", new Trigger[]{new MouseButtonTrigger(0)});
|
||||
this.inputManager.addListener((InputListener)this.actionListener, new String[]{"Walk", "Dodge"});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* com.jme3.app.SimpleApplication
|
||||
* com.jme3.font.BitmapText
|
||||
* com.jme3.light.DirectionalLight
|
||||
* com.jme3.light.Light
|
||||
* com.jme3.material.Material
|
||||
* com.jme3.math.Vector3f
|
||||
* com.jme3.scene.Geometry
|
||||
* com.jme3.scene.Mesh
|
||||
* com.jme3.scene.Spatial
|
||||
* com.jme3.scene.shape.Box
|
||||
*/
|
||||
package jme3.hello;
|
||||
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.font.BitmapText;
|
||||
import com.jme3.light.DirectionalLight;
|
||||
import com.jme3.light.Light;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Mesh;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.shape.Box;
|
||||
|
||||
public class HelloAssets
|
||||
extends SimpleApplication {
|
||||
public static void main(String[] args) {
|
||||
HelloAssets app = new HelloAssets();
|
||||
app.start();
|
||||
}
|
||||
|
||||
public void simpleInitApp() {
|
||||
Spatial teapot = this.assetManager.loadModel("Models/Teapot/Teapot.obj");
|
||||
Material mat_default = new Material(this.assetManager, "Common/MatDefs/Misc/ShowNormals.j3md");
|
||||
teapot.setMaterial(mat_default);
|
||||
this.rootNode.attachChild(teapot);
|
||||
Box box = new Box(Vector3f.ZERO, 2.5f, 2.5f, 1.0f);
|
||||
Geometry wall = new Geometry("Box", (Mesh)box);
|
||||
Material mat_brick = new Material(this.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat_brick.setTexture("ColorMap", this.assetManager.loadTexture("Textures/Terrain/BrickWall/BrickWall.jpg"));
|
||||
wall.setMaterial(mat_brick);
|
||||
wall.setLocalTranslation(2.0f, -2.5f, 0.0f);
|
||||
this.rootNode.attachChild((Spatial)wall);
|
||||
this.guiNode.detachAllChildren();
|
||||
this.guiFont = this.assetManager.loadFont("Interface/Fonts/Default.fnt");
|
||||
BitmapText helloText = new BitmapText(this.guiFont, false);
|
||||
helloText.setSize((float)this.guiFont.getCharSet().getRenderedSize());
|
||||
helloText.setText("Hello World");
|
||||
helloText.setLocalTranslation(300.0f, helloText.getLineHeight(), 0.0f);
|
||||
this.guiNode.attachChild((Spatial)helloText);
|
||||
Spatial ninja = this.assetManager.loadModel("Models/Ninja/Ninja.mesh.xml");
|
||||
ninja.scale(0.05f, 0.05f, 0.05f);
|
||||
ninja.rotate(0.0f, -3.0f, 0.0f);
|
||||
ninja.setLocalTranslation(0.0f, -5.0f, -2.0f);
|
||||
this.rootNode.attachChild(ninja);
|
||||
DirectionalLight sun = new DirectionalLight();
|
||||
sun.setDirection(new Vector3f(-0.1f, -0.7f, -1.0f));
|
||||
this.rootNode.addLight((Light)sun);
|
||||
Spatial gameLevel = this.assetManager.loadModel("Scenes/main.j3o");
|
||||
gameLevel.setLocalTranslation(0.0f, -5.2f, 0.0f);
|
||||
gameLevel.setLocalScale(2.0f);
|
||||
this.rootNode.attachChild(gameLevel);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* com.jme3.app.SimpleApplication
|
||||
* com.jme3.audio.AudioNode
|
||||
* com.jme3.input.controls.ActionListener
|
||||
* com.jme3.input.controls.InputListener
|
||||
* com.jme3.input.controls.MouseButtonTrigger
|
||||
* com.jme3.input.controls.Trigger
|
||||
* com.jme3.material.Material
|
||||
* com.jme3.math.ColorRGBA
|
||||
* com.jme3.math.Vector3f
|
||||
* com.jme3.scene.Geometry
|
||||
* com.jme3.scene.Mesh
|
||||
* com.jme3.scene.Spatial
|
||||
* com.jme3.scene.shape.Box
|
||||
*/
|
||||
package jme3.hello;
|
||||
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.audio.AudioNode;
|
||||
import com.jme3.input.controls.ActionListener;
|
||||
import com.jme3.input.controls.InputListener;
|
||||
import com.jme3.input.controls.MouseButtonTrigger;
|
||||
import com.jme3.input.controls.Trigger;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Mesh;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.shape.Box;
|
||||
|
||||
public class HelloAudio
|
||||
extends SimpleApplication {
|
||||
private AudioNode audio_gun;
|
||||
private AudioNode audio_nature;
|
||||
private Geometry player;
|
||||
private ActionListener actionListener = new ActionListener(){
|
||||
|
||||
public void onAction(String name, boolean keyPressed, float tpf) {
|
||||
if (name.equals("Shoot") && !keyPressed) {
|
||||
HelloAudio.this.audio_gun.playInstance();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public static void main(String[] args) {
|
||||
HelloAudio app = new HelloAudio();
|
||||
app.start();
|
||||
}
|
||||
|
||||
public void simpleInitApp() {
|
||||
this.flyCam.setMoveSpeed(40.0f);
|
||||
Box box1 = new Box(Vector3f.ZERO, 1.0f, 1.0f, 1.0f);
|
||||
this.player = new Geometry("Player", (Mesh)box1);
|
||||
Material mat1 = new Material(this.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat1.setColor("Color", ColorRGBA.Blue);
|
||||
this.player.setMaterial(mat1);
|
||||
this.rootNode.attachChild((Spatial)this.player);
|
||||
this.initKeys();
|
||||
this.initAudio();
|
||||
}
|
||||
|
||||
public void initAudio() {
|
||||
this.audio_gun = new AudioNode(this.assetManager, "Sound/Effects/Gun.wav", false);
|
||||
this.audio_gun.setLooping(false);
|
||||
this.audio_gun.setVolume(2.0f);
|
||||
this.rootNode.attachChild((Spatial)this.audio_gun);
|
||||
this.audio_nature = new AudioNode(this.assetManager, "Sound/Environment/Nature.ogg", false);
|
||||
this.audio_nature.setLooping(true);
|
||||
this.audio_nature.setPositional(false);
|
||||
this.audio_nature.setLocalTranslation(Vector3f.ZERO.clone());
|
||||
this.audio_nature.setVolume(3.0f);
|
||||
this.rootNode.attachChild((Spatial)this.audio_nature);
|
||||
this.audio_nature.play();
|
||||
}
|
||||
|
||||
private void initKeys() {
|
||||
this.inputManager.addMapping("Shoot", new Trigger[]{new MouseButtonTrigger(0)});
|
||||
this.inputManager.addListener((InputListener)this.actionListener, new String[]{"Shoot"});
|
||||
}
|
||||
|
||||
public void simpleUpdate(float tpf) {
|
||||
this.listener.setLocation(this.cam.getLocation());
|
||||
this.listener.setRotation(this.cam.getRotation());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* com.jme3.app.SimpleApplication
|
||||
* com.jme3.app.state.AppState
|
||||
* com.jme3.asset.plugins.ZipLocator
|
||||
* com.jme3.bullet.BulletAppState
|
||||
* com.jme3.bullet.collision.shapes.CapsuleCollisionShape
|
||||
* com.jme3.bullet.collision.shapes.CollisionShape
|
||||
* com.jme3.bullet.control.CharacterControl
|
||||
* com.jme3.bullet.control.RigidBodyControl
|
||||
* com.jme3.bullet.util.CollisionShapeFactory
|
||||
* com.jme3.input.controls.ActionListener
|
||||
* com.jme3.input.controls.InputListener
|
||||
* com.jme3.input.controls.KeyTrigger
|
||||
* com.jme3.input.controls.Trigger
|
||||
* com.jme3.light.AmbientLight
|
||||
* com.jme3.light.DirectionalLight
|
||||
* com.jme3.light.Light
|
||||
* com.jme3.math.ColorRGBA
|
||||
* com.jme3.math.Vector3f
|
||||
* com.jme3.scene.Node
|
||||
* com.jme3.scene.Spatial
|
||||
* com.jme3.scene.control.Control
|
||||
*/
|
||||
package jme3.hello;
|
||||
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.app.state.AppState;
|
||||
import com.jme3.asset.plugins.ZipLocator;
|
||||
import com.jme3.bullet.BulletAppState;
|
||||
import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
|
||||
import com.jme3.bullet.collision.shapes.CollisionShape;
|
||||
import com.jme3.bullet.control.CharacterControl;
|
||||
import com.jme3.bullet.control.RigidBodyControl;
|
||||
import com.jme3.bullet.util.CollisionShapeFactory;
|
||||
import com.jme3.input.controls.ActionListener;
|
||||
import com.jme3.input.controls.InputListener;
|
||||
import com.jme3.input.controls.KeyTrigger;
|
||||
import com.jme3.input.controls.Trigger;
|
||||
import com.jme3.light.AmbientLight;
|
||||
import com.jme3.light.DirectionalLight;
|
||||
import com.jme3.light.Light;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.control.Control;
|
||||
|
||||
public class HelloCollision
|
||||
extends SimpleApplication
|
||||
implements ActionListener {
|
||||
private Spatial sceneModel;
|
||||
private BulletAppState bulletAppState;
|
||||
private RigidBodyControl landscape;
|
||||
private CharacterControl player;
|
||||
private Vector3f walkDirection = new Vector3f();
|
||||
private boolean left = false;
|
||||
private boolean right = false;
|
||||
private boolean up = false;
|
||||
private boolean down = false;
|
||||
|
||||
public static void main(String[] args) {
|
||||
HelloCollision app = new HelloCollision();
|
||||
app.start();
|
||||
}
|
||||
|
||||
public void simpleInitApp() {
|
||||
this.bulletAppState = new BulletAppState();
|
||||
this.stateManager.attach((AppState)this.bulletAppState);
|
||||
this.bulletAppState.getPhysicsSpace().enableDebug(this.assetManager);
|
||||
this.viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1.0f, 1.0f));
|
||||
this.flyCam.setMoveSpeed(100.0f);
|
||||
this.setUpKeys();
|
||||
this.setUpLight();
|
||||
this.assetManager.registerLocator("town.zip", ZipLocator.class);
|
||||
this.sceneModel = this.assetManager.loadModel("main.scene");
|
||||
this.sceneModel.setLocalScale(2.0f);
|
||||
CollisionShape sceneShape = CollisionShapeFactory.createMeshShape((Spatial)((Node)this.sceneModel));
|
||||
this.landscape = new RigidBodyControl(sceneShape, 0.0f);
|
||||
this.sceneModel.addControl((Control)this.landscape);
|
||||
CapsuleCollisionShape capsuleShape = new CapsuleCollisionShape(1.5f, 6.0f, 1);
|
||||
this.player = new CharacterControl((CollisionShape)capsuleShape, 0.5f);
|
||||
this.player.setJumpSpeed(20.0f);
|
||||
this.player.setFallSpeed(30.0f);
|
||||
this.player.setGravity(30.0f);
|
||||
this.player.setPhysicsLocation(new Vector3f(0.0f, 10.0f, 0.0f));
|
||||
this.rootNode.attachChild(this.sceneModel);
|
||||
this.bulletAppState.getPhysicsSpace().add((Object)this.landscape);
|
||||
this.bulletAppState.getPhysicsSpace().add((Object)this.player);
|
||||
}
|
||||
|
||||
private void setUpLight() {
|
||||
AmbientLight al = new AmbientLight();
|
||||
al.setColor(ColorRGBA.White.mult(1.3f));
|
||||
this.rootNode.addLight((Light)al);
|
||||
DirectionalLight dl = new DirectionalLight();
|
||||
dl.setColor(ColorRGBA.White);
|
||||
dl.setDirection(new Vector3f(2.8f, -2.8f, -2.8f).normalizeLocal());
|
||||
this.rootNode.addLight((Light)dl);
|
||||
}
|
||||
|
||||
private void setUpKeys() {
|
||||
this.inputManager.addMapping("Left", new Trigger[]{new KeyTrigger(30)});
|
||||
this.inputManager.addMapping("Right", new Trigger[]{new KeyTrigger(32)});
|
||||
this.inputManager.addMapping("Up", new Trigger[]{new KeyTrigger(17)});
|
||||
this.inputManager.addMapping("Down", new Trigger[]{new KeyTrigger(31)});
|
||||
this.inputManager.addMapping("Jump", new Trigger[]{new KeyTrigger(57)});
|
||||
this.inputManager.addListener((InputListener)this, new String[]{"Left"});
|
||||
this.inputManager.addListener((InputListener)this, new String[]{"Right"});
|
||||
this.inputManager.addListener((InputListener)this, new String[]{"Up"});
|
||||
this.inputManager.addListener((InputListener)this, new String[]{"Down"});
|
||||
this.inputManager.addListener((InputListener)this, new String[]{"Jump"});
|
||||
}
|
||||
|
||||
public void onAction(String binding, boolean value, float tpf) {
|
||||
if (binding.equals("Left")) {
|
||||
this.left = value;
|
||||
} else if (binding.equals("Right")) {
|
||||
this.right = value;
|
||||
} else if (binding.equals("Up")) {
|
||||
this.up = value;
|
||||
} else if (binding.equals("Down")) {
|
||||
this.down = value;
|
||||
} else if (binding.equals("Jump")) {
|
||||
this.player.jump();
|
||||
}
|
||||
}
|
||||
|
||||
public void simpleUpdate(float tpf) {
|
||||
Vector3f camDir = this.cam.getDirection().clone().multLocal(0.6f);
|
||||
Vector3f camLeft = this.cam.getLeft().clone().multLocal(0.4f);
|
||||
this.walkDirection.set(0.0f, 0.0f, 0.0f);
|
||||
if (this.left) {
|
||||
this.walkDirection.addLocal(camLeft);
|
||||
}
|
||||
if (this.right) {
|
||||
this.walkDirection.addLocal(camLeft.negate());
|
||||
}
|
||||
if (this.up) {
|
||||
this.walkDirection.addLocal(camDir);
|
||||
}
|
||||
if (this.down) {
|
||||
this.walkDirection.addLocal(camDir.negate());
|
||||
}
|
||||
this.player.setWalkDirection(this.walkDirection);
|
||||
this.cam.setLocation(this.player.getPhysicsLocation());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* com.jme3.app.SimpleApplication
|
||||
* com.jme3.effect.ParticleEmitter
|
||||
* com.jme3.effect.ParticleMesh$Type
|
||||
* com.jme3.material.Material
|
||||
* com.jme3.math.ColorRGBA
|
||||
* com.jme3.math.Vector3f
|
||||
* com.jme3.scene.Spatial
|
||||
*/
|
||||
package jme3.hello;
|
||||
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.effect.ParticleEmitter;
|
||||
import com.jme3.effect.ParticleMesh;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Spatial;
|
||||
|
||||
public class HelloEffects
|
||||
extends SimpleApplication {
|
||||
public static void main(String[] args) {
|
||||
HelloEffects app = new HelloEffects();
|
||||
app.start();
|
||||
}
|
||||
|
||||
public void simpleInitApp() {
|
||||
ParticleEmitter fire = new ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, 30);
|
||||
Material mat_red = new Material(this.assetManager, "Common/MatDefs/Misc/Particle.j3md");
|
||||
mat_red.setTexture("Texture", this.assetManager.loadTexture("Effects/Explosion/flame.png"));
|
||||
fire.setMaterial(mat_red);
|
||||
fire.setImagesX(2);
|
||||
fire.setImagesY(2);
|
||||
fire.setEndColor(ColorRGBA.Cyan);
|
||||
fire.setStartColor(ColorRGBA.Blue);
|
||||
fire.getParticleInfluencer().setInitialVelocity(new Vector3f(0.0f, -2.0f, 0.0f));
|
||||
fire.setStartSize(0.1f);
|
||||
fire.setEndSize(1.5f);
|
||||
fire.setGravity(0.0f, 1.0f, 0.0f);
|
||||
fire.setLowLife(1.0f);
|
||||
fire.setHighLife(3.0f);
|
||||
fire.getParticleInfluencer().setVelocityVariation(0.3f);
|
||||
this.rootNode.attachChild((Spatial)fire);
|
||||
ParticleEmitter debris = new ParticleEmitter("Debris", ParticleMesh.Type.Triangle, 10);
|
||||
Material debris_mat = new Material(this.assetManager, "Common/MatDefs/Misc/Particle.j3md");
|
||||
debris_mat.setTexture("Texture", this.assetManager.loadTexture("Effects/Explosion/Debris.png"));
|
||||
debris.setMaterial(debris_mat);
|
||||
debris.setImagesX(3);
|
||||
debris.setImagesY(3);
|
||||
debris.setRotateSpeed(4.0f);
|
||||
debris.setSelectRandomImage(true);
|
||||
debris.getParticleInfluencer().setInitialVelocity(new Vector3f(0.0f, 4.0f, 0.0f));
|
||||
debris.setStartColor(ColorRGBA.White);
|
||||
debris.setGravity(0.0f, 6.0f, 0.0f);
|
||||
debris.getParticleInfluencer().setVelocityVariation(0.6f);
|
||||
this.rootNode.attachChild((Spatial)debris);
|
||||
debris.emitAllParticles();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* com.jme3.app.SimpleApplication
|
||||
* com.jme3.input.controls.ActionListener
|
||||
* com.jme3.input.controls.AnalogListener
|
||||
* com.jme3.input.controls.InputListener
|
||||
* com.jme3.input.controls.KeyTrigger
|
||||
* com.jme3.input.controls.MouseAxisTrigger
|
||||
* com.jme3.input.controls.MouseButtonTrigger
|
||||
* com.jme3.input.controls.Trigger
|
||||
* com.jme3.light.DirectionalLight
|
||||
* com.jme3.light.Light
|
||||
* com.jme3.material.Material
|
||||
* com.jme3.math.ColorRGBA
|
||||
* com.jme3.math.Vector3f
|
||||
* com.jme3.scene.Geometry
|
||||
* com.jme3.scene.Mesh
|
||||
* com.jme3.scene.Spatial
|
||||
* com.jme3.scene.shape.Box
|
||||
*/
|
||||
package jme3.hello;
|
||||
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.input.controls.ActionListener;
|
||||
import com.jme3.input.controls.AnalogListener;
|
||||
import com.jme3.input.controls.InputListener;
|
||||
import com.jme3.input.controls.KeyTrigger;
|
||||
import com.jme3.input.controls.MouseAxisTrigger;
|
||||
import com.jme3.input.controls.MouseButtonTrigger;
|
||||
import com.jme3.input.controls.Trigger;
|
||||
import com.jme3.light.DirectionalLight;
|
||||
import com.jme3.light.Light;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Mesh;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.shape.Box;
|
||||
|
||||
public class HelloInput
|
||||
extends SimpleApplication {
|
||||
protected Geometry player;
|
||||
boolean isRunning = true;
|
||||
private ActionListener actionListener = new ActionListener(){
|
||||
|
||||
public void onAction(String name, boolean keyPressed, float tpf) {
|
||||
if (name.equals("Pause") && !keyPressed) {
|
||||
HelloInput.this.isRunning = !HelloInput.this.isRunning;
|
||||
}
|
||||
}
|
||||
};
|
||||
private AnalogListener analogListener = new AnalogListener(){
|
||||
|
||||
public void onAnalog(String name, float value, float tpf) {
|
||||
if (HelloInput.this.isRunning) {
|
||||
Vector3f v;
|
||||
if (name.equals("RotateLeft")) {
|
||||
HelloInput.this.player.rotate(0.0f, value * HelloInput.this.speed, 0.0f);
|
||||
}
|
||||
if (name.equals("RotateRight")) {
|
||||
HelloInput.this.player.rotate(0.0f, value * -HelloInput.this.speed, 0.0f);
|
||||
}
|
||||
if (name.equals("Right")) {
|
||||
v = HelloInput.this.player.getLocalTranslation();
|
||||
HelloInput.this.player.setLocalTranslation(v.x + value * HelloInput.this.speed, v.y, v.z);
|
||||
}
|
||||
if (name.equals("Left")) {
|
||||
v = HelloInput.this.player.getLocalTranslation();
|
||||
HelloInput.this.player.setLocalTranslation(v.x - value * HelloInput.this.speed, v.y, v.z);
|
||||
}
|
||||
if (name.equals("Up")) {
|
||||
v = HelloInput.this.player.getLocalTranslation();
|
||||
HelloInput.this.player.setLocalTranslation(v.x, v.y + value * HelloInput.this.speed, v.z);
|
||||
}
|
||||
if (name.equals("Down")) {
|
||||
v = HelloInput.this.player.getLocalTranslation();
|
||||
HelloInput.this.player.setLocalTranslation(v.x, v.y - value * HelloInput.this.speed, v.z);
|
||||
}
|
||||
} else {
|
||||
System.out.println("Press P to unpause.");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public static void main(String[] args) {
|
||||
HelloInput app = new HelloInput();
|
||||
app.start();
|
||||
}
|
||||
|
||||
public void simpleInitApp() {
|
||||
Box b = new Box(Vector3f.ZERO, 1.0f, 1.0f, 1.0f);
|
||||
this.player = new Geometry("Player", (Mesh)b);
|
||||
Material mat = new Material(this.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat.setColor("Color", ColorRGBA.randomColor());
|
||||
this.player.setMaterial(mat);
|
||||
this.rootNode.attachChild((Spatial)this.player);
|
||||
this.initKeys();
|
||||
Spatial gameLevel = this.assetManager.loadModel("Scenes/main.j3o");
|
||||
gameLevel.setLocalTranslation(0.0f, -5.2f, 0.0f);
|
||||
gameLevel.setLocalScale(2.0f);
|
||||
this.rootNode.attachChild(gameLevel);
|
||||
DirectionalLight sun = new DirectionalLight();
|
||||
sun.setColor(ColorRGBA.White);
|
||||
sun.setDirection(new Vector3f(-0.5f, -0.5f, -0.5f).normalizeLocal());
|
||||
this.rootNode.addLight((Light)sun);
|
||||
}
|
||||
|
||||
private void initKeys() {
|
||||
this.inputManager.addMapping("Pause", new Trigger[]{new KeyTrigger(25)});
|
||||
this.inputManager.addMapping("Left", new Trigger[]{new KeyTrigger(36)});
|
||||
this.inputManager.addMapping("Right", new Trigger[]{new KeyTrigger(38)});
|
||||
this.inputManager.addMapping("Up", new Trigger[]{new KeyTrigger(23), new MouseAxisTrigger(2, true)});
|
||||
this.inputManager.addMapping("Down", new Trigger[]{new KeyTrigger(37), new MouseAxisTrigger(2, false)});
|
||||
this.inputManager.addMapping("RotateLeft", new Trigger[]{new KeyTrigger(57), new MouseButtonTrigger(0)});
|
||||
this.inputManager.addMapping("RotateRight", new Trigger[]{new MouseButtonTrigger(1)});
|
||||
this.inputManager.addListener((InputListener)this.actionListener, new String[]{"Pause"});
|
||||
this.inputManager.addListener((InputListener)this.analogListener, new String[]{"Left", "Right", "RotateLeft", "RotateRight", "Up", "Down"});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* com.jme3.app.SimpleApplication
|
||||
* com.jme3.light.DirectionalLight
|
||||
* com.jme3.light.Light
|
||||
* com.jme3.material.Material
|
||||
* com.jme3.material.RenderState$BlendMode
|
||||
* com.jme3.math.ColorRGBA
|
||||
* com.jme3.math.Vector3f
|
||||
* com.jme3.renderer.queue.RenderQueue$Bucket
|
||||
* com.jme3.scene.Geometry
|
||||
* com.jme3.scene.Mesh
|
||||
* com.jme3.scene.Spatial
|
||||
* com.jme3.scene.shape.Box
|
||||
* com.jme3.scene.shape.Sphere
|
||||
* com.jme3.scene.shape.Sphere$TextureMode
|
||||
* com.jme3.util.TangentBinormalGenerator
|
||||
*/
|
||||
package jme3.hello;
|
||||
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.light.DirectionalLight;
|
||||
import com.jme3.light.Light;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.material.RenderState;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.renderer.queue.RenderQueue;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Mesh;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.shape.Box;
|
||||
import com.jme3.scene.shape.Sphere;
|
||||
import com.jme3.util.TangentBinormalGenerator;
|
||||
|
||||
public class HelloMaterial
|
||||
extends SimpleApplication {
|
||||
public static void main(String[] args) {
|
||||
HelloMaterial app = new HelloMaterial();
|
||||
app.start();
|
||||
}
|
||||
|
||||
public void simpleInitApp() {
|
||||
Box boxshape3 = new Box(new Vector3f(0.0f, 0.0f, 0.0f), 1.0f, 1.0f, 0.01f);
|
||||
Geometry window_frame = new Geometry("window frame", (Mesh)boxshape3);
|
||||
Material mat_tt = new Material(this.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat_tt.setTexture("ColorMap", this.assetManager.loadTexture("Textures/ColoredTex/Monkey.png"));
|
||||
mat_tt.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
|
||||
window_frame.setMaterial(mat_tt);
|
||||
window_frame.setQueueBucket(RenderQueue.Bucket.Transparent);
|
||||
this.rootNode.attachChild((Spatial)window_frame);
|
||||
Box boxshape4 = new Box(new Vector3f(3.0f, -1.0f, 0.0f), 1.0f, 1.0f, 1.0f);
|
||||
Geometry cube_leak = new Geometry("Leak-through color cube", (Mesh)boxshape4);
|
||||
cube_leak.setMaterial(this.assetManager.loadMaterial("Materials/LeakThrough.j3m"));
|
||||
this.rootNode.attachChild((Spatial)cube_leak);
|
||||
Sphere rock = new Sphere(32, 32, 2.0f);
|
||||
Geometry shiny_rock = new Geometry("Shiny rock", (Mesh)rock);
|
||||
rock.setTextureMode(Sphere.TextureMode.Projected);
|
||||
TangentBinormalGenerator.generate((Mesh)rock);
|
||||
Material mat_lit = new Material(this.assetManager, "Common/MatDefs/Light/Lighting.j3md");
|
||||
mat_lit.setTexture("DiffuseMap", this.assetManager.loadTexture("Textures/Terrain/Pond/Pond.jpg"));
|
||||
mat_lit.setTexture("NormalMap", this.assetManager.loadTexture("Textures/Terrain/Pond/Pond_normal.png"));
|
||||
mat_lit.setBoolean("UseMaterialColors", true);
|
||||
mat_lit.setColor("Specular", ColorRGBA.White);
|
||||
mat_lit.setColor("Diffuse", ColorRGBA.White);
|
||||
mat_lit.setFloat("Shininess", 127.0f);
|
||||
shiny_rock.setMaterial(mat_lit);
|
||||
shiny_rock.setLocalTranslation(0.0f, 2.0f, -2.0f);
|
||||
shiny_rock.rotate(1.6f, 0.0f, 0.0f);
|
||||
this.rootNode.attachChild((Spatial)shiny_rock);
|
||||
DirectionalLight sun = new DirectionalLight();
|
||||
sun.setDirection(new Vector3f(1.0f, 0.0f, -2.0f).normalizeLocal());
|
||||
sun.setColor(ColorRGBA.White);
|
||||
this.rootNode.addLight((Light)sun);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* com.jme3.app.SimpleApplication
|
||||
* com.jme3.app.state.AppState
|
||||
* com.jme3.asset.TextureKey
|
||||
* com.jme3.bullet.BulletAppState
|
||||
* com.jme3.bullet.control.RigidBodyControl
|
||||
* com.jme3.font.BitmapText
|
||||
* com.jme3.input.controls.ActionListener
|
||||
* com.jme3.input.controls.InputListener
|
||||
* com.jme3.input.controls.MouseButtonTrigger
|
||||
* com.jme3.input.controls.Trigger
|
||||
* com.jme3.material.Material
|
||||
* com.jme3.math.Vector2f
|
||||
* com.jme3.math.Vector3f
|
||||
* com.jme3.scene.Geometry
|
||||
* com.jme3.scene.Mesh
|
||||
* com.jme3.scene.Spatial
|
||||
* com.jme3.scene.control.Control
|
||||
* com.jme3.scene.shape.Box
|
||||
* com.jme3.scene.shape.Sphere
|
||||
* com.jme3.scene.shape.Sphere$TextureMode
|
||||
* com.jme3.texture.Texture
|
||||
* com.jme3.texture.Texture$WrapMode
|
||||
*/
|
||||
package jme3.hello;
|
||||
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.app.state.AppState;
|
||||
import com.jme3.asset.TextureKey;
|
||||
import com.jme3.bullet.BulletAppState;
|
||||
import com.jme3.bullet.control.RigidBodyControl;
|
||||
import com.jme3.font.BitmapText;
|
||||
import com.jme3.input.controls.ActionListener;
|
||||
import com.jme3.input.controls.InputListener;
|
||||
import com.jme3.input.controls.MouseButtonTrigger;
|
||||
import com.jme3.input.controls.Trigger;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.math.Vector2f;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Mesh;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.control.Control;
|
||||
import com.jme3.scene.shape.Box;
|
||||
import com.jme3.scene.shape.Sphere;
|
||||
import com.jme3.texture.Texture;
|
||||
|
||||
public class HelloPhysik
|
||||
extends SimpleApplication {
|
||||
private BulletAppState bulletAppState;
|
||||
Material wall_mat;
|
||||
Material stone_mat;
|
||||
Material floor_mat;
|
||||
private RigidBodyControl brick_phy;
|
||||
private static final Box box;
|
||||
private RigidBodyControl ball_phy;
|
||||
private static final Sphere sphere;
|
||||
private RigidBodyControl floor_phy;
|
||||
private static final Box floor;
|
||||
private static final float brickLength = 0.48f;
|
||||
private static final float brickWidth = 0.24f;
|
||||
private static final float brickHeight = 0.12f;
|
||||
private ActionListener actionListener = new ActionListener(){
|
||||
|
||||
public void onAction(String name, boolean keyPressed, float tpf) {
|
||||
if (name.equals("shoot") && !keyPressed) {
|
||||
HelloPhysik.this.makeCannonBall();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public static void main(String[] args) {
|
||||
HelloPhysik app = new HelloPhysik();
|
||||
app.start();
|
||||
}
|
||||
|
||||
public void simpleInitApp() {
|
||||
this.bulletAppState = new BulletAppState();
|
||||
this.stateManager.attach((AppState)this.bulletAppState);
|
||||
this.bulletAppState.getPhysicsSpace().enableDebug(this.assetManager);
|
||||
this.cam.setLocation(new Vector3f(0.0f, 4.0f, 6.0f));
|
||||
this.cam.lookAt(new Vector3f(2.0f, 2.0f, 0.0f), Vector3f.UNIT_Y);
|
||||
this.inputManager.addMapping("shoot", new Trigger[]{new MouseButtonTrigger(0)});
|
||||
this.inputManager.addListener((InputListener)this.actionListener, new String[]{"shoot"});
|
||||
this.initMaterials();
|
||||
this.initWall();
|
||||
this.initFloor();
|
||||
this.initCrossHairs();
|
||||
}
|
||||
|
||||
public void initMaterials() {
|
||||
this.wall_mat = new Material(this.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
TextureKey key = new TextureKey("Textures/Terrain/BrickWall/BrickWall.jpg");
|
||||
key.setGenerateMips(true);
|
||||
Texture tex = this.assetManager.loadTexture(key);
|
||||
this.wall_mat.setTexture("ColorMap", tex);
|
||||
this.stone_mat = new Material(this.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
TextureKey key2 = new TextureKey("Textures/Terrain/Rock/Rock.PNG");
|
||||
key2.setGenerateMips(true);
|
||||
Texture tex2 = this.assetManager.loadTexture(key2);
|
||||
this.stone_mat.setTexture("ColorMap", tex2);
|
||||
this.floor_mat = new Material(this.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
TextureKey key3 = new TextureKey("Textures/Terrain/Pond/Pond.jpg");
|
||||
key3.setGenerateMips(true);
|
||||
Texture tex3 = this.assetManager.loadTexture(key3);
|
||||
tex3.setWrap(Texture.WrapMode.Repeat);
|
||||
this.floor_mat.setTexture("ColorMap", tex3);
|
||||
}
|
||||
|
||||
public void initFloor() {
|
||||
Geometry floor_geo = new Geometry("Floor", (Mesh)floor);
|
||||
floor_geo.setMaterial(this.floor_mat);
|
||||
floor_geo.setLocalTranslation(0.0f, -0.1f, 0.0f);
|
||||
this.rootNode.attachChild((Spatial)floor_geo);
|
||||
this.floor_phy = new RigidBodyControl(0.0f);
|
||||
floor_geo.addControl((Control)this.floor_phy);
|
||||
this.bulletAppState.getPhysicsSpace().add((Object)this.floor_phy);
|
||||
}
|
||||
|
||||
public void initWall() {
|
||||
float startpt = 0.12f;
|
||||
float height = 0.0f;
|
||||
for (int j = 0; j < 15; ++j) {
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
Vector3f vt = new Vector3f((float)i * 0.48f * 2.0f + startpt, 0.12f + height, 0.0f);
|
||||
this.makeBrick(vt);
|
||||
}
|
||||
startpt = -startpt;
|
||||
height += 0.24f;
|
||||
}
|
||||
}
|
||||
|
||||
public void makeBrick(Vector3f loc) {
|
||||
Geometry brick_geo = new Geometry("brick", (Mesh)box);
|
||||
brick_geo.setMaterial(this.wall_mat);
|
||||
this.rootNode.attachChild((Spatial)brick_geo);
|
||||
brick_geo.setLocalTranslation(loc);
|
||||
this.brick_phy = new RigidBodyControl(2.0f);
|
||||
brick_geo.addControl((Control)this.brick_phy);
|
||||
this.bulletAppState.getPhysicsSpace().add((Object)this.brick_phy);
|
||||
}
|
||||
|
||||
public void makeCannonBall() {
|
||||
Geometry ball_geo = new Geometry("cannon ball", (Mesh)sphere);
|
||||
ball_geo.setMaterial(this.stone_mat);
|
||||
this.rootNode.attachChild((Spatial)ball_geo);
|
||||
ball_geo.setLocalTranslation(this.cam.getLocation());
|
||||
this.ball_phy = new RigidBodyControl(10.0f);
|
||||
ball_geo.addControl((Control)this.ball_phy);
|
||||
this.bulletAppState.getPhysicsSpace().add((Object)this.ball_phy);
|
||||
this.ball_phy.setLinearVelocity(this.cam.getDirection().mult(25.0f));
|
||||
}
|
||||
|
||||
protected void initCrossHairs() {
|
||||
this.guiNode.detachAllChildren();
|
||||
this.guiFont = this.assetManager.loadFont("Interface/Fonts/Default.fnt");
|
||||
BitmapText ch = new BitmapText(this.guiFont, false);
|
||||
ch.setSize((float)(this.guiFont.getCharSet().getRenderedSize() * 2));
|
||||
ch.setText("+");
|
||||
ch.setLocalTranslation((float)(this.settings.getWidth() / 2 - this.guiFont.getCharSet().getRenderedSize() / 3 * 2), (float)(this.settings.getHeight() / 2) + ch.getLineHeight() / 2.0f, 0.0f);
|
||||
this.guiNode.attachChild((Spatial)ch);
|
||||
}
|
||||
|
||||
static {
|
||||
sphere = new Sphere(32, 32, 0.4f, true, false);
|
||||
sphere.setTextureMode(Sphere.TextureMode.Projected);
|
||||
box = new Box(Vector3f.ZERO, 0.48f, 0.12f, 0.24f);
|
||||
box.scaleTextureCoordinates(new Vector2f(1.0f, 0.5f));
|
||||
floor = new Box(Vector3f.ZERO, 10.0f, 0.1f, 5.0f);
|
||||
floor.scaleTextureCoordinates(new Vector2f(3.0f, 6.0f));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* com.jme3.app.SimpleApplication
|
||||
* com.jme3.collision.Collidable
|
||||
* com.jme3.collision.CollisionResult
|
||||
* com.jme3.collision.CollisionResults
|
||||
* com.jme3.effect.ParticleEmitter
|
||||
* com.jme3.effect.ParticleMesh$Type
|
||||
* com.jme3.font.BitmapText
|
||||
* com.jme3.input.controls.ActionListener
|
||||
* com.jme3.input.controls.InputListener
|
||||
* com.jme3.input.controls.KeyTrigger
|
||||
* com.jme3.input.controls.MouseButtonTrigger
|
||||
* com.jme3.input.controls.Trigger
|
||||
* com.jme3.light.DirectionalLight
|
||||
* com.jme3.light.Light
|
||||
* com.jme3.material.Material
|
||||
* com.jme3.math.ColorRGBA
|
||||
* com.jme3.math.Ray
|
||||
* com.jme3.math.Vector3f
|
||||
* com.jme3.scene.Geometry
|
||||
* com.jme3.scene.Mesh
|
||||
* com.jme3.scene.Node
|
||||
* com.jme3.scene.Spatial
|
||||
* com.jme3.scene.shape.Box
|
||||
*/
|
||||
package jme3.hello;
|
||||
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.collision.Collidable;
|
||||
import com.jme3.collision.CollisionResult;
|
||||
import com.jme3.collision.CollisionResults;
|
||||
import com.jme3.effect.ParticleEmitter;
|
||||
import com.jme3.effect.ParticleMesh;
|
||||
import com.jme3.font.BitmapText;
|
||||
import com.jme3.input.controls.ActionListener;
|
||||
import com.jme3.input.controls.InputListener;
|
||||
import com.jme3.input.controls.KeyTrigger;
|
||||
import com.jme3.input.controls.MouseButtonTrigger;
|
||||
import com.jme3.input.controls.Trigger;
|
||||
import com.jme3.light.DirectionalLight;
|
||||
import com.jme3.light.Light;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Ray;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Mesh;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.shape.Box;
|
||||
|
||||
public class HelloPicking
|
||||
extends SimpleApplication {
|
||||
Node shootables;
|
||||
Node inventory;
|
||||
boolean pickedUp;
|
||||
Geometry mark;
|
||||
private ActionListener actionListener = new ActionListener(){
|
||||
|
||||
public void onAction(String name, boolean keyPressed, float tpf) {
|
||||
Ray ray;
|
||||
CollisionResults results;
|
||||
if (name.equals("Shoot") && !keyPressed) {
|
||||
results = new CollisionResults();
|
||||
ray = new Ray(HelloPicking.this.cam.getLocation(), HelloPicking.this.cam.getDirection());
|
||||
HelloPicking.this.shootables.collideWith((Collidable)ray, results);
|
||||
System.out.println("----- Collisions?" + results.size() + "-----");
|
||||
for (int i = 0; i < results.size(); ++i) {
|
||||
float dist = results.getCollision(i).getDistance();
|
||||
Vector3f pt = results.getCollision(i).getContactPoint();
|
||||
String hit = results.getCollision(i).getGeometry().getName();
|
||||
System.out.println("* Collision #" + i);
|
||||
System.out.println(" You shot " + hit + "at" + pt + "," + dist + "wu away.");
|
||||
}
|
||||
if (results.size() > 0) {
|
||||
CollisionResult closest = results.getClosestCollision();
|
||||
HelloPicking.this.mark.setLocalTranslation(closest.getContactPoint());
|
||||
Geometry geo = closest.getGeometry();
|
||||
Boolean isModel = (Boolean)geo.getUserData("isModel");
|
||||
if (isModel != null && !isModel.booleanValue()) {
|
||||
Material mat = new Material(HelloPicking.this.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat.setColor("Color", ColorRGBA.randomColor());
|
||||
geo.setMaterial(mat);
|
||||
}
|
||||
HelloPicking.this.rootNode.attachChild((Spatial)HelloPicking.this.mark);
|
||||
} else {
|
||||
HelloPicking.this.rootNode.detachChild((Spatial)HelloPicking.this.mark);
|
||||
}
|
||||
}
|
||||
if (name.equals("Inventar") && !keyPressed) {
|
||||
results = new CollisionResults();
|
||||
ray = new Ray(HelloPicking.this.cam.getLocation(), HelloPicking.this.cam.getDirection());
|
||||
HelloPicking.this.shootables.collideWith((Collidable)ray, results);
|
||||
if (results.size() > 0) {
|
||||
CollisionResult closest = results.getClosestCollision();
|
||||
Geometry inv = closest.getGeometry();
|
||||
if (!HelloPicking.this.pickedUp) {
|
||||
HelloPicking.this.pickedUp = true;
|
||||
HelloPicking.this.shootables.detachChild((Spatial)inv);
|
||||
inv.setLocalScale(100.0f);
|
||||
HelloPicking.this.inventory.attachChild((Spatial)inv);
|
||||
} else {
|
||||
HelloPicking.this.pickedUp = false;
|
||||
Geometry box = (Geometry)HelloPicking.this.inventory.getChild(0);
|
||||
box.setLocalScale(1.0f);
|
||||
box.setLocalTranslation(closest.getContactPoint());
|
||||
HelloPicking.this.inventory.detachChild((Spatial)box);
|
||||
HelloPicking.this.shootables.attachChild((Spatial)box);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public static void main(String[] args) {
|
||||
HelloPicking app = new HelloPicking();
|
||||
app.start();
|
||||
}
|
||||
|
||||
public void simpleInitApp() {
|
||||
this.initCrossHairs();
|
||||
this.initKeys();
|
||||
this.initMark();
|
||||
Spatial model = this.assetManager.loadModel("Models/Oto/Oto.mesh.xml");
|
||||
model.setLocalTranslation(new Vector3f(1.0f, 2.0f, -3.0f));
|
||||
model.setUserData("isModel", (Object)new Boolean(true));
|
||||
model.setLocalScale(0.5f);
|
||||
DirectionalLight light = new DirectionalLight();
|
||||
light.setDirection(new Vector3f(-0.1f, -1.0f, -1.0f).normalizeLocal());
|
||||
this.rootNode.addLight((Light)light);
|
||||
this.shootables = new Node("Shootables");
|
||||
this.inventory = new Node("Inventar");
|
||||
this.guiNode.attachChild((Spatial)this.inventory);
|
||||
this.rootNode.attachChild((Spatial)this.shootables);
|
||||
this.shootables.attachChild((Spatial)this.makeCube("a Dragon", -2.0f, 0.0f, 1.0f));
|
||||
this.shootables.attachChild((Spatial)this.makeCube("a tin can", 1.0f, -2.0f, 0.0f));
|
||||
this.shootables.attachChild((Spatial)this.makeCube("the Sheriff", 0.0f, 1.0f, -2.0f));
|
||||
this.shootables.attachChild((Spatial)this.makeCube("the Deputy", 1.0f, 0.0f, -4.0f));
|
||||
this.shootables.attachChild((Spatial)this.makeFloor());
|
||||
this.shootables.attachChild(model);
|
||||
}
|
||||
|
||||
private void initKeys() {
|
||||
this.inputManager.addMapping("Shoot", new Trigger[]{new KeyTrigger(57), new MouseButtonTrigger(0)});
|
||||
this.inputManager.addMapping("Inventar", new Trigger[]{new MouseButtonTrigger(1)});
|
||||
this.inputManager.addListener((InputListener)this.actionListener, new String[]{"Shoot", "Inventar"});
|
||||
}
|
||||
|
||||
protected Geometry makeCube(String name, float x, float y, float z) {
|
||||
Box box = new Box(new Vector3f(x, y, z), 1.0f, 1.0f, 1.0f);
|
||||
Geometry cube = new Geometry(name, (Mesh)box);
|
||||
cube.setUserData("isModel", (Object)new Boolean(false));
|
||||
Material mat1 = new Material(this.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat1.setColor("Color", ColorRGBA.randomColor());
|
||||
cube.setMaterial(mat1);
|
||||
return cube;
|
||||
}
|
||||
|
||||
protected Geometry makeFloor() {
|
||||
Box box = new Box(new Vector3f(0.0f, -4.0f, -5.0f), 15.0f, 0.2f, 15.0f);
|
||||
Geometry floor = new Geometry("the Floor", (Mesh)box);
|
||||
Material mat1 = new Material(this.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat1.setColor("Color", ColorRGBA.LightGray);
|
||||
floor.setMaterial(mat1);
|
||||
return floor;
|
||||
}
|
||||
|
||||
protected void initMark() {
|
||||
ParticleEmitter fire = new ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, 30);
|
||||
Material mat_red = new Material(this.assetManager, "Common/MatDefs/Misc/Particle.j3md");
|
||||
mat_red.setTexture("Texture", this.assetManager.loadTexture("Effects/Explosion/flame.png"));
|
||||
fire.setMaterial(mat_red);
|
||||
fire.setImagesX(2);
|
||||
fire.setImagesY(2);
|
||||
fire.setEndColor(new ColorRGBA(1.0f, 0.0f, 0.0f, 1.0f));
|
||||
fire.setStartColor(new ColorRGBA(1.0f, 1.0f, 0.0f, 0.5f));
|
||||
fire.getParticleInfluencer().setInitialVelocity(new Vector3f(0.0f, 2.0f, 0.0f));
|
||||
fire.setStartSize(1.5f);
|
||||
fire.setEndSize(0.1f);
|
||||
fire.setGravity(0.0f, 0.0f, 0.0f);
|
||||
fire.setLowLife(1.0f);
|
||||
fire.setHighLife(3.0f);
|
||||
fire.getParticleInfluencer().setVelocityVariation(0.3f);
|
||||
this.mark = fire;
|
||||
}
|
||||
|
||||
protected void initCrossHairs() {
|
||||
this.guiNode.detachAllChildren();
|
||||
this.guiFont = this.assetManager.loadFont("Interface/Fonts/Default.fnt");
|
||||
BitmapText ch = new BitmapText(this.guiFont, false);
|
||||
ch.setSize((float)(this.guiFont.getCharSet().getRenderedSize() * 2));
|
||||
ch.setText("+");
|
||||
ch.setLocalTranslation((float)(this.settings.getWidth() / 2 - this.guiFont.getCharSet().getRenderedSize() / 3 * 2), (float)(this.settings.getHeight() / 2) + ch.getLineHeight() / 2.0f, 0.0f);
|
||||
this.guiNode.attachChild((Spatial)ch);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* com.jme3.app.SimpleApplication
|
||||
* com.jme3.material.Material
|
||||
* com.jme3.scene.Spatial
|
||||
* com.jme3.scene.control.Control
|
||||
* com.jme3.terrain.Terrain
|
||||
* com.jme3.terrain.geomipmap.TerrainLodControl
|
||||
* com.jme3.terrain.geomipmap.TerrainQuad
|
||||
* com.jme3.terrain.heightmap.ImageBasedHeightMap
|
||||
* com.jme3.texture.Texture
|
||||
* com.jme3.texture.Texture$WrapMode
|
||||
*/
|
||||
package jme3.hello;
|
||||
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.control.Control;
|
||||
import com.jme3.terrain.Terrain;
|
||||
import com.jme3.terrain.geomipmap.TerrainLodControl;
|
||||
import com.jme3.terrain.geomipmap.TerrainQuad;
|
||||
import com.jme3.terrain.heightmap.ImageBasedHeightMap;
|
||||
import com.jme3.texture.Texture;
|
||||
|
||||
public class HelloTerrain
|
||||
extends SimpleApplication {
|
||||
private TerrainQuad terrain;
|
||||
Material mat_terrain;
|
||||
|
||||
public static void main(String[] agrs) {
|
||||
HelloTerrain app = new HelloTerrain();
|
||||
app.start();
|
||||
}
|
||||
|
||||
public void simpleInitApp() {
|
||||
this.flyCam.setMoveSpeed(50.0f);
|
||||
this.mat_terrain = new Material(this.assetManager, "Common/MatDefs/Terrain/Terrain.j3md");
|
||||
this.mat_terrain.setTexture("Alpha", this.assetManager.loadTexture("Textures/Terrain/splat/alphamap.png"));
|
||||
Texture grass = this.assetManager.loadTexture("Textures/Terrain/splat/grass.jpg");
|
||||
grass.setWrap(Texture.WrapMode.Repeat);
|
||||
this.mat_terrain.setTexture("Tex1", grass);
|
||||
this.mat_terrain.setFloat("Tex1Scale", 64.0f);
|
||||
Texture dirt = this.assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg");
|
||||
dirt.setWrap(Texture.WrapMode.Repeat);
|
||||
this.mat_terrain.setTexture("Tex2", dirt);
|
||||
this.mat_terrain.setFloat("Tex2Scale", 32.0f);
|
||||
Texture rock = this.assetManager.loadTexture("Textures/Terrain/splat/road.jpg");
|
||||
rock.setWrap(Texture.WrapMode.Repeat);
|
||||
this.mat_terrain.setTexture("Tex3", rock);
|
||||
this.mat_terrain.setFloat("Tex3Scale", 128.0f);
|
||||
ImageBasedHeightMap heightmap = null;
|
||||
Texture heightMapImage = this.assetManager.loadTexture("Textures/Terrain/splat/mountains512.png");
|
||||
heightmap = new ImageBasedHeightMap(heightMapImage.getImage());
|
||||
heightmap.load();
|
||||
int patchSize = 65;
|
||||
this.terrain = new TerrainQuad("My terrain", patchSize, 513, heightmap.getHeightMap());
|
||||
this.terrain.setMaterial(this.mat_terrain);
|
||||
this.terrain.setLocalTranslation(0.0f, -100.0f, 0.0f);
|
||||
this.terrain.setLocalScale(2.0f, 1.0f, 2.0f);
|
||||
this.rootNode.attachChild((Spatial)this.terrain);
|
||||
TerrainLodControl control = new TerrainLodControl((Terrain)this.terrain, this.getCamera());
|
||||
this.terrain.addControl((Control)control);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* com.jme3.app.SimpleApplication
|
||||
* com.jme3.material.Material
|
||||
* com.jme3.math.ColorRGBA
|
||||
* com.jme3.math.Vector3f
|
||||
* com.jme3.scene.Geometry
|
||||
* com.jme3.scene.Mesh
|
||||
* com.jme3.scene.Node
|
||||
* com.jme3.scene.Spatial
|
||||
* com.jme3.scene.shape.Box
|
||||
*/
|
||||
package jme3.hello;
|
||||
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Mesh;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.shape.Box;
|
||||
|
||||
public class HelloWorld
|
||||
extends SimpleApplication {
|
||||
public static void main(String[] args) {
|
||||
HelloWorld app = new HelloWorld();
|
||||
app.start();
|
||||
}
|
||||
|
||||
public void simpleInitApp() {
|
||||
Box box1 = new Box(Vector3f.ZERO, 1.0f, 1.0f, 1.0f);
|
||||
Geometry blue = new Geometry("Box", (Mesh)box1);
|
||||
Material mat1 = new Material(this.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat1.setColor("Color", ColorRGBA.Blue);
|
||||
blue.setMaterial(mat1);
|
||||
blue.move(1.0f, -1.0f, 1.0f);
|
||||
Box box2 = new Box(Vector3f.ZERO, 1.0f, 1.0f, 1.0f);
|
||||
Geometry red = new Geometry("Box", (Mesh)box2);
|
||||
Material mat2 = new Material(this.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat2.setColor("Color", ColorRGBA.Red);
|
||||
red.setMaterial(mat2);
|
||||
red.move(1.0f, 3.0f, 4.0f);
|
||||
Node pivot = new Node("pivot");
|
||||
this.rootNode.attachChild((Spatial)pivot);
|
||||
pivot.attachChild((Spatial)blue);
|
||||
pivot.attachChild((Spatial)red);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* com.jme3.app.SimpleApplication
|
||||
* com.jme3.material.Material
|
||||
* com.jme3.math.ColorRGBA
|
||||
* com.jme3.math.Vector3f
|
||||
* com.jme3.scene.Geometry
|
||||
* com.jme3.scene.Mesh
|
||||
* com.jme3.scene.Spatial
|
||||
* com.jme3.scene.shape.Box
|
||||
*/
|
||||
package jme3.hello;
|
||||
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Mesh;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.shape.Box;
|
||||
|
||||
public class SimpleLoop
|
||||
extends SimpleApplication {
|
||||
protected Geometry player;
|
||||
protected Geometry box2;
|
||||
boolean shrink;
|
||||
|
||||
public static void main(String[] args) {
|
||||
SimpleLoop app = new SimpleLoop();
|
||||
app.start();
|
||||
}
|
||||
|
||||
public void simpleInitApp() {
|
||||
Box b = new Box(Vector3f.ZERO, 1.0f, 1.0f, 1.0f);
|
||||
this.player = new Geometry("blue cube", (Mesh)b);
|
||||
Material mat = new Material(this.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat.setColor("Color", ColorRGBA.Yellow);
|
||||
this.player.setMaterial(mat);
|
||||
this.rootNode.attachChild((Spatial)this.player);
|
||||
}
|
||||
|
||||
public void simpleUpdate(float tpf) {
|
||||
this.player.rotate(-2.0f * tpf, 0.0f, -2.0f * tpf);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,522 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* com.jme3.animation.AnimChannel
|
||||
* com.jme3.animation.AnimControl
|
||||
* com.jme3.animation.AnimEventListener
|
||||
* com.jme3.animation.LoopMode
|
||||
* com.jme3.app.Application
|
||||
* com.jme3.app.SimpleApplication
|
||||
* com.jme3.app.state.AppState
|
||||
* com.jme3.audio.AudioNode
|
||||
* com.jme3.bullet.BulletAppState
|
||||
* com.jme3.bullet.collision.PhysicsCollisionEvent
|
||||
* com.jme3.bullet.collision.PhysicsCollisionListener
|
||||
* com.jme3.bullet.control.RigidBodyControl
|
||||
* com.jme3.collision.Collidable
|
||||
* com.jme3.collision.CollisionResults
|
||||
* com.jme3.input.controls.ActionListener
|
||||
* com.jme3.input.controls.AnalogListener
|
||||
* com.jme3.input.controls.InputListener
|
||||
* com.jme3.input.controls.KeyTrigger
|
||||
* com.jme3.input.controls.MouseButtonTrigger
|
||||
* com.jme3.input.controls.Trigger
|
||||
* com.jme3.light.AmbientLight
|
||||
* com.jme3.light.Light
|
||||
* com.jme3.math.ColorRGBA
|
||||
* com.jme3.math.Ray
|
||||
* com.jme3.math.Vector2f
|
||||
* com.jme3.math.Vector3f
|
||||
* com.jme3.post.Filter
|
||||
* com.jme3.post.FilterPostProcessor
|
||||
* com.jme3.post.SceneProcessor
|
||||
* com.jme3.post.filters.CartoonEdgeFilter
|
||||
* com.jme3.renderer.Caps
|
||||
* com.jme3.renderer.queue.RenderQueue$ShadowMode
|
||||
* com.jme3.scene.Spatial
|
||||
* com.jme3.scene.control.Control
|
||||
* com.jme3.shadow.DirectionalLightShadowFilter
|
||||
* com.jme3.shadow.DirectionalLightShadowRenderer
|
||||
* tonegod.gui.controls.extras.Indicator
|
||||
* tonegod.gui.controls.extras.Indicator$Orientation
|
||||
* tonegod.gui.controls.text.Label
|
||||
* tonegod.gui.core.Element
|
||||
* tonegod.gui.core.Screen
|
||||
* tonegod.gui.effects.Effect
|
||||
* tonegod.gui.effects.Effect$EffectEvent
|
||||
* tonegod.gui.effects.Effect$EffectType
|
||||
*/
|
||||
package jme3.hello;
|
||||
|
||||
import com.jme3.animation.AnimChannel;
|
||||
import com.jme3.animation.AnimControl;
|
||||
import com.jme3.animation.AnimEventListener;
|
||||
import com.jme3.animation.LoopMode;
|
||||
import com.jme3.app.Application;
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.app.state.AppState;
|
||||
import com.jme3.audio.AudioNode;
|
||||
import com.jme3.bullet.BulletAppState;
|
||||
import com.jme3.bullet.collision.PhysicsCollisionEvent;
|
||||
import com.jme3.bullet.collision.PhysicsCollisionListener;
|
||||
import com.jme3.bullet.control.RigidBodyControl;
|
||||
import com.jme3.collision.Collidable;
|
||||
import com.jme3.collision.CollisionResults;
|
||||
import com.jme3.input.controls.ActionListener;
|
||||
import com.jme3.input.controls.AnalogListener;
|
||||
import com.jme3.input.controls.InputListener;
|
||||
import com.jme3.input.controls.KeyTrigger;
|
||||
import com.jme3.input.controls.MouseButtonTrigger;
|
||||
import com.jme3.input.controls.Trigger;
|
||||
import com.jme3.light.AmbientLight;
|
||||
import com.jme3.light.Light;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Ray;
|
||||
import com.jme3.math.Vector2f;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.post.Filter;
|
||||
import com.jme3.post.FilterPostProcessor;
|
||||
import com.jme3.post.SceneProcessor;
|
||||
import com.jme3.post.filters.CartoonEdgeFilter;
|
||||
import com.jme3.renderer.Caps;
|
||||
import com.jme3.renderer.queue.RenderQueue;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.control.Control;
|
||||
import com.jme3.shadow.DirectionalLightShadowFilter;
|
||||
import com.jme3.shadow.DirectionalLightShadowRenderer;
|
||||
import java.util.Random;
|
||||
import mygame.Exchange;
|
||||
import mygame.Player;
|
||||
import mygame.Staff;
|
||||
import mygame.Sword;
|
||||
import mygame.WalkingEnemy;
|
||||
import tonegod.gui.controls.extras.Indicator;
|
||||
import tonegod.gui.controls.text.Label;
|
||||
import tonegod.gui.core.Element;
|
||||
import tonegod.gui.core.Screen;
|
||||
import tonegod.gui.effects.Effect;
|
||||
import wpq.tests.PlayerCam;
|
||||
|
||||
public class TestCameraNode
|
||||
extends SimpleApplication
|
||||
implements AnalogListener,
|
||||
ActionListener,
|
||||
AnimEventListener,
|
||||
PhysicsCollisionListener {
|
||||
private Player player;
|
||||
Vector3f direction = new Vector3f();
|
||||
private FilterPostProcessor fpp;
|
||||
private BulletAppState bulletAppState;
|
||||
Screen screen;
|
||||
Indicator ind1;
|
||||
Label spiderCounter;
|
||||
Label label;
|
||||
Indicator waveCounter;
|
||||
Indicator cooldownLeft;
|
||||
Indicator cooldownRight;
|
||||
Indicator skill2;
|
||||
Label gameover;
|
||||
int change = 0;
|
||||
private int winCount = 0;
|
||||
PlayerCam playerCam;
|
||||
private float spawnTimer;
|
||||
private float timer;
|
||||
boolean left = false;
|
||||
boolean right = false;
|
||||
boolean down = false;
|
||||
boolean up = false;
|
||||
Vector3f walkDirection = new Vector3f(Vector3f.ZERO);
|
||||
private DirectionalLightShadowRenderer dlsr;
|
||||
private DirectionalLightShadowFilter dlsf;
|
||||
private static int SPAWN_TIME = 10;
|
||||
private int spawns = 1;
|
||||
private boolean gameover_flag = false;
|
||||
public Exchange exchange;
|
||||
|
||||
public static void main(String[] args) {
|
||||
TestCameraNode app = new TestCameraNode();
|
||||
app.setDisplayFps(false);
|
||||
app.setDisplayStatView(false);
|
||||
app.start();
|
||||
}
|
||||
|
||||
public void simpleInitApp() {
|
||||
this.bulletAppState = new BulletAppState();
|
||||
this.stateManager.attach((AppState)this.bulletAppState);
|
||||
this.exchange = new Exchange();
|
||||
Exchange.setRootNode(this.rootNode);
|
||||
Exchange.setBulletAppState(this.bulletAppState);
|
||||
Exchange.setAssetManager(this.assetManager);
|
||||
Exchange.setViewPort(this.viewPort);
|
||||
Exchange.setApp(this);
|
||||
this.spawnTimer = 0.0f;
|
||||
this.timer = 0.0f;
|
||||
this.setupPlayer();
|
||||
this.setupGround();
|
||||
this.setupWalls();
|
||||
this.setupCam();
|
||||
this.setupLight();
|
||||
this.registerInput();
|
||||
this.setupFilters();
|
||||
this.setupGUI();
|
||||
}
|
||||
|
||||
private void setupPlayer() {
|
||||
this.player = new Player("Player");
|
||||
this.player.setHat(this.assetManager.loadModel("Models/helmet/helmet.j3o"));
|
||||
Staff staff = new Staff("Staff", this.assetManager, this.exchange);
|
||||
staff.setModel(this.assetManager.loadModel("Models/staff/staff.mesh.j3o"));
|
||||
this.player.setLeftHand(staff);
|
||||
Sword sword = new Sword("Sword", this.assetManager, this.exchange);
|
||||
sword.setModel(this.assetManager.loadModel("Models/sword/sword.mesh.j3o"));
|
||||
this.bulletAppState.getPhysicsSpace().add((Object)sword);
|
||||
this.player.setRightHand(sword);
|
||||
this.player.setModel(this.assetManager.loadModel("Models/blob/Blob.mesh.j3o"));
|
||||
this.bulletAppState.getPhysicsSpace().add((Object)this.player.playerControl);
|
||||
this.rootNode.attachChild((Spatial)this.player);
|
||||
this.bulletAppState.getPhysicsSpace().addCollisionListener((PhysicsCollisionListener)this);
|
||||
}
|
||||
|
||||
private void setupSpider(Vector3f spawn) {
|
||||
WalkingEnemy walkingSpider = new WalkingEnemy("Spider", this.assetManager);
|
||||
walkingSpider.setLocalTranslation(spawn);
|
||||
walkingSpider.setModel(this.assetManager.loadModel("Models/spider/spider.mesh.j3o"));
|
||||
walkingSpider.modelAnimChannel.setAnim("walk");
|
||||
walkingSpider.setHealth(100.0f);
|
||||
walkingSpider.modelAnimChannel.setLoopMode(LoopMode.Loop);
|
||||
Exchange.enemies.add(walkingSpider);
|
||||
AudioNode walksound = new AudioNode(this.assetManager, "Sounds/Effects/spiderwalking.ogg", false);
|
||||
walksound.setName("walkingSound");
|
||||
walksound.setLooping(true);
|
||||
walksound.setVolume(2.0f);
|
||||
walkingSpider.attachChild((Spatial)walksound);
|
||||
walksound.play();
|
||||
this.bulletAppState.getPhysicsSpace().add((Object)walkingSpider.ghost);
|
||||
this.bulletAppState.getPhysicsSpace().add((Object)walkingSpider.walkingEnemyControl);
|
||||
this.rootNode.attachChild((Spatial)walkingSpider);
|
||||
}
|
||||
|
||||
private void spawnSpider() {
|
||||
Random r = new Random();
|
||||
int spawnx = r.nextInt(81) - 40;
|
||||
int spawnz = r.nextInt(81) - 40;
|
||||
Vector3f spawnpoint = new Vector3f((float)spawnx, 0.0f, (float)spawnz);
|
||||
AudioNode spawnsound = new AudioNode(this.assetManager, "Sounds/Effects/spawn.ogg", false);
|
||||
spawnsound.setLooping(false);
|
||||
spawnsound.setPositional(true);
|
||||
spawnsound.setLocalTranslation(spawnpoint);
|
||||
spawnsound.setVolume(5.0f);
|
||||
this.rootNode.attachChild((Spatial)spawnsound);
|
||||
spawnsound.play();
|
||||
this.setupSpider(spawnpoint);
|
||||
}
|
||||
|
||||
private void setupGround() {
|
||||
int rotation_flag = 0;
|
||||
for (int x = -50; x < 50; x += 10) {
|
||||
for (int z = -50; z < 50; z += 10) {
|
||||
Spatial ground;
|
||||
if (rotation_flag == 5) {
|
||||
rotation_flag = 0;
|
||||
ground = this.assetManager.loadModel("Models/tile2/tile.mesh.j3o");
|
||||
} else {
|
||||
ground = this.assetManager.loadModel("Models/tile1/tile.mesh.j3o");
|
||||
++rotation_flag;
|
||||
}
|
||||
ground.scale(5.0f);
|
||||
ground.setLocalTranslation((float)x, -1.0f, (float)z);
|
||||
RigidBodyControl ground_solid = new RigidBodyControl(0.0f);
|
||||
ground.addControl((Control)ground_solid);
|
||||
this.bulletAppState.getPhysicsSpace().add((Object)ground_solid);
|
||||
ground.setShadowMode(RenderQueue.ShadowMode.Receive);
|
||||
this.rootNode.attachChild(ground);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setupWalls() {
|
||||
float x;
|
||||
for (x = -60.0f; x < 60.0f; x += 10.0f) {
|
||||
this.createWall(x, 50.0f);
|
||||
this.createWall(x, -50.0f);
|
||||
}
|
||||
for (x = -60.0f; x < 60.0f; x += 10.0f) {
|
||||
this.createWall(50.0f, x);
|
||||
this.createWall(-50.0f, x);
|
||||
}
|
||||
}
|
||||
|
||||
private void createWall(float x, float z) {
|
||||
Spatial wall = this.assetManager.loadModel("Models/wall1/wall1.mesh.j3o");
|
||||
wall.scale(5.0f);
|
||||
wall.setLocalTranslation(x, 0.0f, z);
|
||||
RigidBodyControl wall_solid = new RigidBodyControl(0.0f);
|
||||
wall.addControl((Control)wall_solid);
|
||||
this.bulletAppState.getPhysicsSpace().add((Object)wall);
|
||||
wall.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
|
||||
this.rootNode.attachChild(wall);
|
||||
}
|
||||
|
||||
private void setupCam() {
|
||||
this.playerCam = new PlayerCam(this.player, "CamNode", this.cam);
|
||||
this.playerCam.setupCamera();
|
||||
this.flyCam.setEnabled(false);
|
||||
}
|
||||
|
||||
private void setupLight() {
|
||||
AmbientLight al = new AmbientLight();
|
||||
al.setColor(new ColorRGBA(0.6f, 0.6f, 0.9f, 1.0f));
|
||||
this.rootNode.addLight((Light)al);
|
||||
}
|
||||
|
||||
public void registerInput() {
|
||||
Trigger[] triggerArray = new Trigger[2];
|
||||
triggerArray[0] = new KeyTrigger(200);
|
||||
triggerArray[1] = new KeyTrigger(17);
|
||||
this.inputManager.addMapping("moveForward", triggerArray);
|
||||
Trigger[] triggerArray2 = new Trigger[2];
|
||||
triggerArray2[0] = new KeyTrigger(208);
|
||||
triggerArray2[1] = new KeyTrigger(31);
|
||||
this.inputManager.addMapping("moveBackward", triggerArray2);
|
||||
Trigger[] triggerArray3 = new Trigger[2];
|
||||
triggerArray3[0] = new KeyTrigger(205);
|
||||
triggerArray3[1] = new KeyTrigger(32);
|
||||
this.inputManager.addMapping("moveRight", triggerArray3);
|
||||
Trigger[] triggerArray4 = new Trigger[2];
|
||||
triggerArray4[0] = new KeyTrigger(203);
|
||||
triggerArray4[1] = new KeyTrigger(30);
|
||||
this.inputManager.addMapping("moveLeft", triggerArray4);
|
||||
this.inputManager.addMapping("rightHand", new Trigger[]{new MouseButtonTrigger(1)});
|
||||
Trigger[] triggerArray5 = new Trigger[1];
|
||||
triggerArray5[0] = new KeyTrigger(18);
|
||||
this.inputManager.addMapping("skill2", triggerArray5);
|
||||
this.inputManager.addMapping("leftHand", new Trigger[]{new MouseButtonTrigger(0)});
|
||||
this.inputManager.addMapping("die", new Trigger[]{new MouseButtonTrigger(2)});
|
||||
this.inputManager.addMapping("jump", new Trigger[]{new KeyTrigger(57)});
|
||||
this.inputManager.addListener((InputListener)this, new String[]{"moveForward", "moveBackward", "moveRight", "moveLeft", "rightHand", "leftHand", "die", "jump", "skill2"});
|
||||
}
|
||||
|
||||
public void setupFilters() {
|
||||
if (this.renderer.getCaps().contains(Caps.GLSL100)) {
|
||||
this.fpp = new FilterPostProcessor(this.assetManager);
|
||||
CartoonEdgeFilter toon = new CartoonEdgeFilter();
|
||||
toon.setEdgeColor(ColorRGBA.Black);
|
||||
this.fpp.addFilter((Filter)toon);
|
||||
this.viewPort.addProcessor((SceneProcessor)this.fpp);
|
||||
}
|
||||
}
|
||||
|
||||
public void onAnalog(String name, float value, float tpf) {
|
||||
}
|
||||
|
||||
public void onAction(String name, boolean keyPressed, float tpf) {
|
||||
try {
|
||||
if (name.equals("rightHand") && !keyPressed) {
|
||||
this.player.rightHand.skill1();
|
||||
}
|
||||
if (name.equals("leftHand") && !keyPressed) {
|
||||
this.player.leftHand.skill1();
|
||||
}
|
||||
}
|
||||
catch (NullPointerException e) {
|
||||
// empty catch block
|
||||
}
|
||||
if (name.equals("skill2") && !keyPressed) {
|
||||
this.player.leftHand.skill2();
|
||||
}
|
||||
if (name.equals("die") && !keyPressed) {
|
||||
this.player.switchHands();
|
||||
}
|
||||
if (name.equals("jump")) {
|
||||
this.player.playerControl.jump();
|
||||
}
|
||||
if (name.equals("moveLeft")) {
|
||||
this.left = keyPressed;
|
||||
} else if (name.equals("moveRight")) {
|
||||
this.right = keyPressed;
|
||||
} else if (name.equals("moveForward")) {
|
||||
this.up = keyPressed;
|
||||
} else if (name.equals("moveBackward")) {
|
||||
this.down = keyPressed;
|
||||
}
|
||||
}
|
||||
|
||||
public void setupGUI() {
|
||||
this.screen = new Screen((Application)this, "tonegod/gui/style/def/style_map.xml");
|
||||
this.screen.initialize();
|
||||
this.guiNode.addControl((Control)this.screen);
|
||||
this.ind1 = new Indicator(this.screen, "Healthbar", new Vector2f(10.0f, 10.0f), Indicator.Orientation.HORIZONTAL){
|
||||
|
||||
public void onChange(float arg0, float arg1) {
|
||||
}
|
||||
};
|
||||
this.ind1.setMaxValue(100.0f);
|
||||
this.ind1.setCurrentValue(this.player.health);
|
||||
this.ind1.setIndicatorColor(ColorRGBA.Red);
|
||||
this.ind1.setDisplayPercentage();
|
||||
this.screen.addElement((Element)this.ind1);
|
||||
this.cooldownLeft = new Indicator(this.screen, "cooldownLeft", new Vector2f(10.0f, (float)(this.settings.getHeight() - 30)), Indicator.Orientation.HORIZONTAL){
|
||||
|
||||
public void onChange(float arg0, float arg1) {
|
||||
}
|
||||
};
|
||||
this.cooldownLeft.setMaxValue(100.0f);
|
||||
this.cooldownLeft.setCurrentValue(100.0f);
|
||||
this.cooldownLeft.setIndicatorColor(ColorRGBA.Blue);
|
||||
this.cooldownLeft.setText("Fireball");
|
||||
this.screen.addElement((Element)this.cooldownLeft);
|
||||
this.cooldownRight = new Indicator(this.screen, "cooldownRight", new Vector2f(10.0f, (float)(this.settings.getHeight() - 70)), Indicator.Orientation.HORIZONTAL){
|
||||
|
||||
public void onChange(float arg0, float arg1) {
|
||||
}
|
||||
};
|
||||
this.cooldownRight.setMaxValue(100.0f);
|
||||
this.cooldownRight.setCurrentValue(100.0f);
|
||||
this.cooldownRight.setIndicatorColor(ColorRGBA.Blue);
|
||||
this.cooldownRight.setText("Sword");
|
||||
this.screen.addElement((Element)this.cooldownRight);
|
||||
this.skill2 = new Indicator(this.screen, "skill2", new Vector2f(10.0f, (float)(this.settings.getHeight() - 50)), Indicator.Orientation.HORIZONTAL){
|
||||
|
||||
public void onChange(float arg0, float arg1) {
|
||||
}
|
||||
};
|
||||
this.skill2.setMaxValue(100.0f);
|
||||
this.skill2.setCurrentValue(100.0f);
|
||||
this.skill2.setIndicatorColor(ColorRGBA.Blue);
|
||||
this.skill2.setText("Teleport");
|
||||
this.screen.addElement((Element)this.skill2);
|
||||
this.label = new Label(this.screen, "Timer", new Vector2f(10.0f, 50.0f), new Vector2f(200.0f, 100.0f));
|
||||
this.label.setFontColor(ColorRGBA.Yellow);
|
||||
this.label.setFontSize(30.0f);
|
||||
this.screen.addElement((Element)this.label);
|
||||
this.waveCounter = new Indicator(this.screen, "waveCounter", new Vector2f(10.0f, 30.0f), Indicator.Orientation.HORIZONTAL){
|
||||
|
||||
public void onChange(float arg0, float arg1) {
|
||||
}
|
||||
};
|
||||
this.waveCounter.setMaxValue((float)SPAWN_TIME);
|
||||
this.waveCounter.setCurrentValue((float)SPAWN_TIME - this.spawnTimer);
|
||||
this.waveCounter.setIndicatorColor(ColorRGBA.Yellow);
|
||||
this.waveCounter.setFontColor(ColorRGBA.Black);
|
||||
this.waveCounter.setText("next Wave in...");
|
||||
this.screen.addElement((Element)this.waveCounter);
|
||||
this.spiderCounter = new Label(this.screen, "SpiderCounter", new Vector2f(10.0f, 30.0f), new Vector2f(200.0f, 100.0f));
|
||||
this.spiderCounter.setFontColor(ColorRGBA.Yellow);
|
||||
this.spiderCounter.setFontSize(30.0f);
|
||||
this.screen.addElement((Element)this.spiderCounter);
|
||||
this.gameover = new Label(this.screen, "GameOver", new Vector2f(0.0f, 0.0f), new Vector2f((float)this.settings.getWidth(), 400.0f));
|
||||
this.gameover.setFont("/Interface/Fonts/FleshWound.fnt");
|
||||
this.gameover.setFontSize(100.0f);
|
||||
this.gameover.setFontColor(ColorRGBA.White);
|
||||
this.gameover.setText("Verloren!");
|
||||
Effect ef = new Effect(Effect.EffectType.FadeIn, Effect.EffectEvent.Show, 2.0f);
|
||||
this.gameover.hide();
|
||||
this.gameover.addEffect(Effect.EffectEvent.Show, ef);
|
||||
this.screen.addElement((Element)this.gameover);
|
||||
}
|
||||
|
||||
public void reduceCooldowns(float tpf) {
|
||||
this.player.leftHand.reduceCooldowns(tpf);
|
||||
this.player.rightHand.reduceCooldowns(tpf);
|
||||
}
|
||||
|
||||
public void simpleUpdate(float tpf) {
|
||||
this.reduceCooldowns(tpf);
|
||||
this.cooldownLeft.setCurrentValue(this.player.leftHand.cooldown1 * (this.cooldownLeft.getMaxValue() / this.player.leftHand.COOLDOWN1_TIME));
|
||||
this.skill2.setCurrentValue(this.player.leftHand.cooldown2 * (this.skill2.getMaxValue() / this.player.leftHand.COOLDOWN2_TIME));
|
||||
this.cooldownRight.setCurrentValue(this.player.rightHand.cooldown1 * (this.cooldownRight.getMaxValue() / this.player.rightHand.COOLDOWN1_TIME));
|
||||
this.spiderCounter.setText("Kills: " + Exchange.killed);
|
||||
if (!this.gameover_flag) {
|
||||
CollisionResults results = new CollisionResults();
|
||||
Vector2f click2d = this.inputManager.getCursorPosition();
|
||||
Vector3f click3d = this.cam.getWorldCoordinates(new Vector2f(click2d.x, click2d.y), 0.0f).clone();
|
||||
Vector3f dir = this.cam.getWorldCoordinates(new Vector2f(click2d.x, click2d.y), 1.0f).subtractLocal(click3d).normalizeLocal();
|
||||
Ray ray = new Ray(click3d, dir);
|
||||
this.rootNode.collideWith((Collidable)ray, results);
|
||||
for (int i = 0; i < results.size(); ++i) {
|
||||
float dist = results.getCollision(i).getDistance();
|
||||
Vector3f pt = results.getCollision(i).getContactPoint();
|
||||
this.player.wholeBody.lookAt(pt, Vector3f.UNIT_Y);
|
||||
this.player.pt = pt;
|
||||
}
|
||||
Vector3f camDir = this.cam.getDirection().clone().multLocal(8.0f);
|
||||
Vector3f camLeft = this.cam.getLeft().clone().multLocal(8.0f);
|
||||
camDir.y = 0.0f;
|
||||
camLeft.y = 0.0f;
|
||||
this.walkDirection.set(0.0f, 0.0f, 0.0f);
|
||||
if (this.left) {
|
||||
this.walkDirection.addLocal(camLeft);
|
||||
}
|
||||
if (this.right) {
|
||||
this.walkDirection.addLocal(camLeft.negate());
|
||||
}
|
||||
if (this.up) {
|
||||
this.walkDirection.addLocal(camDir);
|
||||
}
|
||||
if (this.down) {
|
||||
this.walkDirection.addLocal(camDir.negate());
|
||||
}
|
||||
if (this.walkDirection.length() == 0.0f) {
|
||||
if ("walk".equals(this.player.modelAnimChannel.getAnimationName())) {
|
||||
this.player.modelAnimChannel.setAnim("stand", 1.0f);
|
||||
}
|
||||
} else if ("stand".equals(this.player.modelAnimChannel.getAnimationName())) {
|
||||
this.player.modelAnimChannel.setAnim("walk", 0.7f);
|
||||
}
|
||||
this.walkDirection.normalizeLocal().multLocal(7.5f);
|
||||
this.player.playerControl.setWalkDirection(this.walkDirection);
|
||||
this.ind1.setCurrentValue(this.player.health);
|
||||
if (this.player.health <= 0.0f) {
|
||||
this.player.die();
|
||||
this.gameover.showWithEffect();
|
||||
AudioNode gameover = new AudioNode(this.assetManager, "Sounds/Effects/gameover.ogg", false);
|
||||
gameover.setLooping(false);
|
||||
gameover.setVolume(5.0f);
|
||||
this.rootNode.attachChild((Spatial)gameover);
|
||||
gameover.play();
|
||||
this.gameover_flag = true;
|
||||
}
|
||||
this.waveCounter.setCurrentValue((float)SPAWN_TIME - this.spawnTimer);
|
||||
this.spawnTimer += tpf;
|
||||
if (this.spawnTimer >= (float)SPAWN_TIME) {
|
||||
for (int spawn_counts = 0; spawn_counts < this.spawns; ++spawn_counts) {
|
||||
this.spawnSpider();
|
||||
}
|
||||
++this.spawns;
|
||||
this.spawnTimer = 0.0f;
|
||||
}
|
||||
this.label.setText("Wave No.:" + (this.spawns - 1));
|
||||
}
|
||||
}
|
||||
|
||||
public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) {
|
||||
if (animName.equals("walk")) {
|
||||
channel.setAnim("stand", 0.1f);
|
||||
channel.setLoopMode(LoopMode.DontLoop);
|
||||
channel.setSpeed(1.0f);
|
||||
}
|
||||
if (animName.equals("skill1")) {
|
||||
channel.setAnim("normal", 0.1f);
|
||||
channel.setLoopMode(LoopMode.DontLoop);
|
||||
channel.setSpeed(1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public void onAnimChange(AnimControl control, AnimChannel channel, String animName) {
|
||||
if (animName.equals("skill1")) {
|
||||
// empty if block
|
||||
}
|
||||
}
|
||||
|
||||
public void collision(PhysicsCollisionEvent event) {
|
||||
try {
|
||||
if (event.getNodeA().getName().equals("Spider") && event.getNodeB().getName().equals("Player") || event.getNodeB().getName().equals("Spider") && event.getNodeA().getName().equals("Player")) {
|
||||
this.player.health -= 0.25f;
|
||||
}
|
||||
}
|
||||
catch (Exception exception) {
|
||||
// empty catch block
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* com.jme3.app.SimpleApplication
|
||||
* com.jme3.input.ChaseCamera
|
||||
* com.jme3.input.controls.ActionListener
|
||||
* com.jme3.input.controls.AnalogListener
|
||||
* com.jme3.input.controls.InputListener
|
||||
* com.jme3.input.controls.KeyTrigger
|
||||
* com.jme3.input.controls.Trigger
|
||||
* com.jme3.material.Material
|
||||
* com.jme3.math.Quaternion
|
||||
* com.jme3.math.Vector3f
|
||||
* com.jme3.scene.Geometry
|
||||
* com.jme3.scene.Mesh
|
||||
* com.jme3.scene.Spatial
|
||||
* com.jme3.scene.shape.Quad
|
||||
*/
|
||||
package jme3.hello;
|
||||
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.input.ChaseCamera;
|
||||
import com.jme3.input.controls.ActionListener;
|
||||
import com.jme3.input.controls.AnalogListener;
|
||||
import com.jme3.input.controls.InputListener;
|
||||
import com.jme3.input.controls.KeyTrigger;
|
||||
import com.jme3.input.controls.Trigger;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Mesh;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.shape.Quad;
|
||||
|
||||
public class TestChaseCamera
|
||||
extends SimpleApplication
|
||||
implements AnalogListener,
|
||||
ActionListener {
|
||||
private Spatial teaGeom;
|
||||
private ChaseCamera chaseCam;
|
||||
|
||||
public static void main(String[] args) {
|
||||
TestChaseCamera app = new TestChaseCamera();
|
||||
app.start();
|
||||
}
|
||||
|
||||
public void simpleInitApp() {
|
||||
this.teaGeom = this.assetManager.loadModel("Models/blob/Blob.mesh.xml");
|
||||
Material mat_tea = new Material(this.assetManager, "Common/MatDefs/Misc/ShowNormals.j3md");
|
||||
this.teaGeom.setMaterial(mat_tea);
|
||||
this.rootNode.attachChild(this.teaGeom);
|
||||
Material mat_ground = new Material(this.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
Geometry ground = new Geometry("ground", (Mesh)new Quad(50.0f, 50.0f));
|
||||
ground.setLocalRotation(new Quaternion().fromAngleAxis(-1.5707964f, Vector3f.UNIT_X));
|
||||
ground.setLocalTranslation(-25.0f, -1.0f, 25.0f);
|
||||
ground.setMaterial(mat_ground);
|
||||
this.rootNode.attachChild((Spatial)ground);
|
||||
this.flyCam.setEnabled(false);
|
||||
this.chaseCam = new ChaseCamera(this.cam, this.teaGeom, this.inputManager);
|
||||
this.chaseCam.setRotationSpeed(0.0f);
|
||||
this.chaseCam.setSmoothMotion(true);
|
||||
this.chaseCam.setTrailingEnabled(true);
|
||||
this.chaseCam.setLookAtOffset(Vector3f.UNIT_Y.mult(3.0f));
|
||||
this.registerInput();
|
||||
}
|
||||
|
||||
public void registerInput() {
|
||||
Trigger[] triggerArray = new Trigger[2];
|
||||
triggerArray[0] = new KeyTrigger(200);
|
||||
triggerArray[1] = new KeyTrigger(17);
|
||||
this.inputManager.addMapping("moveForward", triggerArray);
|
||||
Trigger[] triggerArray2 = new Trigger[2];
|
||||
triggerArray2[0] = new KeyTrigger(208);
|
||||
triggerArray2[1] = new KeyTrigger(31);
|
||||
this.inputManager.addMapping("moveBackward", triggerArray2);
|
||||
Trigger[] triggerArray3 = new Trigger[2];
|
||||
triggerArray3[0] = new KeyTrigger(205);
|
||||
triggerArray3[1] = new KeyTrigger(32);
|
||||
this.inputManager.addMapping("moveRight", triggerArray3);
|
||||
Trigger[] triggerArray4 = new Trigger[2];
|
||||
triggerArray4[0] = new KeyTrigger(203);
|
||||
triggerArray4[1] = new KeyTrigger(30);
|
||||
this.inputManager.addMapping("moveLeft", triggerArray4);
|
||||
Trigger[] triggerArray5 = new Trigger[1];
|
||||
triggerArray5[0] = new KeyTrigger(25);
|
||||
this.inputManager.addMapping("displayPosition", triggerArray5);
|
||||
this.inputManager.addListener((InputListener)this, new String[]{"moveForward", "moveBackward", "moveRight", "moveLeft"});
|
||||
this.inputManager.addListener((InputListener)this, new String[]{"displayPosition"});
|
||||
}
|
||||
|
||||
public void onAnalog(String name, float value, float tpf) {
|
||||
if (name.equals("moveForward")) {
|
||||
this.teaGeom.move(0.0f, 0.0f, -5.0f * tpf);
|
||||
}
|
||||
if (name.equals("moveBackward")) {
|
||||
this.teaGeom.move(0.0f, 0.0f, 5.0f * tpf);
|
||||
}
|
||||
if (name.equals("moveRight")) {
|
||||
this.teaGeom.move(5.0f * tpf, 0.0f, 0.0f);
|
||||
}
|
||||
if (name.equals("moveLeft")) {
|
||||
this.teaGeom.move(-5.0f * tpf, 0.0f, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public void onAction(String name, boolean keyPressed, float tpf) {
|
||||
if (name.equals("displayPosition") && keyPressed) {
|
||||
this.teaGeom.move(10.0f, 10.0f, 10.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public void simpleUpdate(float tpf) {
|
||||
super.simpleUpdate(tpf);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* com.jme3.animation.AnimChannel
|
||||
* com.jme3.animation.AnimControl
|
||||
* com.jme3.animation.AnimEventListener
|
||||
* com.jme3.app.SimpleApplication
|
||||
* com.jme3.app.state.AppState
|
||||
* com.jme3.bullet.BulletAppState
|
||||
* com.jme3.bullet.BulletAppState$ThreadingType
|
||||
* com.jme3.bullet.collision.PhysicsCollisionEvent
|
||||
* com.jme3.bullet.collision.PhysicsCollisionListener
|
||||
* com.jme3.bullet.collision.shapes.CapsuleCollisionShape
|
||||
* com.jme3.bullet.collision.shapes.CollisionShape
|
||||
* com.jme3.bullet.control.CharacterControl
|
||||
* com.jme3.bullet.control.RigidBodyControl
|
||||
* com.jme3.input.ChaseCamera
|
||||
* com.jme3.input.controls.ActionListener
|
||||
* com.jme3.input.controls.InputListener
|
||||
* com.jme3.input.controls.KeyTrigger
|
||||
* com.jme3.input.controls.Trigger
|
||||
* com.jme3.light.AmbientLight
|
||||
* com.jme3.light.DirectionalLight
|
||||
* com.jme3.light.Light
|
||||
* com.jme3.material.Material
|
||||
* com.jme3.math.ColorRGBA
|
||||
* com.jme3.math.Quaternion
|
||||
* com.jme3.math.Vector3f
|
||||
* com.jme3.scene.Geometry
|
||||
* com.jme3.scene.Mesh
|
||||
* com.jme3.scene.Node
|
||||
* com.jme3.scene.Spatial
|
||||
* com.jme3.scene.control.Control
|
||||
* com.jme3.scene.shape.Quad
|
||||
*/
|
||||
package jme3.hello;
|
||||
|
||||
import com.jme3.animation.AnimChannel;
|
||||
import com.jme3.animation.AnimControl;
|
||||
import com.jme3.animation.AnimEventListener;
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.app.state.AppState;
|
||||
import com.jme3.bullet.BulletAppState;
|
||||
import com.jme3.bullet.collision.PhysicsCollisionEvent;
|
||||
import com.jme3.bullet.collision.PhysicsCollisionListener;
|
||||
import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
|
||||
import com.jme3.bullet.collision.shapes.CollisionShape;
|
||||
import com.jme3.bullet.control.CharacterControl;
|
||||
import com.jme3.bullet.control.RigidBodyControl;
|
||||
import com.jme3.input.ChaseCamera;
|
||||
import com.jme3.input.controls.ActionListener;
|
||||
import com.jme3.input.controls.InputListener;
|
||||
import com.jme3.input.controls.KeyTrigger;
|
||||
import com.jme3.input.controls.Trigger;
|
||||
import com.jme3.light.AmbientLight;
|
||||
import com.jme3.light.DirectionalLight;
|
||||
import com.jme3.light.Light;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Mesh;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.control.Control;
|
||||
import com.jme3.scene.shape.Quad;
|
||||
|
||||
public class test
|
||||
extends SimpleApplication
|
||||
implements ActionListener,
|
||||
PhysicsCollisionListener,
|
||||
AnimEventListener {
|
||||
private BulletAppState bulletAppState;
|
||||
CharacterControl character;
|
||||
Node model;
|
||||
Vector3f walkDirection = new Vector3f();
|
||||
Geometry ground;
|
||||
ChaseCamera chaseCam;
|
||||
boolean left = false;
|
||||
boolean right = false;
|
||||
boolean up = false;
|
||||
boolean down = false;
|
||||
|
||||
public static void main(String[] args) {
|
||||
test app = new test();
|
||||
app.start();
|
||||
}
|
||||
|
||||
public void simpleInitApp() {
|
||||
this.bulletAppState = new BulletAppState();
|
||||
this.bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL);
|
||||
this.stateManager.attach((AppState)this.bulletAppState);
|
||||
this.setupKeys();
|
||||
this.createLight();
|
||||
this.createCharacter();
|
||||
Material mat = new Material(this.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat.setTexture("ColorMap", this.assetManager.loadTexture("Textures/Rock.PNG"));
|
||||
this.ground = new Geometry("ground", (Mesh)new Quad(50.0f, 50.0f));
|
||||
this.ground.setLocalRotation(new Quaternion().fromAngleAxis(-1.5707964f, Vector3f.UNIT_X));
|
||||
this.ground.setLocalTranslation(0.0f, 0.0f, 0.0f);
|
||||
this.ground.setMaterial(mat);
|
||||
RigidBodyControl ground_solid = new RigidBodyControl(0.0f);
|
||||
this.ground.addControl((Control)ground_solid);
|
||||
this.bulletAppState.getPhysicsSpace().add((Object)ground_solid);
|
||||
this.rootNode.attachChild((Spatial)this.ground);
|
||||
this.setupChaseCamera();
|
||||
}
|
||||
|
||||
private void setupKeys() {
|
||||
this.inputManager.addMapping("CharLeft", new Trigger[]{new KeyTrigger(30)});
|
||||
this.inputManager.addMapping("CharRight", new Trigger[]{new KeyTrigger(32)});
|
||||
this.inputManager.addMapping("CharUp", new Trigger[]{new KeyTrigger(17)});
|
||||
this.inputManager.addMapping("CharDown", new Trigger[]{new KeyTrigger(31)});
|
||||
this.inputManager.addListener((InputListener)this, new String[]{"CharLeft", "CharRight", "CharUp", "CharDown"});
|
||||
}
|
||||
|
||||
private void createLight() {
|
||||
AmbientLight al = new AmbientLight();
|
||||
al.setColor(ColorRGBA.White.mult(1.3f));
|
||||
this.rootNode.addLight((Light)al);
|
||||
DirectionalLight dl = new DirectionalLight();
|
||||
dl.setColor(ColorRGBA.White);
|
||||
dl.setDirection(new Vector3f(2.8f, -2.8f, -2.8f).normalizeLocal());
|
||||
this.rootNode.addLight((Light)dl);
|
||||
}
|
||||
|
||||
private void createGround() {
|
||||
Material mat = new Material(this.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat.setTexture("ColorMap", this.assetManager.loadTexture("Textures/Rock.PNG"));
|
||||
this.ground = new Geometry("ground", (Mesh)new Quad(50.0f, 50.0f));
|
||||
this.ground.setLocalRotation(new Quaternion().fromAngleAxis(-1.5707964f, Vector3f.UNIT_X));
|
||||
this.ground.setLocalTranslation(0.0f, 0.0f, 0.0f);
|
||||
this.ground.setMaterial(mat);
|
||||
RigidBodyControl ground_solid = new RigidBodyControl(0.0f);
|
||||
this.ground.addControl((Control)ground_solid);
|
||||
this.bulletAppState.getPhysicsSpace().add((Object)ground_solid);
|
||||
this.rootNode.attachChild((Spatial)this.ground);
|
||||
}
|
||||
|
||||
private void createCharacter() {
|
||||
CapsuleCollisionShape capsule = new CapsuleCollisionShape(1.0f, 1.0f);
|
||||
this.character = new CharacterControl((CollisionShape)capsule, 1.0f);
|
||||
this.model = (Node)this.assetManager.loadModel("Models/spider/Spider.mesh.j3o");
|
||||
this.model.addControl((Control)this.character);
|
||||
this.model.setLocalTranslation(0.0f, 10.0f, 0.0f);
|
||||
this.character.setPhysicsLocation(new Vector3f(-140.0f, 15.0f, -10.0f));
|
||||
this.rootNode.attachChild((Spatial)this.model);
|
||||
this.bulletAppState.getPhysicsSpace().add((Object)this.character);
|
||||
}
|
||||
|
||||
private void setupChaseCamera() {
|
||||
this.flyCam.setEnabled(false);
|
||||
this.chaseCam = new ChaseCamera(this.cam, (Spatial)this.model, this.inputManager);
|
||||
}
|
||||
|
||||
public void simpleUpdate(float tpf) {
|
||||
this.walkDirection.set(0.0f, 0.0f, 0.0f);
|
||||
Vector3f camDir = this.cam.getDirection().clone().multLocal(0.1f);
|
||||
Vector3f camLeft = this.cam.getLeft().clone().multLocal(0.1f);
|
||||
camDir.y = 0.0f;
|
||||
camLeft.y = 0.0f;
|
||||
this.walkDirection.set(0.0f, 0.0f, 0.0f);
|
||||
if (this.left) {
|
||||
this.walkDirection.addLocal(camLeft);
|
||||
}
|
||||
if (this.right) {
|
||||
this.walkDirection.addLocal(camLeft.negate());
|
||||
}
|
||||
if (this.up) {
|
||||
this.walkDirection.addLocal(camDir);
|
||||
}
|
||||
if (this.down) {
|
||||
this.walkDirection.addLocal(camDir.negate());
|
||||
}
|
||||
if (this.walkDirection.length() != 0.0f) {
|
||||
this.character.setViewDirection(this.walkDirection);
|
||||
}
|
||||
this.character.setWalkDirection(this.walkDirection);
|
||||
}
|
||||
|
||||
public void onAction(String binding, boolean value, float tpf) {
|
||||
if (binding.equals("CharLeft")) {
|
||||
this.left = value;
|
||||
} else if (binding.equals("CharRight")) {
|
||||
this.right = value;
|
||||
} else if (binding.equals("CharUp")) {
|
||||
this.up = value;
|
||||
} else if (binding.equals("CharDown")) {
|
||||
this.down = value;
|
||||
}
|
||||
}
|
||||
|
||||
public void collision(PhysicsCollisionEvent event) {
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) {
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
public void onAnimChange(AnimControl control, AnimChannel channel, String animName) {
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*/
|
||||
package mygame;
|
||||
|
||||
interface AnimeEventListener {
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* com.jme3.animation.AnimChannel
|
||||
* com.jme3.animation.AnimControl
|
||||
* com.jme3.scene.Spatial
|
||||
*/
|
||||
package mygame;
|
||||
|
||||
import com.jme3.animation.AnimChannel;
|
||||
import com.jme3.animation.AnimControl;
|
||||
import com.jme3.scene.Spatial;
|
||||
import mygame.Interactive;
|
||||
|
||||
public abstract class Character
|
||||
extends Interactive {
|
||||
public AnimChannel modelAnimChannel;
|
||||
public AnimControl modelAnimControl;
|
||||
|
||||
public Character(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
public void setModel(Spatial model) {
|
||||
super.setModel(model);
|
||||
this.setupModelControl();
|
||||
}
|
||||
|
||||
public abstract void setupModelControl();
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* com.jme3.animation.AnimControl
|
||||
* com.jme3.bullet.collision.shapes.CapsuleCollisionShape
|
||||
* com.jme3.bullet.collision.shapes.CollisionShape
|
||||
* com.jme3.bullet.control.GhostControl
|
||||
* com.jme3.effect.ParticleEmitter
|
||||
* com.jme3.effect.ParticleMesh$Type
|
||||
* com.jme3.light.Light
|
||||
* com.jme3.light.PointLight
|
||||
* com.jme3.material.Material
|
||||
* com.jme3.math.ColorRGBA
|
||||
* com.jme3.math.Vector3f
|
||||
* com.jme3.scene.Spatial
|
||||
* com.jme3.scene.control.Control
|
||||
*/
|
||||
package mygame;
|
||||
|
||||
import com.jme3.animation.AnimControl;
|
||||
import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
|
||||
import com.jme3.bullet.collision.shapes.CollisionShape;
|
||||
import com.jme3.bullet.control.GhostControl;
|
||||
import com.jme3.effect.ParticleEmitter;
|
||||
import com.jme3.effect.ParticleMesh;
|
||||
import com.jme3.light.Light;
|
||||
import com.jme3.light.PointLight;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.control.Control;
|
||||
import mygame.Character;
|
||||
import mygame.CrossControl;
|
||||
import mygame.Exchange;
|
||||
|
||||
public class Cross
|
||||
extends Character {
|
||||
public GhostControl collision;
|
||||
public CrossControl control;
|
||||
private PointLight light;
|
||||
|
||||
public Cross(String name) {
|
||||
super(name);
|
||||
this.setModel();
|
||||
this.setupControl();
|
||||
}
|
||||
|
||||
public void setupModelControl() {
|
||||
this.modelAnimControl = (AnimControl)this.model.getControl(AnimControl.class);
|
||||
this.modelAnimChannel = this.modelAnimControl.createChannel();
|
||||
this.modelAnimChannel.setAnim("normal");
|
||||
this.modelAnimChannel.setSpeed(0.5f);
|
||||
}
|
||||
|
||||
void setupModel() {
|
||||
this.wholeBody.attachChild(this.model);
|
||||
}
|
||||
|
||||
public void setupControl() {
|
||||
this.collision = new GhostControl((CollisionShape)new CapsuleCollisionShape(1.0f, 0.5f));
|
||||
this.collision.addCollideWithGroup(2);
|
||||
this.addControl((Control)this.collision);
|
||||
this.control = new CrossControl();
|
||||
this.addControl((Control)this.control);
|
||||
Exchange.bulletAppState.getPhysicsSpace().add((Object)this.collision);
|
||||
}
|
||||
|
||||
public void setModel() {
|
||||
this.model = Exchange.assetManager.loadModel("Models/kross/kross.mesh.j3o");
|
||||
this.setLocalScale(0.25f);
|
||||
this.setupModel();
|
||||
this.setupModelControl();
|
||||
this.wholeBody.attachChild(this.model);
|
||||
ParticleEmitter funken = new ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, 50);
|
||||
Material mat_red = new Material(Exchange.assetManager, "Common/MatDefs/Misc/Particle.j3md");
|
||||
mat_red.setTexture("Texture", Exchange.assetManager.loadTexture("Effects/flame.png"));
|
||||
funken.setMaterial(mat_red);
|
||||
funken.setImagesX(2);
|
||||
funken.setImagesY(2);
|
||||
funken.setEndColor(new ColorRGBA(0.0f, 1.0f, 0.0f, 1.0f));
|
||||
funken.setStartColor(new ColorRGBA(0.0f, 1.0f, 0.0f, 1.0f));
|
||||
funken.getParticleInfluencer().setInitialVelocity(new Vector3f(0.25f, 2.0f, 0.25f));
|
||||
funken.setStartSize(0.1f);
|
||||
funken.setEndSize(0.1f);
|
||||
funken.setGravity(0.0f, 0.0f, 0.0f);
|
||||
funken.setLowLife(1.0f);
|
||||
funken.setHighLife(2.0f);
|
||||
funken.getParticleInfluencer().setVelocityVariation(1.0f);
|
||||
this.attachChild((Spatial)funken);
|
||||
}
|
||||
|
||||
public void kill() {
|
||||
this.removeFromParent();
|
||||
Exchange.rootNode.removeLight((Light)this.light);
|
||||
}
|
||||
|
||||
public void setLight() {
|
||||
this.light = new PointLight();
|
||||
this.light.setPosition(this.getLocalTranslation().add(new Vector3f(0.0f, 1.0f, 0.0f)));
|
||||
this.light.setColor(ColorRGBA.Green);
|
||||
this.light.setRadius(10.0f);
|
||||
Exchange.rootNode.addLight((Light)this.light);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* com.jme3.renderer.RenderManager
|
||||
* com.jme3.renderer.ViewPort
|
||||
* com.jme3.scene.control.AbstractControl
|
||||
*/
|
||||
package mygame;
|
||||
|
||||
import com.jme3.renderer.RenderManager;
|
||||
import com.jme3.renderer.ViewPort;
|
||||
import com.jme3.scene.control.AbstractControl;
|
||||
import mygame.Cross;
|
||||
import mygame.Exchange;
|
||||
import mygame.Player;
|
||||
|
||||
public class CrossControl
|
||||
extends AbstractControl {
|
||||
protected void controlUpdate(float tpf) {
|
||||
Cross cross = (Cross)this.spatial;
|
||||
Player player = (Player)Exchange.rootNode.getChild("Player");
|
||||
if (cross.collision.getOverlappingObjects().contains(player.collision)) {
|
||||
player.heal(25.0f);
|
||||
cross.kill();
|
||||
}
|
||||
}
|
||||
|
||||
protected void controlRender(RenderManager rm, ViewPort vp) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* com.jme3.app.SimpleApplication
|
||||
* com.jme3.asset.AssetManager
|
||||
* com.jme3.bullet.BulletAppState
|
||||
* com.jme3.math.Vector3f
|
||||
* com.jme3.renderer.ViewPort
|
||||
* com.jme3.scene.Node
|
||||
* com.jme3.scene.Spatial
|
||||
*/
|
||||
package mygame;
|
||||
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.asset.AssetManager;
|
||||
import com.jme3.bullet.BulletAppState;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.renderer.ViewPort;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.Spatial;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Random;
|
||||
import mygame.Cross;
|
||||
import mygame.WalkingEnemy;
|
||||
|
||||
public class Exchange {
|
||||
public static ArrayList<WalkingEnemy> enemies;
|
||||
public static Node rootNode;
|
||||
public static BulletAppState bulletAppState;
|
||||
public static AssetManager assetManager;
|
||||
public static ViewPort viewPort;
|
||||
public static SimpleApplication app;
|
||||
public static int killed;
|
||||
|
||||
public static int getKilled() {
|
||||
return killed;
|
||||
}
|
||||
|
||||
public static void setKilled(int killed) {
|
||||
Exchange.killed = killed;
|
||||
}
|
||||
|
||||
public static SimpleApplication getApp() {
|
||||
return app;
|
||||
}
|
||||
|
||||
public static void setApp(SimpleApplication app) {
|
||||
Exchange.app = app;
|
||||
}
|
||||
|
||||
public static AssetManager getAssetManager() {
|
||||
return assetManager;
|
||||
}
|
||||
|
||||
public static void setAssetManager(AssetManager assetManager) {
|
||||
Exchange.assetManager = assetManager;
|
||||
}
|
||||
|
||||
public static BulletAppState getBulletAppState() {
|
||||
return bulletAppState;
|
||||
}
|
||||
|
||||
public static void setBulletAppState(BulletAppState bulletAppState) {
|
||||
Exchange.bulletAppState = bulletAppState;
|
||||
}
|
||||
|
||||
public static Node getRootNode() {
|
||||
return rootNode;
|
||||
}
|
||||
|
||||
public static void setRootNode(Node rootNode) {
|
||||
Exchange.rootNode = rootNode;
|
||||
}
|
||||
|
||||
public Exchange() {
|
||||
enemies = new ArrayList();
|
||||
}
|
||||
|
||||
public static void removeEnemy(WalkingEnemy en) {
|
||||
enemies.remove((Object)en);
|
||||
}
|
||||
|
||||
public static ViewPort getViewPort() {
|
||||
return viewPort;
|
||||
}
|
||||
|
||||
public static void setViewPort(ViewPort viewPort) {
|
||||
Exchange.viewPort = viewPort;
|
||||
}
|
||||
|
||||
public static void spawnItem(Vector3f place) {
|
||||
Random r = new Random();
|
||||
if (r.nextInt(100) > 90) {
|
||||
Cross cross = new Cross("Cross");
|
||||
cross.setLocalTranslation(place);
|
||||
cross.setLight();
|
||||
rootNode.attachChild((Spatial)cross);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* com.jme3.asset.AssetManager
|
||||
* com.jme3.audio.AudioNode
|
||||
* com.jme3.bullet.collision.shapes.CapsuleCollisionShape
|
||||
* com.jme3.bullet.collision.shapes.CollisionShape
|
||||
* com.jme3.bullet.control.BetterCharacterControl
|
||||
* com.jme3.bullet.control.GhostControl
|
||||
* com.jme3.effect.ParticleEmitter
|
||||
* com.jme3.effect.ParticleMesh$Type
|
||||
* com.jme3.light.Light
|
||||
* com.jme3.light.PointLight
|
||||
* com.jme3.material.Material
|
||||
* com.jme3.math.ColorRGBA
|
||||
* com.jme3.math.Vector3f
|
||||
* com.jme3.post.SceneProcessor
|
||||
* com.jme3.scene.Node
|
||||
* com.jme3.scene.Spatial
|
||||
* com.jme3.scene.control.Control
|
||||
* com.jme3.scene.control.LightControl
|
||||
* com.jme3.shadow.EdgeFilteringMode
|
||||
* com.jme3.shadow.PointLightShadowFilter
|
||||
* com.jme3.shadow.PointLightShadowRenderer
|
||||
*/
|
||||
package mygame;
|
||||
|
||||
import com.jme3.asset.AssetManager;
|
||||
import com.jme3.audio.AudioNode;
|
||||
import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
|
||||
import com.jme3.bullet.collision.shapes.CollisionShape;
|
||||
import com.jme3.bullet.control.BetterCharacterControl;
|
||||
import com.jme3.bullet.control.GhostControl;
|
||||
import com.jme3.effect.ParticleEmitter;
|
||||
import com.jme3.effect.ParticleMesh;
|
||||
import com.jme3.light.Light;
|
||||
import com.jme3.light.PointLight;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.post.SceneProcessor;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.control.Control;
|
||||
import com.jme3.scene.control.LightControl;
|
||||
import com.jme3.shadow.EdgeFilteringMode;
|
||||
import com.jme3.shadow.PointLightShadowFilter;
|
||||
import com.jme3.shadow.PointLightShadowRenderer;
|
||||
import mygame.Exchange;
|
||||
import mygame.FireballControl;
|
||||
|
||||
public class Fireball
|
||||
extends Node {
|
||||
ParticleEmitter fire;
|
||||
PointLight light;
|
||||
LightControl lightControl;
|
||||
GhostControl collisionGhost;
|
||||
GhostControl damageAreaGhost;
|
||||
BetterCharacterControl bodyControl;
|
||||
PointLightShadowRenderer dlsr;
|
||||
PointLightShadowFilter dlsf;
|
||||
FireballControl fireballControl;
|
||||
private AssetManager assetManager;
|
||||
Vector3f direction;
|
||||
Exchange exchange;
|
||||
boolean explode;
|
||||
float timer;
|
||||
float lifetime = 1.0f;
|
||||
|
||||
public Fireball(AssetManager assetManager, Vector3f finish, Vector3f start, Exchange ex) {
|
||||
this.direction = finish;
|
||||
this.exchange = ex;
|
||||
assetManager = Exchange.assetManager;
|
||||
this.setLocalTranslation(start.add(new Vector3f(1.0f, 1.0f, 1.0f)));
|
||||
this.fire = new ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, 30);
|
||||
Material mat_red = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
|
||||
mat_red.setTexture("Texture", assetManager.loadTexture("Effects/flame.png"));
|
||||
this.fire.setMaterial(mat_red);
|
||||
this.fire.setImagesX(2);
|
||||
this.fire.setImagesY(2);
|
||||
this.fire.setEndColor(new ColorRGBA(1.0f, 0.0f, 0.0f, 1.0f));
|
||||
this.fire.setStartColor(new ColorRGBA(1.0f, 1.0f, 0.0f, 0.5f));
|
||||
this.fire.getParticleInfluencer().setInitialVelocity(new Vector3f(0.0f, 2.0f, 0.0f));
|
||||
this.fire.setStartSize(1.5f);
|
||||
this.fire.setEndSize(0.1f);
|
||||
this.fire.setGravity(0.0f, 0.0f, 0.0f);
|
||||
this.fire.setLowLife(1.0f);
|
||||
this.fire.setHighLife(3.0f);
|
||||
this.fire.getParticleInfluencer().setVelocityVariation(0.3f);
|
||||
this.attachChild((Spatial)this.fire);
|
||||
this.light = new PointLight();
|
||||
this.light.setColor(new ColorRGBA(1.0f, 0.5f, 0.0f, 1.0f));
|
||||
this.light.setPosition(this.getLocalTranslation());
|
||||
this.light.setRadius(100.0f);
|
||||
Exchange.rootNode.addLight((Light)this.light);
|
||||
this.dlsr = new PointLightShadowRenderer(assetManager, 128);
|
||||
this.dlsr.setLight(this.light);
|
||||
this.dlsr.setShadowIntensity(0.2f);
|
||||
this.dlsr.setEdgeFilteringMode(EdgeFilteringMode.Dither);
|
||||
Exchange.viewPort.addProcessor((SceneProcessor)this.dlsr);
|
||||
this.dlsf = new PointLightShadowFilter(assetManager, 128);
|
||||
this.dlsf.setLight(this.light);
|
||||
this.dlsf.setShadowIntensity(0.2f);
|
||||
this.dlsf.setEdgeFilteringMode(EdgeFilteringMode.Dither);
|
||||
this.dlsf.setEnabled(false);
|
||||
this.collisionGhost = new GhostControl((CollisionShape)new CapsuleCollisionShape(1.0f, 0.5f));
|
||||
this.collisionGhost.setSpatial((Spatial)this);
|
||||
this.collisionGhost.addCollideWithGroup(1);
|
||||
this.addControl((Control)this.collisionGhost);
|
||||
this.damageAreaGhost = new GhostControl((CollisionShape)new CapsuleCollisionShape(5.0f, 1.0f));
|
||||
this.damageAreaGhost.addCollideWithGroup(1);
|
||||
this.addControl((Control)this.damageAreaGhost);
|
||||
this.bodyControl = new BetterCharacterControl(0.1f, 0.1f, 1.0f);
|
||||
this.addControl((Control)this.bodyControl);
|
||||
this.fireballControl = new FireballControl();
|
||||
this.addControl((Control)this.fireballControl);
|
||||
Exchange.rootNode.attachChild((Spatial)this);
|
||||
Exchange.bulletAppState.getPhysicsSpace().add((Object)this.damageAreaGhost);
|
||||
Exchange.bulletAppState.getPhysicsSpace().add((Object)this.collisionGhost);
|
||||
Exchange.bulletAppState.getPhysicsSpace().add((Object)this.bodyControl);
|
||||
AudioNode hit = new AudioNode(assetManager, "Sounds/Effects/fireball.ogg", false);
|
||||
hit.setLooping(false);
|
||||
hit.setPositional(true);
|
||||
hit.setLocalTranslation(this.getLocalTranslation());
|
||||
hit.setVolume(5.0f);
|
||||
this.attachChild((Spatial)hit);
|
||||
hit.play();
|
||||
}
|
||||
|
||||
public void explode() {
|
||||
ParticleEmitter xplosion = new ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, 50);
|
||||
Material mat_red = new Material(Exchange.assetManager, "Common/MatDefs/Misc/Particle.j3md");
|
||||
mat_red.setTexture("Texture", Exchange.assetManager.loadTexture("Effects/flame.png"));
|
||||
xplosion.setMaterial(mat_red);
|
||||
xplosion.setImagesX(2);
|
||||
xplosion.setParticlesPerSec(0.0f);
|
||||
xplosion.setImagesY(2);
|
||||
xplosion.setEndColor(new ColorRGBA(1.0f, 0.0f, 0.0f, 1.0f));
|
||||
xplosion.setStartColor(new ColorRGBA(1.0f, 1.0f, 0.0f, 0.5f));
|
||||
xplosion.getParticleInfluencer().setInitialVelocity(new Vector3f(0.5f, 0.5f, 0.5f));
|
||||
xplosion.setStartSize(1.5f);
|
||||
xplosion.setEndSize(2.0f);
|
||||
xplosion.setGravity(0.0f, 1.0f, 0.0f);
|
||||
xplosion.setLowLife(1.0f);
|
||||
xplosion.setHighLife(2.0f);
|
||||
xplosion.getParticleInfluencer().setVelocityVariation(2.0f);
|
||||
this.attachChild((Spatial)xplosion);
|
||||
xplosion.emitAllParticles();
|
||||
ParticleEmitter stones = new ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, 20);
|
||||
Material mat_stones = new Material(Exchange.assetManager, "Common/MatDefs/Misc/Particle.j3md");
|
||||
mat_stones.setTexture("Texture", Exchange.assetManager.loadTexture("Effects/stein.png"));
|
||||
mat_stones.setTexture("GlowMap", Exchange.assetManager.loadTexture("Effects/stein.png"));
|
||||
mat_stones.setTexture("DepthTexture", Exchange.assetManager.loadTexture("Effects/stein.png"));
|
||||
stones.setMaterial(mat_stones);
|
||||
stones.setImagesX(2);
|
||||
stones.setParticlesPerSec(0.0f);
|
||||
stones.setImagesY(2);
|
||||
stones.setEndColor(new ColorRGBA(0.1f, 0.1f, 0.1f, 1.0f));
|
||||
stones.setStartColor(new ColorRGBA(0.0f, 0.0f, 0.0f, 1.0f));
|
||||
stones.getParticleInfluencer().setInitialVelocity(new Vector3f(2.0f, 0.0f, 2.0f));
|
||||
stones.setStartSize(0.1f);
|
||||
stones.setEndSize(0.5f);
|
||||
stones.setGravity(2.0f, 0.0f, 2.0f);
|
||||
stones.setLowLife(1.0f);
|
||||
stones.setHighLife(1.0f);
|
||||
stones.getParticleInfluencer().setVelocityVariation(5.0f);
|
||||
this.attachChild((Spatial)stones);
|
||||
stones.emitAllParticles();
|
||||
AudioNode explode = new AudioNode(Exchange.assetManager, "Sounds/Effects/explosion.ogg", false);
|
||||
explode.setLooping(false);
|
||||
explode.setPositional(true);
|
||||
explode.setLocalTranslation(this.getLocalTranslation());
|
||||
explode.setVolume(5.0f);
|
||||
this.attachChild((Spatial)explode);
|
||||
explode.play();
|
||||
this.explode = true;
|
||||
this.fire.emitAllParticles();
|
||||
this.timer = 0.1f;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* com.jme3.light.Light
|
||||
* com.jme3.math.Vector3f
|
||||
* com.jme3.post.SceneProcessor
|
||||
* com.jme3.renderer.RenderManager
|
||||
* com.jme3.renderer.ViewPort
|
||||
* com.jme3.scene.control.AbstractControl
|
||||
* com.jme3.scene.control.Control
|
||||
*/
|
||||
package mygame;
|
||||
|
||||
import com.jme3.light.Light;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.post.SceneProcessor;
|
||||
import com.jme3.renderer.RenderManager;
|
||||
import com.jme3.renderer.ViewPort;
|
||||
import com.jme3.scene.control.AbstractControl;
|
||||
import com.jme3.scene.control.Control;
|
||||
import mygame.Exchange;
|
||||
import mygame.Fireball;
|
||||
import mygame.WalkingEnemy;
|
||||
|
||||
public class FireballControl
|
||||
extends AbstractControl {
|
||||
protected void controlUpdate(float tpf) {
|
||||
WalkingEnemy enemy;
|
||||
Fireball fireball = (Fireball)this.spatial;
|
||||
if (!fireball.explode) {
|
||||
fireball.bodyControl.setWalkDirection(fireball.direction);
|
||||
} else {
|
||||
fireball.bodyControl.setWalkDirection(Vector3f.ZERO);
|
||||
}
|
||||
Vector3f lightpos = fireball.collisionGhost.getPhysicsLocation();
|
||||
lightpos.setY(0.5f);
|
||||
fireball.light.setPosition(lightpos);
|
||||
int i = 0;
|
||||
while (true) {
|
||||
Exchange cfr_ignored_0 = fireball.exchange;
|
||||
if (i >= Exchange.enemies.size()) break;
|
||||
Exchange cfr_ignored_1 = fireball.exchange;
|
||||
enemy = Exchange.enemies.get(i);
|
||||
if (fireball.collisionGhost.getOverlappingObjects().contains(enemy.ghost)) {
|
||||
fireball.explode = true;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
if (fireball.explode) {
|
||||
i = 0;
|
||||
while (true) {
|
||||
Exchange cfr_ignored_2 = fireball.exchange;
|
||||
if (i >= Exchange.enemies.size()) break;
|
||||
Exchange cfr_ignored_3 = fireball.exchange;
|
||||
enemy = Exchange.enemies.get(i);
|
||||
if (fireball.damageAreaGhost.getOverlappingObjects().contains(enemy.ghost)) {
|
||||
enemy.doDamage(100.0f);
|
||||
fireball.explode();
|
||||
}
|
||||
++i;
|
||||
}
|
||||
}
|
||||
fireball.lifetime -= tpf;
|
||||
if (fireball.lifetime <= 0.0f && !fireball.explode) {
|
||||
fireball.explode();
|
||||
}
|
||||
if (fireball.timer > 0.0f) {
|
||||
fireball.timer += tpf;
|
||||
if ((double)fireball.timer > 0.2) {
|
||||
fireball.bodyControl.setEnabled(false);
|
||||
fireball.removeControl((Control)fireball.bodyControl);
|
||||
fireball.removeControl((Control)fireball.collisionGhost);
|
||||
fireball.collisionGhost.setEnabled(false);
|
||||
fireball.removeControl((Control)fireball.damageAreaGhost);
|
||||
fireball.damageAreaGhost.setEnabled(false);
|
||||
fireball.fire.setEnabled(false);
|
||||
}
|
||||
if (fireball.timer > 2.0f) {
|
||||
fireball.removeFromParent();
|
||||
Exchange.viewPort.removeProcessor((SceneProcessor)fireball.dlsr);
|
||||
Exchange.rootNode.removeLight((Light)fireball.light);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void controlRender(RenderManager rm, ViewPort vp) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* com.jme3.renderer.queue.RenderQueue$ShadowMode
|
||||
* com.jme3.scene.Node
|
||||
* com.jme3.scene.Spatial
|
||||
*/
|
||||
package mygame;
|
||||
|
||||
import com.jme3.renderer.queue.RenderQueue;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.Spatial;
|
||||
|
||||
public abstract class Interactive
|
||||
extends Node {
|
||||
public Spatial model;
|
||||
public Node wholeBody;
|
||||
|
||||
public Interactive(String name) {
|
||||
super(name);
|
||||
this.setShadowMode(RenderQueue.ShadowMode.Cast);
|
||||
this.wholeBody = new Node(name);
|
||||
this.attachChild((Spatial)this.wholeBody);
|
||||
}
|
||||
|
||||
public Spatial getModel() {
|
||||
return this.model;
|
||||
}
|
||||
|
||||
public void setModel(Spatial model) {
|
||||
this.model = model;
|
||||
this.setupModel();
|
||||
this.wholeBody.attachChild(model);
|
||||
}
|
||||
|
||||
abstract void setupModel();
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*/
|
||||
package mygame;
|
||||
|
||||
import jme3.hello.TestCameraNode;
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
TestCameraNode app = new TestCameraNode();
|
||||
app.setDisplayFps(false);
|
||||
app.setDisplayStatView(false);
|
||||
app.start();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* com.jme3.animation.AnimChannel
|
||||
* com.jme3.animation.AnimControl
|
||||
* com.jme3.animation.LoopMode
|
||||
* com.jme3.bullet.collision.shapes.CapsuleCollisionShape
|
||||
* com.jme3.bullet.collision.shapes.CollisionShape
|
||||
* com.jme3.bullet.control.BetterCharacterControl
|
||||
* com.jme3.bullet.control.GhostControl
|
||||
* com.jme3.math.Vector3f
|
||||
* com.jme3.scene.Spatial
|
||||
* com.jme3.scene.control.Control
|
||||
*/
|
||||
package mygame;
|
||||
|
||||
import com.jme3.animation.AnimChannel;
|
||||
import com.jme3.animation.AnimControl;
|
||||
import com.jme3.animation.LoopMode;
|
||||
import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
|
||||
import com.jme3.bullet.collision.shapes.CollisionShape;
|
||||
import com.jme3.bullet.control.BetterCharacterControl;
|
||||
import com.jme3.bullet.control.GhostControl;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.control.Control;
|
||||
import mygame.Character;
|
||||
import mygame.Exchange;
|
||||
import mygame.Weapon;
|
||||
|
||||
public class Player
|
||||
extends Character {
|
||||
public Weapon rightHand;
|
||||
public Weapon leftHand;
|
||||
public Spatial hat;
|
||||
public Spatial model;
|
||||
public BetterCharacterControl playerControl;
|
||||
public AnimChannel leftHandAnimChannel;
|
||||
public AnimControl leftHandAnimControl;
|
||||
public AnimChannel rightHandAnimChannel;
|
||||
public AnimControl rightHandAnimControl;
|
||||
public Vector3f pt;
|
||||
public GhostControl collision;
|
||||
public float health = 100.0f;
|
||||
public static float MAX_HEALTH = 100.0f;
|
||||
|
||||
public Player(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
public Spatial getRightHand() {
|
||||
return this.rightHand;
|
||||
}
|
||||
|
||||
public void setRightHand(Weapon rightHand) {
|
||||
this.rightHand = rightHand;
|
||||
this.setupRightHand();
|
||||
}
|
||||
|
||||
public Spatial getLeftHand() {
|
||||
return this.leftHand;
|
||||
}
|
||||
|
||||
public void setLeftHand(Weapon leftHand) {
|
||||
this.leftHand = leftHand;
|
||||
this.setupLeftHand();
|
||||
}
|
||||
|
||||
public Spatial getHat() {
|
||||
return this.hat;
|
||||
}
|
||||
|
||||
public void setHat(Spatial hat) {
|
||||
this.hat = hat;
|
||||
this.setupHat();
|
||||
}
|
||||
|
||||
public Spatial getModel() {
|
||||
return this.model;
|
||||
}
|
||||
|
||||
public void setModel(Spatial model) {
|
||||
this.model = model;
|
||||
this.setupModel();
|
||||
this.setupControl();
|
||||
this.setupModelControl();
|
||||
this.wholeBody.attachChild(this.model);
|
||||
}
|
||||
|
||||
public void switchHands() {
|
||||
this.wholeBody.detachChild((Spatial)this.leftHand);
|
||||
this.wholeBody.detachChild((Spatial)this.rightHand);
|
||||
Weapon temp = this.rightHand;
|
||||
this.setRightHand(this.leftHand);
|
||||
this.setLeftHand(temp);
|
||||
}
|
||||
|
||||
public void die() {
|
||||
this.modelAnimChannel.setAnim("die");
|
||||
this.modelAnimChannel.setLoopMode(LoopMode.DontLoop);
|
||||
this.wholeBody.setLocalTranslation(0.0f, 0.5f, 0.0f);
|
||||
this.wholeBody.detachChild(this.hat);
|
||||
this.wholeBody.detachChild((Spatial)this.rightHand);
|
||||
this.wholeBody.detachChild((Spatial)this.leftHand);
|
||||
}
|
||||
|
||||
private void setupLeftHand() {
|
||||
this.leftHand.setLocalTranslation(0.4f, 0.3f, 0.4f);
|
||||
this.leftHand.setLocalScale(0.35f);
|
||||
this.wholeBody.attachChild((Spatial)this.leftHand);
|
||||
}
|
||||
|
||||
private void setupRightHand() {
|
||||
this.rightHand.setLocalTranslation(-0.4f, 0.3f, 0.4f);
|
||||
this.rightHand.setLocalScale(0.35f);
|
||||
this.wholeBody.attachChild((Spatial)this.rightHand);
|
||||
}
|
||||
|
||||
private void setupHat() {
|
||||
this.hat.setLocalScale(0.35f);
|
||||
this.hat.setLocalTranslation(0.0f, 0.9f, -0.2f);
|
||||
this.hat.rotate(-0.4f, 0.0f, 0.0f);
|
||||
this.wholeBody.attachChild(this.hat);
|
||||
}
|
||||
|
||||
public void setupModel() {
|
||||
this.model.setLocalScale(0.5f);
|
||||
this.wholeBody.attachChild(this.model);
|
||||
}
|
||||
|
||||
private void setupControl() {
|
||||
this.playerControl = new BetterCharacterControl(0.5f, 1.0f, 8.0f);
|
||||
this.addControl((Control)this.playerControl);
|
||||
this.collision = new GhostControl((CollisionShape)new CapsuleCollisionShape(1.0f, 0.5f));
|
||||
this.collision.addCollideWithGroup(2);
|
||||
this.addControl((Control)this.collision);
|
||||
Exchange.bulletAppState.getPhysicsSpace().add((Object)this.collision);
|
||||
}
|
||||
|
||||
public void setupModelControl() {
|
||||
this.modelAnimControl = (AnimControl)this.model.getControl(AnimControl.class);
|
||||
this.modelAnimChannel = this.modelAnimControl.createChannel();
|
||||
this.modelAnimChannel.setAnim("stand");
|
||||
}
|
||||
|
||||
public void heal(float amount) {
|
||||
this.health = amount + this.health > MAX_HEALTH ? MAX_HEALTH : (this.health += amount);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* com.jme3.animation.AnimControl
|
||||
* com.jme3.bullet.collision.shapes.CapsuleCollisionShape
|
||||
* com.jme3.bullet.collision.shapes.CollisionShape
|
||||
* com.jme3.bullet.control.GhostControl
|
||||
* com.jme3.effect.ParticleEmitter
|
||||
* com.jme3.effect.ParticleMesh$Type
|
||||
* com.jme3.material.Material
|
||||
* com.jme3.math.ColorRGBA
|
||||
* com.jme3.math.Quaternion
|
||||
* com.jme3.math.Vector3f
|
||||
* com.jme3.scene.Spatial
|
||||
* com.jme3.scene.control.Control
|
||||
*/
|
||||
package mygame;
|
||||
|
||||
import com.jme3.animation.AnimControl;
|
||||
import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
|
||||
import com.jme3.bullet.collision.shapes.CollisionShape;
|
||||
import com.jme3.bullet.control.GhostControl;
|
||||
import com.jme3.effect.ParticleEmitter;
|
||||
import com.jme3.effect.ParticleMesh;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.control.Control;
|
||||
import mygame.Character;
|
||||
import mygame.Exchange;
|
||||
|
||||
public class PowerUpShield
|
||||
extends Character {
|
||||
public GhostControl collision;
|
||||
|
||||
public PowerUpShield(String name) {
|
||||
super(name);
|
||||
this.setModel();
|
||||
this.setupControl();
|
||||
}
|
||||
|
||||
public void setupModelControl() {
|
||||
this.modelAnimControl = (AnimControl)this.model.getControl(AnimControl.class);
|
||||
this.modelAnimChannel = this.modelAnimControl.createChannel();
|
||||
this.modelAnimChannel.setAnim("normal");
|
||||
this.modelAnimChannel.setSpeed(0.5f);
|
||||
}
|
||||
|
||||
void setupModel() {
|
||||
this.wholeBody.attachChild(this.model);
|
||||
}
|
||||
|
||||
public void setupControl() {
|
||||
this.collision = new GhostControl((CollisionShape)new CapsuleCollisionShape(1.0f, 0.5f));
|
||||
this.collision.addCollideWithGroup(2);
|
||||
this.addControl((Control)this.collision);
|
||||
Exchange.bulletAppState.getPhysicsSpace().add((Object)this.collision);
|
||||
}
|
||||
|
||||
public void setModel() {
|
||||
this.model = Exchange.assetManager.loadModel("Models/shield/shield.mesh.j3o");
|
||||
this.model.setMaterial(Exchange.assetManager.loadMaterial("Materials/PowerUpShield.j3m"));
|
||||
this.model.rotate(new Quaternion().fromAngleAxis((float)Math.PI, new Vector3f(0.0f, 1.0f, 0.0f)));
|
||||
this.model.setLocalTranslation(0.0f, 1.0f, 0.0f);
|
||||
this.setLocalScale(0.5f);
|
||||
this.setupModel();
|
||||
this.setupModelControl();
|
||||
this.wholeBody.attachChild(this.model);
|
||||
ParticleEmitter bobble = new ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, 1);
|
||||
Material mat_red = Exchange.assetManager.loadMaterial("Materials/bubble.j3m");
|
||||
bobble.setMaterial(mat_red);
|
||||
bobble.setImagesX(1);
|
||||
bobble.setImagesY(1);
|
||||
bobble.setEndColor(new ColorRGBA(1.0f, 1.0f, 0.0f, 0.75f));
|
||||
bobble.setStartColor(new ColorRGBA(1.0f, 1.0f, 0.0f, 1.0f));
|
||||
bobble.getParticleInfluencer().setInitialVelocity(new Vector3f(0.0f, 0.0f, 0.0f));
|
||||
bobble.setStartSize(1.0f);
|
||||
bobble.setEndSize(1.1f);
|
||||
bobble.setGravity(0.0f, 0.0f, 0.0f);
|
||||
bobble.setLowLife(1.0f);
|
||||
bobble.setHighLife(1.0f);
|
||||
bobble.getParticleInfluencer().setVelocityVariation(0.0f);
|
||||
this.attachChild((Spatial)bobble);
|
||||
}
|
||||
|
||||
public void kill() {
|
||||
this.removeFromParent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* com.jme3.animation.AnimChannel
|
||||
* com.jme3.animation.AnimControl
|
||||
* com.jme3.animation.AnimEventListener
|
||||
*/
|
||||
package mygame;
|
||||
|
||||
import com.jme3.animation.AnimChannel;
|
||||
import com.jme3.animation.AnimControl;
|
||||
import com.jme3.animation.AnimEventListener;
|
||||
import mygame.Weapon;
|
||||
|
||||
public class SkillListener
|
||||
implements AnimEventListener {
|
||||
private Weapon weapon;
|
||||
|
||||
public SkillListener(Weapon w) {
|
||||
this.weapon = w;
|
||||
}
|
||||
|
||||
public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) {
|
||||
if (animName.equals("skill1")) {
|
||||
this.weapon.skill1 = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void onAnimChange(AnimControl control, AnimChannel channel, String animName) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* com.jme3.animation.LoopMode
|
||||
* com.jme3.asset.AssetManager
|
||||
* com.jme3.audio.AudioNode
|
||||
* com.jme3.bullet.control.GhostControl
|
||||
* com.jme3.math.Ray
|
||||
* com.jme3.math.Vector3f
|
||||
* com.jme3.scene.Spatial
|
||||
*/
|
||||
package mygame;
|
||||
|
||||
import com.jme3.animation.LoopMode;
|
||||
import com.jme3.asset.AssetManager;
|
||||
import com.jme3.audio.AudioNode;
|
||||
import com.jme3.bullet.control.GhostControl;
|
||||
import com.jme3.math.Ray;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Spatial;
|
||||
import mygame.Exchange;
|
||||
import mygame.Fireball;
|
||||
import mygame.Player;
|
||||
import mygame.Weapon;
|
||||
|
||||
public class Staff
|
||||
extends Weapon {
|
||||
GhostControl ghost;
|
||||
AssetManager assetManager;
|
||||
|
||||
public Staff(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
public Staff(String name, AssetManager am, Exchange ex) {
|
||||
super(name);
|
||||
this.assetManager = am;
|
||||
this.exchange = ex;
|
||||
this.COOLDOWN1_TIME = 5.0f;
|
||||
this.COOLDOWN2_TIME = 50.0f;
|
||||
}
|
||||
|
||||
protected void setupControl() {
|
||||
}
|
||||
|
||||
public void skill1() {
|
||||
if (this.cooldown1 <= 0.0f) {
|
||||
this.cooldown1 = this.COOLDOWN1_TIME;
|
||||
this.modelAnimChannel.setAnim("skill1");
|
||||
Player player = (Player)this.getParent().getParent();
|
||||
Ray ray = new Ray(this.getWorldTranslation(), player.pt);
|
||||
Vector3f direction = player.pt.subtract(this.getWorldTranslation()).normalize().mult(25.0f);
|
||||
direction.setY(0.0f);
|
||||
this.modelAnimChannel.setLoopMode(LoopMode.DontLoop);
|
||||
Fireball fireball = new Fireball(this.assetManager, direction, player.getLocalTranslation(), this.exchange);
|
||||
}
|
||||
}
|
||||
|
||||
public void skill2() {
|
||||
if (this.cooldown2 <= 0.0f) {
|
||||
this.cooldown2 = this.COOLDOWN2_TIME;
|
||||
AudioNode hit = new AudioNode(this.assetManager, "Sounds/Effects/teleport.ogg", false);
|
||||
hit.setLooping(false);
|
||||
hit.setPositional(true);
|
||||
hit.setLocalTranslation(this.getLocalTranslation());
|
||||
hit.setVolume(5.0f);
|
||||
this.attachChild((Spatial)hit);
|
||||
hit.play();
|
||||
Player player = (Player)Exchange.getRootNode().getChild("Player");
|
||||
Vector3f place = player.pt;
|
||||
place.setY(1.0f);
|
||||
player.playerControl.warp(place);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* com.jme3.animation.AnimEventListener
|
||||
* com.jme3.animation.LoopMode
|
||||
* com.jme3.asset.AssetManager
|
||||
* com.jme3.audio.AudioNode
|
||||
* com.jme3.bullet.collision.shapes.BoxCollisionShape
|
||||
* com.jme3.bullet.collision.shapes.CollisionShape
|
||||
* com.jme3.bullet.control.GhostControl
|
||||
* com.jme3.math.Vector3f
|
||||
* com.jme3.scene.Spatial
|
||||
* com.jme3.scene.control.Control
|
||||
*/
|
||||
package mygame;
|
||||
|
||||
import com.jme3.animation.AnimEventListener;
|
||||
import com.jme3.animation.LoopMode;
|
||||
import com.jme3.asset.AssetManager;
|
||||
import com.jme3.audio.AudioNode;
|
||||
import com.jme3.bullet.collision.shapes.BoxCollisionShape;
|
||||
import com.jme3.bullet.collision.shapes.CollisionShape;
|
||||
import com.jme3.bullet.control.GhostControl;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.control.Control;
|
||||
import mygame.Exchange;
|
||||
import mygame.Player;
|
||||
import mygame.SkillListener;
|
||||
import mygame.WalkingEnemy;
|
||||
import mygame.Weapon;
|
||||
|
||||
public class Sword
|
||||
extends Weapon
|
||||
implements Runnable {
|
||||
GhostControl ghost;
|
||||
AssetManager assetManager;
|
||||
|
||||
public Sword(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
public Sword(String name, AssetManager am, Exchange ex) {
|
||||
super(name);
|
||||
this.assetManager = am;
|
||||
this.exchange = ex;
|
||||
this.COOLDOWN1_TIME = 0.5f;
|
||||
}
|
||||
|
||||
protected void setupControl() {
|
||||
this.ghost = new GhostControl((CollisionShape)new BoxCollisionShape(new Vector3f(0.25f, 0.25f, 1.75f)));
|
||||
this.ghost.addCollideWithGroup(1);
|
||||
this.addControl((Control)this.ghost);
|
||||
this.modelAnimControl.addListener((AnimEventListener)new SkillListener(this));
|
||||
}
|
||||
|
||||
public void skill1() {
|
||||
if (this.cooldown1 <= 0.0f && !this.skill1) {
|
||||
this.skill1 = true;
|
||||
this.cooldown1 = this.COOLDOWN1_TIME;
|
||||
this.modelAnimChannel.setAnim("skill1");
|
||||
this.modelAnimChannel.setSpeed(2.0f);
|
||||
this.modelAnimChannel.setLoopMode(LoopMode.DontLoop);
|
||||
AudioNode hit = new AudioNode(this.assetManager, "Sounds/Effects/sword_hit1.ogg", false);
|
||||
hit.setLooping(false);
|
||||
hit.setPositional(true);
|
||||
hit.setLocalTranslation(this.getLocalTranslation());
|
||||
hit.setVolume(5.0f);
|
||||
this.attachChild((Spatial)hit);
|
||||
hit.play();
|
||||
int i = 0;
|
||||
while (true) {
|
||||
if (i >= Exchange.enemies.size()) break;
|
||||
WalkingEnemy enemy = Exchange.enemies.get(i);
|
||||
if (this.ghost.getOverlappingObjects().contains(enemy.ghost)) {
|
||||
enemy.doDamage(50.0f);
|
||||
Player player = (Player)this.getParent().getParent();
|
||||
}
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void run() {
|
||||
this.skill1();
|
||||
}
|
||||
|
||||
public void skill2() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* com.jme3.animation.AnimControl
|
||||
* com.jme3.animation.LoopMode
|
||||
* com.jme3.app.Application
|
||||
* com.jme3.asset.AssetManager
|
||||
* com.jme3.audio.AudioNode
|
||||
* com.jme3.bullet.collision.shapes.CapsuleCollisionShape
|
||||
* com.jme3.bullet.collision.shapes.CollisionShape
|
||||
* com.jme3.bullet.control.BetterCharacterControl
|
||||
* com.jme3.bullet.control.GhostControl
|
||||
* com.jme3.effect.ParticleEmitter
|
||||
* com.jme3.effect.ParticleMesh$Type
|
||||
* com.jme3.material.Material
|
||||
* com.jme3.math.ColorRGBA
|
||||
* com.jme3.math.Vector3f
|
||||
* com.jme3.scene.Spatial
|
||||
* com.jme3.scene.control.Control
|
||||
* tonegod.gui.core.Screen
|
||||
*/
|
||||
package mygame;
|
||||
|
||||
import com.jme3.animation.AnimControl;
|
||||
import com.jme3.animation.LoopMode;
|
||||
import com.jme3.app.Application;
|
||||
import com.jme3.asset.AssetManager;
|
||||
import com.jme3.audio.AudioNode;
|
||||
import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
|
||||
import com.jme3.bullet.collision.shapes.CollisionShape;
|
||||
import com.jme3.bullet.control.BetterCharacterControl;
|
||||
import com.jme3.bullet.control.GhostControl;
|
||||
import com.jme3.effect.ParticleEmitter;
|
||||
import com.jme3.effect.ParticleMesh;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.control.Control;
|
||||
import mygame.Character;
|
||||
import mygame.Exchange;
|
||||
import mygame.WalkingEnemyControl;
|
||||
import tonegod.gui.core.Screen;
|
||||
|
||||
public class WalkingEnemy
|
||||
extends Character {
|
||||
public BetterCharacterControl walkingEnemyControl;
|
||||
public GhostControl ghost;
|
||||
private WalkingEnemyControl control = new WalkingEnemyControl();
|
||||
private float health;
|
||||
private AssetManager assetManager;
|
||||
public float reverseWalk;
|
||||
public ParticleEmitter blood;
|
||||
public boolean dead = false;
|
||||
public float deadtimer = 0.0f;
|
||||
|
||||
public WalkingEnemy(String name) {
|
||||
super(name);
|
||||
this.addControl((Control)this.control);
|
||||
}
|
||||
|
||||
public WalkingEnemy(String name, AssetManager am) {
|
||||
super(name);
|
||||
this.addControl((Control)this.control);
|
||||
this.assetManager = am;
|
||||
}
|
||||
|
||||
public void setupModelControl() {
|
||||
this.modelAnimControl = (AnimControl)this.model.getControl(AnimControl.class);
|
||||
this.modelAnimChannel = this.modelAnimControl.createChannel();
|
||||
this.modelAnimChannel.setAnim("stand");
|
||||
}
|
||||
|
||||
public void setupGUI() {
|
||||
Screen screen = new Screen((Application)Exchange.app, "tonegod/gui/style/def/style_map.xml");
|
||||
}
|
||||
|
||||
public void setModel(Spatial model) {
|
||||
this.model = model;
|
||||
this.setupModel();
|
||||
this.setupControl();
|
||||
this.setupModelControl();
|
||||
this.wholeBody.attachChild(this.model);
|
||||
this.blood = new ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, 10);
|
||||
Material mat_red = this.assetManager.loadMaterial("Materials/blood.j3m");
|
||||
this.blood.setMaterial(mat_red);
|
||||
this.blood.setImagesX(2);
|
||||
this.blood.setImagesY(2);
|
||||
this.blood.setEndColor(new ColorRGBA(1.0f, 0.0f, 0.0f, 0.75f));
|
||||
this.blood.setStartColor(new ColorRGBA(1.0f, 0.0f, 0.0f, 0.25f));
|
||||
this.blood.getParticleInfluencer().setInitialVelocity(new Vector3f(1.0f, 2.0f, 1.0f));
|
||||
this.blood.setParticlesPerSec(0.0f);
|
||||
this.blood.setStartSize(1.0f);
|
||||
this.blood.setEndSize(1.0f);
|
||||
this.blood.setGravity(0.0f, 2.0f, 0.0f);
|
||||
this.blood.setLowLife(1.0f);
|
||||
this.blood.setHighLife(0.5f);
|
||||
this.blood.getParticleInfluencer().setVelocityVariation(0.3f);
|
||||
this.blood.setRandomAngle(true);
|
||||
this.blood.setSelectRandomImage(true);
|
||||
this.attachChild((Spatial)this.blood);
|
||||
}
|
||||
|
||||
void setupModel() {
|
||||
this.model.setLocalScale(0.5f);
|
||||
this.wholeBody.attachChild(this.model);
|
||||
}
|
||||
|
||||
private void setupControl() {
|
||||
this.walkingEnemyControl = new BetterCharacterControl(1.0f, 2.0f, 8.0f);
|
||||
this.addControl((Control)this.walkingEnemyControl);
|
||||
this.ghost = new GhostControl((CollisionShape)new CapsuleCollisionShape(1.0f, 0.5f));
|
||||
this.ghost.addCollideWithGroup(1);
|
||||
this.addControl((Control)this.ghost);
|
||||
}
|
||||
|
||||
public float getHealth() {
|
||||
return this.health;
|
||||
}
|
||||
|
||||
public void setHealth(float health) {
|
||||
this.health = health;
|
||||
}
|
||||
|
||||
public void doDamage(float damage) {
|
||||
this.health -= damage;
|
||||
this.reverseWalk = 0.5f;
|
||||
if (this.health <= 0.0f) {
|
||||
this.die();
|
||||
}
|
||||
AudioNode hit = new AudioNode(this.assetManager, "Sounds/Effects/damage.ogg", false);
|
||||
hit.setLooping(false);
|
||||
hit.setVolume(100.0f);
|
||||
this.getParent().attachChild((Spatial)hit);
|
||||
hit.play();
|
||||
this.blood.emitAllParticles();
|
||||
}
|
||||
|
||||
public void die() {
|
||||
this.modelAnimChannel.setAnim("die");
|
||||
this.modelAnimChannel.setLoopMode(LoopMode.DontLoop);
|
||||
this.ghost.setEnabled(false);
|
||||
this.walkingEnemyControl.setEnabled(false);
|
||||
AudioNode an = (AudioNode)this.getChild("walkingSound");
|
||||
an.stop();
|
||||
Exchange.removeEnemy(this);
|
||||
Exchange.setKilled(Exchange.killed + 1);
|
||||
Exchange.spawnItem(this.getLocalTranslation());
|
||||
this.dead = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* com.jme3.math.Vector3f
|
||||
* com.jme3.renderer.RenderManager
|
||||
* com.jme3.renderer.ViewPort
|
||||
* com.jme3.scene.control.AbstractControl
|
||||
*/
|
||||
package mygame;
|
||||
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.renderer.RenderManager;
|
||||
import com.jme3.renderer.ViewPort;
|
||||
import com.jme3.scene.control.AbstractControl;
|
||||
import mygame.WalkingEnemy;
|
||||
|
||||
public class WalkingEnemyControl
|
||||
extends AbstractControl {
|
||||
public WalkingEnemy walkingEnemy;
|
||||
|
||||
protected void controlUpdate(float tpf) {
|
||||
this.walkingEnemy = (WalkingEnemy)this.spatial;
|
||||
if (!this.walkingEnemy.dead) {
|
||||
Vector3f walkdirection = new Vector3f(this.walkingEnemy.walkingEnemyControl.getViewDirection().x, 0.0f, this.walkingEnemy.walkingEnemyControl.getViewDirection().z).normalize().mult(6.0f);
|
||||
if (this.walkingEnemy.reverseWalk > 0.0f) {
|
||||
walkdirection.negateLocal();
|
||||
this.walkingEnemy.reverseWalk -= tpf;
|
||||
}
|
||||
this.walkingEnemy.walkingEnemyControl.setViewDirection(this.spatial.getParent().getChild("Player").getLocalTranslation().subtract(this.spatial.getLocalTranslation()));
|
||||
this.walkingEnemy.walkingEnemyControl.setWalkDirection(walkdirection);
|
||||
} else {
|
||||
this.walkingEnemy.deadtimer += tpf;
|
||||
if (this.walkingEnemy.deadtimer > 10.0f) {
|
||||
this.walkingEnemy.removeFromParent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void controlRender(RenderManager rm, ViewPort vp) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* com.jme3.animation.AnimControl
|
||||
* com.jme3.bullet.control.GhostControl
|
||||
* com.jme3.scene.Spatial
|
||||
*/
|
||||
package mygame;
|
||||
|
||||
import com.jme3.animation.AnimControl;
|
||||
import com.jme3.bullet.control.GhostControl;
|
||||
import com.jme3.scene.Spatial;
|
||||
import mygame.Character;
|
||||
import mygame.Exchange;
|
||||
|
||||
public abstract class Weapon
|
||||
extends Character {
|
||||
GhostControl weaponControl;
|
||||
public Exchange exchange;
|
||||
public boolean skill1 = false;
|
||||
public float cooldown1 = 0.0f;
|
||||
public float cooldown2 = 0.0f;
|
||||
public float cooldown3 = 0.0f;
|
||||
public float COOLDOWN1_TIME = 0.0f;
|
||||
public float COOLDOWN2_TIME = 0.0f;
|
||||
public float COOLDOWN3_TIME = 0.0f;
|
||||
|
||||
public Weapon(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
public Weapon(String name, Exchange ex) {
|
||||
super(name);
|
||||
this.exchange = ex;
|
||||
}
|
||||
|
||||
public void setModel(Spatial model) {
|
||||
this.model = model;
|
||||
this.setupModel();
|
||||
this.setupModelControl();
|
||||
this.setupControl();
|
||||
this.wholeBody.attachChild(this.model);
|
||||
}
|
||||
|
||||
protected abstract void setupControl();
|
||||
|
||||
public void setupModelControl() {
|
||||
this.modelAnimControl = (AnimControl)this.model.getControl(AnimControl.class);
|
||||
this.modelAnimChannel = this.modelAnimControl.createChannel();
|
||||
this.modelAnimChannel.setAnim("normal");
|
||||
}
|
||||
|
||||
void setupModel() {
|
||||
this.wholeBody.attachChild(this.model);
|
||||
}
|
||||
|
||||
public void reduceCooldowns(float tpf) {
|
||||
if (this.cooldown1 > 0.0f) {
|
||||
this.cooldown1 -= tpf;
|
||||
}
|
||||
if (this.cooldown2 > 0.0f) {
|
||||
this.cooldown2 -= tpf;
|
||||
}
|
||||
if (this.cooldown3 > 0.0f) {
|
||||
this.cooldown3 -= tpf;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void skill1();
|
||||
|
||||
public abstract void skill2();
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* com.jme3.app.SimpleApplication
|
||||
* com.jme3.material.Material
|
||||
* com.jme3.math.Quaternion
|
||||
* com.jme3.math.Vector3f
|
||||
* com.jme3.scene.Geometry
|
||||
* com.jme3.scene.Mesh
|
||||
* com.jme3.scene.Spatial
|
||||
* com.jme3.scene.shape.Quad
|
||||
*/
|
||||
package mygame;
|
||||
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Mesh;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.shape.Quad;
|
||||
|
||||
public class test1
|
||||
extends SimpleApplication {
|
||||
private Spatial player;
|
||||
|
||||
public static void main(String[] args) {
|
||||
test1 app = new test1();
|
||||
app.start();
|
||||
}
|
||||
|
||||
public void simpleInitApp() {
|
||||
this.player = this.assetManager.loadModel("Models/blob/Blob.mesh.xml");
|
||||
Material mat_ground = new Material(this.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat_ground.setTexture("ColorMap", this.assetManager.loadTexture("Textures/Rock.PNG"));
|
||||
Geometry ground = new Geometry("ground", (Mesh)new Quad(50.0f, 50.0f));
|
||||
ground.setLocalRotation(new Quaternion().fromAngleAxis(-1.5707964f, Vector3f.UNIT_X));
|
||||
ground.setLocalTranslation(-25.0f, -1.0f, 25.0f);
|
||||
ground.setMaterial(mat_ground);
|
||||
this.rootNode.attachChild((Spatial)ground);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* com.jme3.math.Quaternion
|
||||
* com.jme3.math.Vector3f
|
||||
* com.jme3.renderer.Camera
|
||||
* com.jme3.scene.CameraNode
|
||||
* com.jme3.scene.Node
|
||||
* com.jme3.scene.Spatial
|
||||
* com.jme3.scene.control.CameraControl
|
||||
*/
|
||||
package wpq.tests;
|
||||
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.renderer.Camera;
|
||||
import com.jme3.scene.CameraNode;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.control.CameraControl;
|
||||
|
||||
public class PlayerCam
|
||||
extends CameraNode {
|
||||
private Node player;
|
||||
private Quaternion CAM_ROTATION;
|
||||
public static Vector3f CAM_VECTOR = new Vector3f(0.0f, 25.0f, -15.0f);
|
||||
|
||||
public void setPlayer(Node player) {
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
public Node getPlayer() {
|
||||
return this.player;
|
||||
}
|
||||
|
||||
public PlayerCam(Node player, String name, Camera camera) {
|
||||
super(name, new CameraControl(camera));
|
||||
this.player = player;
|
||||
this.CAM_ROTATION = new Quaternion();
|
||||
this.CAM_ROTATION.fromAngleAxis(0.7853982f, Vector3f.UNIT_X);
|
||||
}
|
||||
|
||||
public void setupCamera() {
|
||||
this.player.attachChild((Spatial)this);
|
||||
this.setLocalTranslation(CAM_VECTOR);
|
||||
this.lookAt(this.player.getLocalTranslation(), Vector3f.UNIT_Y);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,392 @@
|
||||
import com.jme3.animation.AnimControl;
|
||||
import com.jme3.animation.Animation;
|
||||
import com.jme3.animation.Bone;
|
||||
import com.jme3.animation.BoneTrack;
|
||||
import com.jme3.animation.Skeleton;
|
||||
import com.jme3.animation.Track;
|
||||
import com.jme3.export.binary.BinaryImporter;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Mesh;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.VertexBuffer;
|
||||
import com.jme3.util.IntMap;
|
||||
import com.jme3.util.SafeArrayList;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class J3OConverter {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(J3OConverter.class.getName());
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args.length < 2) {
|
||||
System.err.println("Usage: J3OConverter <input.j3o> <output.json>");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
File inputFile = new File(args[0]);
|
||||
File outputFile = new File(args[1]);
|
||||
|
||||
try {
|
||||
BinaryImporter importer = new BinaryImporter();
|
||||
Object root = importer.load(inputFile);
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
List<Map<String, Object>> geometries = new ArrayList<>();
|
||||
List<Map<String, Object>> skeletons = new ArrayList<>();
|
||||
Map<String, Object> animations = new HashMap<>();
|
||||
|
||||
if (root instanceof Node) {
|
||||
extractFromNode((Node) root, geometries, skeletons, animations, new HashMap<>());
|
||||
}
|
||||
|
||||
result.put("geometries", geometries);
|
||||
result.put("skeletons", skeletons);
|
||||
result.put("animations", animations);
|
||||
|
||||
writeJson(outputFile, result);
|
||||
System.out.println("Wrote " + outputFile);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private static void extractFromNode(Node node,
|
||||
List<Map<String, Object>> geometries,
|
||||
List<Map<String, Object>> skeletons,
|
||||
Map<String, Object> animations,
|
||||
Map<Integer, Integer> boneIdMap) {
|
||||
for (Spatial child : node.getChildren()) {
|
||||
if (child instanceof Node) {
|
||||
extractFromNode((Node) child, geometries, skeletons, animations, boneIdMap);
|
||||
} else if (child instanceof Geometry) {
|
||||
Geometry geo = (Geometry) child;
|
||||
Map<String, Object> meshData = extractMesh(geo.getMesh());
|
||||
if (meshData != null) {
|
||||
meshData.put("name", geo.getName());
|
||||
geometries.add(meshData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Look for AnimControl
|
||||
AnimControl animControl = node.getControl(AnimControl.class);
|
||||
if (animControl != null) {
|
||||
Skeleton skeleton = animControl.getSkeleton();
|
||||
if (skeleton != null) {
|
||||
Map<String, Object> skeletonData = extractSkeleton(skeleton);
|
||||
if (skeletonData != null) {
|
||||
skeletons.add(skeletonData);
|
||||
}
|
||||
}
|
||||
|
||||
for (String animName : animControl.getAnimationNames()) {
|
||||
Animation anim = animControl.getAnim(animName);
|
||||
Map<String, Object> animData = extractAnimation(anim);
|
||||
if (animData != null) {
|
||||
animations.put(animName, animData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<String, Object> extractMesh(Mesh mesh) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("vertexCount", mesh.getVertexCount());
|
||||
result.put("triangleCount", mesh.getTriangleCount());
|
||||
result.put("elementCount", mesh.getTriangleCount() * 3);
|
||||
|
||||
// Positions
|
||||
VertexBuffer posBuf = mesh.getBuffer(VertexBuffer.Type.Position);
|
||||
if (posBuf != null) {
|
||||
FloatBuffer fb = (FloatBuffer) posBuf.getData();
|
||||
float[] positions = new float[fb.limit()];
|
||||
fb.rewind();
|
||||
fb.get(positions);
|
||||
result.put("positions", positions);
|
||||
}
|
||||
|
||||
// Normals
|
||||
VertexBuffer normBuf = mesh.getBuffer(VertexBuffer.Type.Normal);
|
||||
if (normBuf != null) {
|
||||
FloatBuffer fb = (FloatBuffer) normBuf.getData();
|
||||
float[] normals = new float[fb.limit()];
|
||||
fb.rewind();
|
||||
fb.get(normals);
|
||||
result.put("normals", normals);
|
||||
}
|
||||
|
||||
// TexCoords
|
||||
VertexBuffer texBuf = mesh.getBuffer(VertexBuffer.Type.TexCoord);
|
||||
if (texBuf != null) {
|
||||
FloatBuffer fb = (FloatBuffer) texBuf.getData();
|
||||
float[] texcoords = new float[fb.limit()];
|
||||
fb.rewind();
|
||||
fb.get(texcoords);
|
||||
result.put("texcoords", texcoords);
|
||||
}
|
||||
|
||||
// BoneWeights
|
||||
VertexBuffer weightBuf = mesh.getBuffer(VertexBuffer.Type.BoneWeight);
|
||||
if (weightBuf != null) {
|
||||
FloatBuffer fb = (FloatBuffer) weightBuf.getData();
|
||||
float[] weights = new float[fb.limit()];
|
||||
fb.rewind();
|
||||
fb.get(weights);
|
||||
result.put("boneWeights", weights);
|
||||
}
|
||||
|
||||
// BoneIndices
|
||||
VertexBuffer indexBuf = mesh.getBuffer(VertexBuffer.Type.BoneIndex);
|
||||
if (indexBuf != null) {
|
||||
java.nio.Buffer buf = indexBuf.getData();
|
||||
short[] indices;
|
||||
if (buf instanceof ShortBuffer) {
|
||||
ShortBuffer sb = (ShortBuffer) buf;
|
||||
indices = new short[sb.limit()];
|
||||
sb.rewind();
|
||||
sb.get(indices);
|
||||
int[] intIndices = new int[indices.length];
|
||||
for (int i = 0; i < indices.length; i++) {
|
||||
intIndices[i] = indices[i] & 0xFFFF;
|
||||
}
|
||||
result.put("boneIndices", intIndices);
|
||||
} else if (buf instanceof java.nio.ByteBuffer) {
|
||||
java.nio.ByteBuffer bb = (java.nio.ByteBuffer) buf;
|
||||
byte[] bytes = new byte[bb.limit()];
|
||||
bb.rewind();
|
||||
bb.get(bytes);
|
||||
int[] intIndices = new int[bytes.length];
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
intIndices[i] = bytes[i] & 0xFF;
|
||||
}
|
||||
result.put("boneIndices", intIndices);
|
||||
}
|
||||
}
|
||||
|
||||
// Indices - JME3 doesn't always have an index buffer (uses non-indexed rendering)
|
||||
// Try to get the indices from the mesh's LOD levels or mode
|
||||
VertexBuffer indexBuffer = mesh.getBuffer(VertexBuffer.Type.Index);
|
||||
if (indexBuffer != null) {
|
||||
java.nio.Buffer buf = indexBuffer.getData();
|
||||
if (buf instanceof ShortBuffer) {
|
||||
ShortBuffer sb = (ShortBuffer) buf;
|
||||
short[] indices = new short[sb.limit()];
|
||||
sb.rewind();
|
||||
sb.get(indices);
|
||||
int[] intIndices = new int[indices.length];
|
||||
for (int i = 0; i < indices.length; i++) {
|
||||
intIndices[i] = indices[i] & 0xFFFF;
|
||||
}
|
||||
result.put("indices", intIndices);
|
||||
} else if (buf instanceof java.nio.IntBuffer) {
|
||||
java.nio.IntBuffer ib = (java.nio.IntBuffer) buf;
|
||||
int[] indices = new int[ib.limit()];
|
||||
ib.rewind();
|
||||
ib.get(indices);
|
||||
result.put("indices", indices);
|
||||
}
|
||||
}
|
||||
|
||||
if (!result.containsKey("indices")) {
|
||||
// Non-indexed rendering: generate sequential indices
|
||||
int count = mesh.getVertexCount();
|
||||
int[] indices = new int[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
indices[i] = i;
|
||||
}
|
||||
result.put("indices", indices);
|
||||
result.put("nonIndexed", true);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Map<String, Object> extractSkeleton(Skeleton skeleton) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
Bone[] bones = skeleton.getBones();
|
||||
List<Map<String, Object>> boneList = new ArrayList<>();
|
||||
Map<String, Integer> nameToIndex = new HashMap<>();
|
||||
|
||||
for (int i = 0; i < bones.length; i++) {
|
||||
Bone bone = bones[i];
|
||||
nameToIndex.put(bone.getName(), i);
|
||||
|
||||
Map<String, Object> bdata = new HashMap<>();
|
||||
bdata.put("name", bone.getName());
|
||||
bdata.put("id", i);
|
||||
|
||||
Vector3f pos = bone.getBindPosition();
|
||||
bdata.put("position", new float[]{pos.x, pos.y, pos.z});
|
||||
|
||||
Quaternion rot = bone.getBindRotation();
|
||||
bdata.put("rotation", new float[]{rot.getX(), rot.getY(), rot.getZ(), rot.getW()});
|
||||
|
||||
Vector3f scale = bone.getBindScale();
|
||||
bdata.put("scale", new float[]{scale.x, scale.y, scale.z});
|
||||
|
||||
// Get children bone names
|
||||
List<String> children = new ArrayList<>();
|
||||
for (Bone child : bone.getChildren()) {
|
||||
children.add(child.getName());
|
||||
}
|
||||
bdata.put("children", children);
|
||||
|
||||
boneList.add(bdata);
|
||||
}
|
||||
|
||||
result.put("bones", boneList);
|
||||
|
||||
// Roots
|
||||
List<String> roots = new ArrayList<>();
|
||||
Bone[] rootBones = skeleton.getRoots();
|
||||
for (Bone root : rootBones) {
|
||||
roots.add(root.getName());
|
||||
}
|
||||
result.put("roots", roots);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Map<String, Object> extractAnimation(Animation anim) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("name", anim.getName());
|
||||
result.put("length", anim.getLength());
|
||||
|
||||
Track[] tracks = anim.getTracks();
|
||||
List<Map<String, Object>> trackList = new ArrayList<>();
|
||||
|
||||
for (Track track : tracks) {
|
||||
if (track instanceof BoneTrack) {
|
||||
BoneTrack boneTrack = (BoneTrack) track;
|
||||
Map<String, Object> trackData = new HashMap<>();
|
||||
trackData.put("boneIndex", boneTrack.getTargetBoneIndex());
|
||||
trackData.put("times", boneTrack.getTimes());
|
||||
|
||||
// Translations
|
||||
Vector3f[] translations = boneTrack.getTranslations();
|
||||
float[] transData = new float[translations.length * 3];
|
||||
for (int i = 0; i < translations.length; i++) {
|
||||
transData[i * 3] = translations[i].x;
|
||||
transData[i * 3 + 1] = translations[i].y;
|
||||
transData[i * 3 + 2] = translations[i].z;
|
||||
}
|
||||
trackData.put("translations", transData);
|
||||
|
||||
// Rotations
|
||||
Quaternion[] rotations = boneTrack.getRotations();
|
||||
float[] rotData = new float[rotations.length * 4];
|
||||
for (int i = 0; i < rotations.length; i++) {
|
||||
rotData[i * 4] = rotations[i].getX();
|
||||
rotData[i * 4 + 1] = rotations[i].getY();
|
||||
rotData[i * 4 + 2] = rotations[i].getZ();
|
||||
rotData[i * 4 + 3] = rotations[i].getW();
|
||||
}
|
||||
trackData.put("rotations", rotData);
|
||||
|
||||
// Scales
|
||||
Vector3f[] scales = boneTrack.getScales();
|
||||
float[] scaleData = new float[scales.length * 3];
|
||||
for (int i = 0; i < scales.length; i++) {
|
||||
scaleData[i * 3] = scales[i].x;
|
||||
scaleData[i * 3 + 1] = scales[i].y;
|
||||
scaleData[i * 3 + 2] = scales[i].z;
|
||||
}
|
||||
trackData.put("scales", scaleData);
|
||||
|
||||
trackList.add(trackData);
|
||||
}
|
||||
}
|
||||
|
||||
result.put("tracks", trackList);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void writeJson(File file, Object data) throws IOException {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
writeValue(sb, data);
|
||||
FileWriter writer = new FileWriter(file);
|
||||
writer.write(sb.toString());
|
||||
writer.close();
|
||||
}
|
||||
|
||||
private static void writeValue(StringBuilder sb, Object value) {
|
||||
if (value == null) {
|
||||
sb.append("null");
|
||||
} else if (value instanceof String) {
|
||||
sb.append('"').append(escapeString((String) value)).append('"');
|
||||
} else if (value instanceof Integer || value instanceof Long ||
|
||||
value instanceof Float || value instanceof Double ||
|
||||
value instanceof Boolean) {
|
||||
sb.append(value);
|
||||
} else if (value instanceof float[]) {
|
||||
sb.append('[');
|
||||
float[] arr = (float[]) value;
|
||||
for (int i = 0; i < arr.length; i++) {
|
||||
if (i > 0) sb.append(',');
|
||||
sb.append(arr[i]);
|
||||
}
|
||||
sb.append(']');
|
||||
} else if (value instanceof int[]) {
|
||||
sb.append('[');
|
||||
int[] arr = (int[]) value;
|
||||
for (int i = 0; i < arr.length; i++) {
|
||||
if (i > 0) sb.append(',');
|
||||
sb.append(arr[i]);
|
||||
}
|
||||
sb.append(']');
|
||||
} else if (value instanceof List) {
|
||||
sb.append('[');
|
||||
List<?> list = (List<?>) value;
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
if (i > 0) sb.append(',');
|
||||
writeValue(sb, list.get(i));
|
||||
}
|
||||
sb.append(']');
|
||||
} else if (value instanceof Map) {
|
||||
sb.append('{');
|
||||
Map<?, ?> map = (Map<?, ?>) value;
|
||||
boolean first = true;
|
||||
for (Map.Entry<?, ?> entry : map.entrySet()) {
|
||||
if (!first) sb.append(',');
|
||||
sb.append('"').append(escapeString(entry.getKey().toString())).append('"').append(':');
|
||||
writeValue(sb, entry.getValue());
|
||||
first = false;
|
||||
}
|
||||
sb.append('}');
|
||||
}
|
||||
}
|
||||
|
||||
private static String escapeString(String s) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (char c : s.toCharArray()) {
|
||||
switch (c) {
|
||||
case '"': sb.append("\\\""); break;
|
||||
case '\\': sb.append("\\\\"); break;
|
||||
case '\n': sb.append("\\n"); break;
|
||||
case '\r': sb.append("\\r"); break;
|
||||
case '\t': sb.append("\\t"); break;
|
||||
default:
|
||||
if (c < 32) {
|
||||
sb.append(String.format("\\u%04x", (int) c));
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,462 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Convert JME3-extracted JSON (from J3OConverter.java) to glTF 2.0 GLB with animations."""
|
||||
|
||||
import json
|
||||
import struct
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
|
||||
def quat_mult(qa, qb):
|
||||
ax, ay, az, aw = qa
|
||||
bx, by, bz, bw = qb
|
||||
return (
|
||||
aw*bx + ax*bw + ay*bz - az*by,
|
||||
aw*by - ax*bz + ay*bw + az*bx,
|
||||
aw*bz + ax*by - ay*bx + az*bw,
|
||||
aw*bw - ax*bx - ay*by - az*bz,
|
||||
)
|
||||
|
||||
def pad4(data):
|
||||
while len(data) % 4:
|
||||
data += b'\x00'
|
||||
return data
|
||||
|
||||
def mat4_mult(a, b):
|
||||
r = [0]*16
|
||||
for i in range(4):
|
||||
for j in range(4):
|
||||
for k in range(4):
|
||||
r[j*4 + i] += a[k*4 + i] * b[j*4 + k]
|
||||
return r
|
||||
|
||||
def mat4_from_trs(t, r, s):
|
||||
x, y, z, w = r
|
||||
xx, yy, zz = x*x, y*y, z*z
|
||||
xy, xz, yz = x*y, x*z, y*z
|
||||
wx, wy, wz = w*x, w*y, w*z
|
||||
return [
|
||||
(1-2*(yy+zz))*s[0], (2*(xy+wz))*s[0], (2*(xz-wy))*s[0], 0,
|
||||
(2*(xy-wz))*s[1], (1-2*(xx+zz))*s[1], (2*(yz+wx))*s[1], 0,
|
||||
(2*(xz+wy))*s[2], (2*(yz-wx))*s[2], (1-2*(xx+yy))*s[2], 0,
|
||||
t[0], t[1], t[2], 1,
|
||||
]
|
||||
|
||||
def invert_4x4_row(m):
|
||||
a,b,c,d, e,f,g,h, i,j,k,l, m_,n,o,p = m
|
||||
det = (a*(f*k*p+g*l*n+h*j*o-h*k*n-f*l*o-g*j*p)
|
||||
- b*(e*k*p+g*l*m_+h*i*o-h*k*m_-e*l*o-g*i*p)
|
||||
+ c*(e*j*p+f*l*m_+h*i*n-h*j*m_-e*l*n-f*i*p)
|
||||
- d*(e*j*o+f*k*m_+g*i*n-g*j*m_-e*k*n-f*i*o))
|
||||
if abs(det) < 1e-12:
|
||||
return [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]
|
||||
idet = 1.0/det
|
||||
return [
|
||||
(f*k*p+g*l*n+h*j*o-h*k*n-f*l*o-g*j*p)*idet,
|
||||
-(b*k*p+c*l*n+d*j*o-d*k*n-b*l*o-c*j*p)*idet,
|
||||
(b*g*p+c*h*n+d*f*o-d*g*n-b*h*o-c*f*p)*idet,
|
||||
-(b*g*l+c*h*j+d*f*k-d*g*j-b*h*k-c*f*l)*idet,
|
||||
-(e*k*p+g*l*m_+h*i*o-h*k*m_-e*l*o-g*i*p)*idet,
|
||||
(a*k*p+c*l*m_+d*i*o-d*k*m_-a*l*o-c*i*p)*idet,
|
||||
-(a*g*p+c*h*m_+d*e*o-d*g*m_-a*h*o-c*e*p)*idet,
|
||||
(a*g*l+c*h*i+d*e*k-d*g*i-a*h*k-c*e*l)*idet,
|
||||
(e*j*p+f*l*m_+h*i*n-h*j*m_-e*l*n-f*i*p)*idet,
|
||||
-(a*j*p+b*l*m_+d*i*n-d*j*m_-a*l*n-b*i*p)*idet,
|
||||
(a*f*p+b*h*m_+d*e*n-d*f*m_-a*h*n-b*e*p)*idet,
|
||||
-(a*f*l+b*h*i+d*e*j-d*f*i-a*h*j-b*e*l)*idet,
|
||||
-(e*j*o+f*k*m_+g*i*n-g*j*m_-e*k*n-f*i*o)*idet,
|
||||
(a*j*o+b*k*m_+c*i*n-c*j*m_-a*k*n-b*i*o)*idet,
|
||||
-(a*f*o+b*g*m_+c*e*n-c*f*m_-a*g*n-b*e*o)*idet,
|
||||
(a*f*k+b*g*i+c*e*j-c*f*i-a*g*j-b*e*k)*idet,
|
||||
]
|
||||
|
||||
def convert(json_path, output_path):
|
||||
with open(json_path) as f:
|
||||
data = json.load(f)
|
||||
|
||||
geos = data.get('geometries', [])
|
||||
skels = data.get('skeletons', [])
|
||||
anims = data.get('animations', {})
|
||||
|
||||
if not geos:
|
||||
print(f' No geometry in {json_path}')
|
||||
return
|
||||
|
||||
geo = geos[0]
|
||||
positions = geo['positions']
|
||||
normals = geo.get('normals', [])
|
||||
indices = geo.get('indices', [])
|
||||
texcoords = geo.get('texcoords', [])
|
||||
vertex_count = geo.get('vertexCount', len(positions) // 3)
|
||||
|
||||
bone_weights = geo.get('boneWeights', [])
|
||||
bone_indices = geo.get('boneIndices', [])
|
||||
|
||||
skel = skels[0] if skels else None
|
||||
|
||||
nodes = []
|
||||
skin = None
|
||||
animations_out = []
|
||||
|
||||
# ── Skeleton → glTF nodes ──────────────────────────────────────────────
|
||||
if skel:
|
||||
bones = skel['bones']
|
||||
roots = skel['roots']
|
||||
name_to_bone = {b['name']: b for b in bones}
|
||||
name_to_idx = {}
|
||||
|
||||
# IMPORTANT: Keep the ORIGINAL bone list order (from JME3 skeleton.getBone(i)).
|
||||
# BoneTrack.getTargetBoneIndex() and the mesh JOINTS_0 data reference
|
||||
# this exact order.
|
||||
order = [b['name'] for b in bones]
|
||||
for i, name in enumerate(order):
|
||||
name_to_idx[name] = i
|
||||
b = name_to_bone[name]
|
||||
nodes.append({
|
||||
'name': name,
|
||||
'translation': b['position'],
|
||||
'rotation': b['rotation'], # x,y,z,w
|
||||
'scale': b['scale'],
|
||||
})
|
||||
|
||||
# Hierarchy (by name)
|
||||
for name in order:
|
||||
b = name_to_bone[name]
|
||||
for child_name in b['children']:
|
||||
if child_name in name_to_idx and name in name_to_idx:
|
||||
pi = name_to_idx[name]
|
||||
ci = name_to_idx[child_name]
|
||||
nodes[pi].setdefault('children', []).append(ci)
|
||||
|
||||
# Inverse bind matrices
|
||||
def world_transform(name, cache={}):
|
||||
if name in cache:
|
||||
return cache[name]
|
||||
b = name_to_bone[name]
|
||||
local = mat4_from_trs(b['position'], b['rotation'], b['scale'])
|
||||
# Find parent
|
||||
parent = None
|
||||
for pn, pb in name_to_bone.items():
|
||||
if name in pb['children']:
|
||||
parent = pn
|
||||
break
|
||||
if parent and parent in name_to_bone:
|
||||
w = mat4_mult(world_transform(parent, cache), local)
|
||||
else:
|
||||
w = local
|
||||
cache[name] = w
|
||||
return w
|
||||
|
||||
ibms = []
|
||||
for name in order:
|
||||
w = world_transform(name)
|
||||
# Convert col-major to row-major, invert, back to col-major
|
||||
row = [w[i + j*4] for j in range(4) for i in range(4)]
|
||||
inv_row = invert_4x4_row(row)
|
||||
inv_col = [inv_row[j + i*4] for i in range(4) for j in range(4)]
|
||||
ibms.extend(inv_col)
|
||||
|
||||
# Joint indices (identity: bone list order == glTF node order)
|
||||
joints = list(range(len(order)))
|
||||
skin = {'joints': joints}
|
||||
|
||||
# Mesh node with skin
|
||||
mesh_node_idx = len(nodes)
|
||||
nodes.append({'name': 'mesh', 'mesh': 0, 'skin': 0})
|
||||
|
||||
# Skeleton root node
|
||||
root_node_idx = len(nodes)
|
||||
root_children = [name_to_idx[r] for r in roots if r in name_to_idx]
|
||||
root_children.append(mesh_node_idx)
|
||||
nodes.append({'name': 'skeleton_root', 'children': root_children})
|
||||
|
||||
# Scene root
|
||||
scene_node_idx = len(nodes)
|
||||
nodes.append({'name': 'scene_root', 'children': [root_node_idx]})
|
||||
|
||||
# ── Animations ──────────────────────────────────────────────────────
|
||||
anim_bin = bytearray()
|
||||
anim_buf_views = []
|
||||
anim_accessors = []
|
||||
|
||||
for anim_name, anim in anims.items():
|
||||
tracks = anim.get('tracks', [])
|
||||
if not tracks:
|
||||
continue
|
||||
|
||||
# Collect all keyframe times
|
||||
all_times = set()
|
||||
for t in tracks:
|
||||
for tm in t.get('times', []):
|
||||
all_times.add(round(tm, 6))
|
||||
sorted_times = sorted(all_times)
|
||||
if len(sorted_times) < 2:
|
||||
continue
|
||||
|
||||
# Per-animation sampler/channel lists
|
||||
anim_samplers_this = []
|
||||
anim_channels_this = []
|
||||
|
||||
# Time buffer
|
||||
time_bytes = pad4(struct.pack(f'<{len(sorted_times)}f', *sorted_times))
|
||||
time_off = len(anim_bin)
|
||||
anim_bin.extend(time_bytes)
|
||||
tv_idx = len(anim_buf_views)
|
||||
anim_buf_views.append({'byteOffset': time_off, 'byteLength': len(time_bytes)})
|
||||
t_acc = len(anim_accessors)
|
||||
anim_accessors.append({'type': 'SCALAR', 'count': len(sorted_times),
|
||||
'min': [sorted_times[0]], 'max': [sorted_times[-1]]})
|
||||
|
||||
for track in tracks:
|
||||
bone_idx = track.get('boneIndex', 0)
|
||||
if bone_idx >= len(order):
|
||||
continue
|
||||
node_idx = name_to_idx.get(order[bone_idx], None)
|
||||
if node_idx is None:
|
||||
node_idx = bone_idx
|
||||
|
||||
# Get bind pose for this bone (JME3 tracks are relative to bind)
|
||||
bone_name = order[bone_idx]
|
||||
bind = name_to_bone[bone_name]
|
||||
bind_pos = bind['position']
|
||||
bind_rot = bind['rotation']
|
||||
bind_scale = bind['scale']
|
||||
|
||||
kf_times = list(track.get('times', []))
|
||||
kf_trans = list(track.get('translations', []))
|
||||
kf_rot = list(track.get('rotations', []))
|
||||
kf_scale = list(track.get('scales', []))
|
||||
|
||||
# Combine with bind pose: final = bind * relative
|
||||
# rotation: bindRot * trackRot
|
||||
combined_rot = []
|
||||
for i in range(0, len(kf_rot), 4):
|
||||
q = quat_mult(bind_rot, kf_rot[i:i+4])
|
||||
combined_rot.extend(q)
|
||||
kf_rot = combined_rot
|
||||
|
||||
# translation: bindPos + trackTrans
|
||||
combined_trans = []
|
||||
for i in range(0, len(kf_trans), 3):
|
||||
combined_trans.extend([
|
||||
bind_pos[0] + kf_trans[i],
|
||||
bind_pos[1] + kf_trans[i+1],
|
||||
bind_pos[2] + kf_trans[i+2],
|
||||
])
|
||||
kf_trans = combined_trans
|
||||
|
||||
# scale: bindScale * trackScale
|
||||
combined_scale = []
|
||||
for i in range(0, len(kf_scale), 3):
|
||||
combined_scale.extend([
|
||||
bind_scale[0] * kf_scale[i],
|
||||
bind_scale[1] * kf_scale[i+1],
|
||||
bind_scale[2] * kf_scale[i+2],
|
||||
])
|
||||
kf_scale = combined_scale
|
||||
|
||||
def lerp_to(times, values, val_size, targets):
|
||||
if not values:
|
||||
return []
|
||||
result = []
|
||||
n_kf = len(times)
|
||||
for qt in targets:
|
||||
if qt <= times[0]:
|
||||
result.extend(values[0:val_size])
|
||||
elif qt >= times[-1]:
|
||||
result.extend(values[(n_kf-1)*val_size:(n_kf-1)*val_size+val_size])
|
||||
else:
|
||||
for i in range(n_kf - 1):
|
||||
if times[i] <= qt <= times[i+1]:
|
||||
a = (qt - times[i]) / (times[i+1] - times[i]) if times[i+1] != times[i] else 0
|
||||
base_i = i * val_size
|
||||
base_j = (i+1) * val_size
|
||||
result.extend([
|
||||
values[base_i + j] + a * (values[base_j + j] - values[base_i + j])
|
||||
for j in range(val_size)
|
||||
])
|
||||
break
|
||||
else:
|
||||
result.extend(values[(n_kf-1)*val_size:(n_kf-1)*val_size+val_size])
|
||||
return result
|
||||
|
||||
trans_interp = lerp_to(kf_times, kf_trans, 3, sorted_times) if kf_trans else []
|
||||
rot_interp = lerp_to(kf_times, kf_rot, 4, sorted_times) if kf_rot else []
|
||||
scale_interp = lerp_to(kf_times, kf_scale, 3, sorted_times) if kf_scale else []
|
||||
|
||||
for vals, count, acc_type, path in [
|
||||
(trans_interp, 3, 'VEC3', 'translation'),
|
||||
(rot_interp, 4, 'VEC4', 'rotation'),
|
||||
(scale_interp, 3, 'VEC3', 'scale'),
|
||||
]:
|
||||
if not vals:
|
||||
continue
|
||||
raw = pad4(struct.pack(f'<{len(vals)}f', *vals))
|
||||
off2 = len(anim_bin)
|
||||
anim_bin.extend(raw)
|
||||
dv_idx = len(anim_buf_views)
|
||||
anim_buf_views.append({'byteOffset': off2, 'byteLength': len(raw)})
|
||||
acc_idx = len(anim_accessors)
|
||||
anim_accessors.append({'type': acc_type, 'count': len(sorted_times)})
|
||||
samp_idx = len(anim_samplers_this)
|
||||
anim_samplers_this.append({'input': t_acc, 'output': acc_idx})
|
||||
anim_channels_this.append({'sampler': samp_idx,
|
||||
'target': {'node': node_idx, 'path': path}})
|
||||
|
||||
if anim_samplers_this:
|
||||
animations_out.append({
|
||||
'name': anim_name,
|
||||
'samplers': anim_samplers_this,
|
||||
'channels': anim_channels_this,
|
||||
})
|
||||
else:
|
||||
# No skeleton - simple mesh node
|
||||
mesh_node_idx = len(nodes)
|
||||
nodes.append({'name': 'mesh', 'mesh': 0})
|
||||
scene_node_idx = len(nodes)
|
||||
nodes.append({'name': 'scene_root', 'children': [mesh_node_idx]})
|
||||
anim_bin = bytearray()
|
||||
anim_buf_views = []
|
||||
anim_accessors = []
|
||||
anim_samplers = []
|
||||
anim_channels = []
|
||||
|
||||
# ── Build binary buffer ─────────────────────────────────────────────────
|
||||
pos_bytes = pad4(struct.pack(f'<{len(positions)}f', *positions))
|
||||
norm_bytes = pad4(struct.pack(f'<{len(normals)}f', *normals))
|
||||
idx_bytes = pad4(struct.pack(f'<{len(indices)}H', *indices))
|
||||
uv_bytes = pad4(struct.pack(f'<{len(texcoords)}f', *texcoords)) if texcoords else b''
|
||||
|
||||
joint_bytes = b''
|
||||
weight_bytes = b''
|
||||
if skel and bone_weights:
|
||||
# Joints: use existing bone_indices if available
|
||||
joint_vals = [i & 0xFFFF for i in bone_indices] if bone_indices else [0] * (vertex_count * 4)
|
||||
joint_bytes = pad4(struct.pack(f'<{len(joint_vals)}H', *joint_vals))
|
||||
weight_bytes = pad4(struct.pack(f'<{len(bone_weights)}f', *bone_weights))
|
||||
|
||||
ibm_bytes = pad4(struct.pack(f'<{len(ibms)}f', *ibms)) if skel else b''
|
||||
|
||||
all_bin = pos_bytes + norm_bytes + idx_bytes + uv_bytes + joint_bytes + weight_bytes + ibm_bytes + bytes(anim_bin)
|
||||
|
||||
# ── Buffer views and accessors ──────────────────────────────────────────
|
||||
off = 0
|
||||
buffer_views = []
|
||||
bv_pos = {'buffer': 0, 'byteOffset': off, 'byteLength': len(pos_bytes), 'target': 34962}; off += len(pos_bytes)
|
||||
bv_nrm = {'buffer': 0, 'byteOffset': off, 'byteLength': len(norm_bytes), 'target': 34962}; off += len(norm_bytes)
|
||||
bv_idx = {'buffer': 0, 'byteOffset': off, 'byteLength': len(idx_bytes), 'target': 34963}; off += len(idx_bytes)
|
||||
buffer_views = [bv_pos, bv_nrm, bv_idx]
|
||||
|
||||
accessors = [
|
||||
{'bufferView': 0, 'componentType': 5126, 'count': vertex_count, 'type': 'VEC3'},
|
||||
{'bufferView': 1, 'componentType': 5126, 'count': vertex_count, 'type': 'VEC3'},
|
||||
{'bufferView': 2, 'componentType': 5123, 'count': len(indices), 'type': 'SCALAR'},
|
||||
]
|
||||
|
||||
attributes = {'POSITION': 0, 'NORMAL': 1}
|
||||
|
||||
if texcoords:
|
||||
bv_uv = {'buffer': 0, 'byteOffset': off, 'byteLength': len(uv_bytes), 'target': 34962}; off += len(uv_bytes)
|
||||
buffer_views.append(bv_uv)
|
||||
accessors.append({'bufferView': 3, 'componentType': 5126, 'count': vertex_count, 'type': 'VEC2'})
|
||||
attributes['TEXCOORD_0'] = 3
|
||||
|
||||
# Joint/weight accessor indices shift if UV present
|
||||
jnt_acc = 4 if texcoords else 3
|
||||
wgt_acc = 5 if texcoords else 4
|
||||
|
||||
if skel and bone_weights:
|
||||
bv_jnt = {'buffer': 0, 'byteOffset': off, 'byteLength': len(joint_bytes), 'target': 34962}; off += len(joint_bytes)
|
||||
bv_wgt = {'buffer': 0, 'byteOffset': off, 'byteLength': len(weight_bytes), 'target': 34962}; off += len(weight_bytes)
|
||||
buffer_views.extend([bv_jnt, bv_wgt])
|
||||
accessors.append({'bufferView': 3 if not texcoords else 4, 'componentType': 5123, 'count': vertex_count, 'type': 'VEC4'})
|
||||
accessors.append({'bufferView': 4 if not texcoords else 5, 'componentType': 5126, 'count': vertex_count, 'type': 'VEC4'})
|
||||
attributes['JOINTS_0'] = jnt_acc
|
||||
attributes['WEIGHTS_0'] = wgt_acc
|
||||
|
||||
ibm_accessor_idx = None
|
||||
if skel:
|
||||
bv_ibm = {'buffer': 0, 'byteOffset': off, 'byteLength': len(ibm_bytes)}; off += len(ibm_bytes)
|
||||
buffer_views.append(bv_ibm)
|
||||
ibm_accessor_idx = len(accessors)
|
||||
accessors.append({'bufferView': len(buffer_views)-1, 'componentType': 5126,
|
||||
'count': len(skin['joints']), 'type': 'MAT4'})
|
||||
skin['inverseBindMatrices'] = ibm_accessor_idx
|
||||
|
||||
anim_base = off
|
||||
for bv in anim_buf_views:
|
||||
buffer_views.append({'buffer': 0, 'byteOffset': anim_base + bv['byteOffset'], 'byteLength': bv['byteLength']})
|
||||
|
||||
acc_offset = len(accessors)
|
||||
for acc in anim_accessors:
|
||||
a = {'componentType': 5126, 'type': acc['type'], 'count': acc['count'],
|
||||
'bufferView': len(buffer_views) - len(anim_accessors) + 0}
|
||||
accessors.append(a)
|
||||
|
||||
# Fix animation accessor bufferView indices
|
||||
for i, acc in enumerate(anim_accessors):
|
||||
accessors[acc_offset + i]['bufferView'] = len(buffer_views) - len(anim_accessors) + i
|
||||
|
||||
# Fix sampler input/output indices (they reference anim_accessors relative)
|
||||
for anim in animations_out:
|
||||
for s in anim['samplers']:
|
||||
s['input'] += acc_offset
|
||||
s['output'] += acc_offset
|
||||
|
||||
# ── Assemble glTF JSON ──────────────────────────────────────────────────
|
||||
meshes = [{'name': geo.get('name', 'mesh'), 'primitives': [{
|
||||
'attributes': attributes,
|
||||
'indices': 2,
|
||||
'material': 0,
|
||||
}]}]
|
||||
|
||||
gltf = {
|
||||
'asset': {'version': '2.0', 'generator': 'jme3-json2gltf.py'},
|
||||
'scene': 0,
|
||||
'scenes': [{'nodes': [scene_node_idx]}],
|
||||
'nodes': nodes,
|
||||
'meshes': meshes,
|
||||
'materials': [{'name': 'default', 'pbrMetallicRoughness': {
|
||||
'baseColorFactor': [1, 1, 1, 1], 'metallicFactor': 0, 'roughnessFactor': 0.5}}],
|
||||
'animations': animations_out,
|
||||
'accessors': accessors,
|
||||
'bufferViews': buffer_views,
|
||||
'buffers': [{'byteLength': len(all_bin)}],
|
||||
}
|
||||
|
||||
if skin:
|
||||
gltf['skins'] = [skin]
|
||||
|
||||
gltf_json = json.dumps(gltf, separators=(',', ':')).encode('utf-8')
|
||||
while len(gltf_json) % 4:
|
||||
gltf_json += b' '
|
||||
while len(all_bin) % 4:
|
||||
all_bin += b'\x00'
|
||||
|
||||
header = struct.pack('<III', 0x46546C67, 2, 12 + 8 + len(gltf_json) + 8 + len(all_bin))
|
||||
json_chunk = struct.pack('<II', len(gltf_json), 0x4E4F534A)
|
||||
bin_chunk = struct.pack('<II', len(all_bin), 0x004E4942)
|
||||
|
||||
with open(output_path, 'wb') as f:
|
||||
f.write(header + json_chunk + gltf_json + bin_chunk + all_bin)
|
||||
|
||||
print(f' -> {output_path} (verts={vertex_count}, bones={len(skin["joints"]) if skin else 0}, '
|
||||
f'anims={len(animations_out)})')
|
||||
|
||||
def main():
|
||||
in_dir = '/var/home/nico/Gamedev/Wackelpeter/web/public/models'
|
||||
models = ['spider', 'staff', 'cross', 'helmet', 'wall1', 'tile1', 'tile2', 'blobknochen']
|
||||
for m in models:
|
||||
jp = os.path.join(in_dir, f'{m}.json')
|
||||
gp = os.path.join(in_dir, f'{m}.glb')
|
||||
if os.path.exists(jp):
|
||||
print(f'Converting {m}...')
|
||||
try:
|
||||
convert(jp, gp)
|
||||
except Exception as e:
|
||||
print(f' ERROR: {e}')
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,822 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Parse JMonkeyEngine .j3o binary files and extract mesh/skeleton/animation data.
|
||||
|
||||
Outputs glTF 2.0 .glb for meshes with skeletons, or OBJ-like JSON for static meshes.
|
||||
"""
|
||||
|
||||
import struct
|
||||
import math
|
||||
import os
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
# ── Constants ───────────────────────────────────────────────────────────────
|
||||
|
||||
NULL_OBJECT = -1
|
||||
DEFAULT_OBJECT = -2
|
||||
SIGNATURE = 0x4A4D4533
|
||||
|
||||
FIELD_TYPE = {
|
||||
0: 'BYTE', 1: 'BYTE_1D', 2: 'BYTE_2D',
|
||||
10: 'INT', 11: 'INT_1D', 12: 'INT_2D',
|
||||
20: 'FLOAT', 21: 'FLOAT_1D', 22: 'FLOAT_2D',
|
||||
30: 'DOUBLE', 31: 'DOUBLE_1D', 32: 'DOUBLE_2D',
|
||||
40: 'LONG', 41: 'LONG_1D', 42: 'LONG_2D',
|
||||
50: 'SHORT', 51: 'SHORT_1D', 52: 'SHORT_2D',
|
||||
60: 'BOOLEAN', 61: 'BOOLEAN_1D', 62: 'BOOLEAN_2D',
|
||||
70: 'STRING', 71: 'STRING_1D', 72: 'STRING_2D',
|
||||
80: 'BITSET',
|
||||
90: 'SAVABLE', 91: 'SAVABLE_1D', 92: 'SAVABLE_2D',
|
||||
100: 'SAVABLE_ARRAYLIST', 101: 'SAVABLE_ARRAYLIST_1D', 102: 'SAVABLE_ARRAYLIST_2D',
|
||||
105: 'SAVABLE_MAP', 106: 'STRING_SAVABLE_MAP', 107: 'INT_SAVABLE_MAP',
|
||||
110: 'FLOATBUFFER_ARRAYLIST', 111: 'BYTEBUFFER_ARRAYLIST',
|
||||
120: 'FLOATBUFFER', 121: 'INTBUFFER', 122: 'BYTEBUFFER', 123: 'SHORTBUFFER',
|
||||
}
|
||||
|
||||
# VertexBuffer usage types (key for IntMap in Mesh.buffers)
|
||||
VB_USAGE = {
|
||||
'Position': 0, 'Normal': 1, 'TexCoord': 2, 'Color': 3,
|
||||
'Tangent': 4, 'Binormal': 5,
|
||||
'Size': 6, 'InterleavedData': 7, 'Misc': 8,
|
||||
'BoneIndex': 9, 'BoneWeight': 10, 'HWBoneIndex': 11, 'HWBoneWeight': 12,
|
||||
'BindPosePosition': 13, 'BindPoseNormal': 14, 'BindPoseTangent': 15,
|
||||
}
|
||||
|
||||
VB_FORMAT = {'Float': 0, 'Short': 2, 'UnsignedShort': 4, 'UnsignedByte': 5, 'Byte': 6, 'Half': 7, 'Int': 8, 'UnsignedInt': 9}
|
||||
|
||||
|
||||
# ── Binary reader ───────────────────────────────────────────────────────────
|
||||
|
||||
class J3OReader:
|
||||
def __init__(self, data):
|
||||
self.data = data
|
||||
self.offset = 0
|
||||
|
||||
def tell(self):
|
||||
return self.offset
|
||||
|
||||
def read_bytes(self, n):
|
||||
r = self.data[self.offset:self.offset + n]
|
||||
self.offset += n
|
||||
return r
|
||||
|
||||
def read_be_int32(self):
|
||||
return struct.unpack('>i', self.read_bytes(4))[0]
|
||||
|
||||
def read_be_uint32(self):
|
||||
return struct.unpack('>I', self.read_bytes(4))[0]
|
||||
|
||||
def read_be_int16(self):
|
||||
return struct.unpack('>h', self.read_bytes(2))[0]
|
||||
|
||||
def read_byte(self):
|
||||
b = self.data[self.offset]
|
||||
self.offset += 1
|
||||
return b
|
||||
|
||||
def read_float(self):
|
||||
i = struct.unpack('>i', self.read_bytes(4))[0]
|
||||
return struct.unpack('>f', struct.pack('>i', i))[0]
|
||||
|
||||
def read_double(self):
|
||||
i = struct.unpack('>q', self.read_bytes(8))[0]
|
||||
return struct.unpack('>d', struct.pack('>q', i))[0]
|
||||
|
||||
def read_short(self):
|
||||
return struct.unpack('>h', self.read_bytes(2))[0]
|
||||
|
||||
def read_bool(self):
|
||||
return self.read_byte() != 0
|
||||
|
||||
def read_compressed_int(self):
|
||||
first = self.read_byte()
|
||||
if first == 0xFF:
|
||||
return NULL_OBJECT
|
||||
if first == 0xFE:
|
||||
return DEFAULT_OBJECT
|
||||
if first == 0x00:
|
||||
return 0
|
||||
length = first & 0xFF
|
||||
raw = self.read_bytes(length)
|
||||
# Right-align to 4 bytes
|
||||
if length <= 4:
|
||||
pad_len = 4 - length
|
||||
value_bytes = b'\x00' * pad_len + raw
|
||||
else:
|
||||
# If longer than 4 bytes, take only the last 4
|
||||
value_bytes = raw[length-4:]
|
||||
try:
|
||||
val = struct.unpack('>i', value_bytes)[0]
|
||||
except struct.error:
|
||||
return NULL_OBJECT
|
||||
if val in (NULL_OBJECT, DEFAULT_OBJECT):
|
||||
if length == 4:
|
||||
self.offset -= 4
|
||||
return val
|
||||
|
||||
def read_compressed_long(self):
|
||||
first = self.read_byte()
|
||||
if first == 0xFF:
|
||||
return NULL_OBJECT
|
||||
if first == 0xFE:
|
||||
return DEFAULT_OBJECT
|
||||
if first == 0x00:
|
||||
return 0
|
||||
length = first & 0xFF
|
||||
raw = self.read_bytes(length)
|
||||
pad = b'\x00' * (8 - length)
|
||||
return struct.unpack('>q', pad + raw)[0]
|
||||
|
||||
def read_string(self):
|
||||
length = self.read_compressed_int()
|
||||
if length == NULL_OBJECT:
|
||||
return None
|
||||
return self.read_bytes(length).decode('utf-8', errors='replace')
|
||||
|
||||
|
||||
# ── Field reading ───────────────────────────────────────────────────────────
|
||||
|
||||
class CapsuleReader:
|
||||
"""Reads field data for a single capsule."""
|
||||
def __init__(self, rdr: J3OReader, length):
|
||||
self.rdr = rdr
|
||||
self.end_offset = rdr.tell() + length
|
||||
self.class_fields = None # set by BinaryClassObject
|
||||
|
||||
def read_field_value(self, field_type, expected_alias=None):
|
||||
"""Read a value of the given type. Returns (value, field_alias)."""
|
||||
if expected_alias is not None:
|
||||
alias = self.rdr.read_byte()
|
||||
else:
|
||||
alias = 0
|
||||
|
||||
val = None
|
||||
|
||||
if field_type in (0,): # BYTE
|
||||
val = self.rdr.read_byte()
|
||||
|
||||
elif field_type in (1,): # BYTE_1D
|
||||
ln = self.rdr.read_compressed_int()
|
||||
val = list(self.rdr.read_bytes(ln)) if ln > 0 else []
|
||||
|
||||
elif field_type in (2,): # BYTE_2D
|
||||
outer = self.rdr.read_compressed_int()
|
||||
val = []
|
||||
for _ in range(outer):
|
||||
ln = self.rdr.read_compressed_int()
|
||||
val.append(list(self.rdr.read_bytes(ln)) if ln > 0 else [])
|
||||
|
||||
elif field_type in (10,): # INT
|
||||
val = self.rdr.read_compressed_int()
|
||||
|
||||
elif field_type in (11,): # INT_1D
|
||||
ln = self.rdr.read_compressed_int()
|
||||
val = [self.rdr.read_compressed_int() for _ in range(ln)]
|
||||
|
||||
elif field_type in (12,): # INT_2D
|
||||
outer = self.rdr.read_compressed_int()
|
||||
val = [[self.rdr.read_compressed_int() for _ in range(self.rdr.read_compressed_int())] for _ in range(outer)]
|
||||
|
||||
elif field_type in (20,): # FLOAT
|
||||
val = self.rdr.read_float()
|
||||
|
||||
elif field_type in (21,): # FLOAT_1D
|
||||
ln = self.rdr.read_compressed_int()
|
||||
val = [self.rdr.read_float() for _ in range(ln)]
|
||||
|
||||
elif field_type in (22,): # FLOAT_2D
|
||||
outer = self.rdr.read_compressed_int()
|
||||
val = [[self.rdr.read_float() for _ in range(self.rdr.read_compressed_int())] for _ in range(outer)]
|
||||
|
||||
elif field_type in (30,): # DOUBLE
|
||||
val = self.rdr.read_double()
|
||||
|
||||
elif field_type in (40,): # LONG
|
||||
val = self.rdr.read_compressed_long()
|
||||
|
||||
elif field_type in (50,): # SHORT
|
||||
val = self.rdr.read_short()
|
||||
|
||||
elif field_type in (51,): # SHORT_1D
|
||||
ln = self.rdr.read_compressed_int()
|
||||
val = [self.rdr.read_short() for _ in range(ln)]
|
||||
|
||||
elif field_type in (60,): # BOOLEAN
|
||||
val = self.rdr.read_bool()
|
||||
|
||||
elif field_type in (61,): # BOOLEAN_1D
|
||||
ln = self.rdr.read_compressed_int()
|
||||
val = [self.rdr.read_bool() for _ in range(ln)]
|
||||
|
||||
elif field_type in (70,): # STRING
|
||||
val = self.rdr.read_string()
|
||||
|
||||
elif field_type in (71,): # STRING_1D
|
||||
ln = self.rdr.read_compressed_int()
|
||||
val = [self.rdr.read_string() for _ in range(ln)]
|
||||
|
||||
elif field_type in (80,): # BITSET
|
||||
ln = self.rdr.read_compressed_int()
|
||||
val = [self.rdr.read_bool() for _ in range(ln)]
|
||||
|
||||
elif field_type in (90,): # SAVABLE
|
||||
val = self.rdr.read_compressed_int()
|
||||
|
||||
elif field_type in (91, 100): # SAVABLE_1D / SAVABLE_ARRAYLIST
|
||||
ln = self.rdr.read_compressed_int()
|
||||
val = [self.rdr.read_compressed_int() for _ in range(ln)]
|
||||
|
||||
elif field_type in (92, 101): # SAVABLE_2D
|
||||
outer = self.rdr.read_compressed_int()
|
||||
val = [[self.rdr.read_compressed_int() for _ in range(self.rdr.read_compressed_int())] for _ in range(outer)]
|
||||
|
||||
elif field_type in (105,): # SAVABLE_MAP
|
||||
ln = self.rdr.read_compressed_int()
|
||||
val = [[self.rdr.read_compressed_int(), self.rdr.read_compressed_int()] for _ in range(ln)]
|
||||
|
||||
elif field_type in (106,): # STRING_SAVABLE_MAP
|
||||
ln = self.rdr.read_compressed_int()
|
||||
keys = [self.rdr.read_string() for _ in range(ln)]
|
||||
_ = self.rdr.read_compressed_int() # skip length
|
||||
vals = [self.rdr.read_compressed_int() for _ in range(ln)]
|
||||
val = dict(zip(keys, vals))
|
||||
|
||||
elif field_type in (107,): # INT_SAVABLE_MAP
|
||||
ln = self.rdr.read_compressed_int()
|
||||
keys = [self.rdr.read_compressed_int() for _ in range(ln)]
|
||||
ln2 = self.rdr.read_compressed_int()
|
||||
vals = [self.rdr.read_compressed_int() for _ in range(ln2)]
|
||||
val = dict(zip(keys, vals))
|
||||
|
||||
elif field_type in (120, 121, 122, 123): # Buffer types
|
||||
ln = self.rdr.read_compressed_int()
|
||||
if ln > 0:
|
||||
elem_size = {120: 4, 121: 4, 122: 1, 123: 2}[field_type]
|
||||
raw = self.rdr.read_bytes(ln * elem_size)
|
||||
val = raw
|
||||
else:
|
||||
val = b''
|
||||
|
||||
elif field_type in (110,): # FLOATBUFFER_ARRAYLIST
|
||||
ln = self.rdr.read_compressed_int()
|
||||
val = []
|
||||
for _ in range(ln):
|
||||
nb = self.rdr.read_compressed_int()
|
||||
if nb > 0:
|
||||
val.append(self.rdr.read_bytes(nb * 4))
|
||||
else:
|
||||
val.append(b'')
|
||||
|
||||
else:
|
||||
# Unknown type - skip ahead
|
||||
val = None
|
||||
|
||||
return val, alias
|
||||
|
||||
def read_all_fields(self, bco):
|
||||
"""Read all fields of a capsule using its class definition."""
|
||||
fields_read = {}
|
||||
while self.rdr.tell() < self.end_offset - 1:
|
||||
try:
|
||||
alias = self.rdr.read_byte()
|
||||
except (IndexError, struct.error):
|
||||
break
|
||||
field = bco.alias_fields.get(alias)
|
||||
if field is None:
|
||||
# Unknown field alias - skip the rest
|
||||
break
|
||||
try:
|
||||
val, _ = self.read_field_value(field['type'], expected_alias=None)
|
||||
fields_read[field['name']] = val
|
||||
except (IndexError, struct.error):
|
||||
break
|
||||
return fields_read
|
||||
|
||||
|
||||
# ── Class table ─────────────────────────────────────────────────────────────
|
||||
|
||||
class BinaryClassObject:
|
||||
def __init__(self, alias, name, fields):
|
||||
self.alias = alias
|
||||
self.name = name
|
||||
self.fields = fields
|
||||
self.alias_fields = {f['alias']: f for f in fields}
|
||||
|
||||
|
||||
def parse_j3o_header(filepath):
|
||||
"""Parse J3O header, class table, and location table. Return loader function."""
|
||||
with open(filepath, 'rb') as f:
|
||||
data = f.read()
|
||||
|
||||
rdr = J3OReader(data)
|
||||
|
||||
# Header
|
||||
sig = rdr.read_be_uint32()
|
||||
if sig != SIGNATURE:
|
||||
# Old format (version 0): no signature, first int is num_classes
|
||||
rdr.offset = 0
|
||||
version = 0
|
||||
num_classes = rdr.read_be_int32()
|
||||
else:
|
||||
version = rdr.read_be_int32()
|
||||
num_classes = rdr.read_be_int32()
|
||||
|
||||
alias_width = int(math.log(max(num_classes, 2), 256)) + 1
|
||||
|
||||
# Class table
|
||||
classes = {}
|
||||
for i in range(num_classes):
|
||||
alias = rdr.read_bytes(alias_width)
|
||||
|
||||
if version >= 1:
|
||||
hier_size = rdr.read_byte()
|
||||
_ = [rdr.read_be_int32() for _ in range(hier_size)]
|
||||
else:
|
||||
_ = [0]
|
||||
|
||||
name_len = rdr.read_be_int32()
|
||||
name = rdr.read_bytes(name_len).decode('ascii', errors='replace')
|
||||
|
||||
num_fields = rdr.read_be_int32()
|
||||
fields = []
|
||||
for _ in range(num_fields):
|
||||
fa = rdr.read_byte()
|
||||
ft = rdr.read_byte()
|
||||
fn_len = rdr.read_be_int32()
|
||||
fn = rdr.read_bytes(fn_len).decode('ascii', errors='replace')
|
||||
fields.append({'alias': fa, 'type': ft, 'name': fn})
|
||||
|
||||
classes[alias] = BinaryClassObject(alias, name, fields)
|
||||
|
||||
# Location table
|
||||
num_locs = rdr.read_be_int32()
|
||||
location_table = {}
|
||||
for _ in range(num_locs):
|
||||
obj_id = rdr.read_be_int32()
|
||||
loc = rdr.read_be_int32()
|
||||
location_table[obj_id] = loc
|
||||
|
||||
# Root
|
||||
_ = rdr.read_be_int32()
|
||||
root_id = rdr.read_be_int32()
|
||||
|
||||
payload_start = rdr.tell()
|
||||
payload = data[payload_start:]
|
||||
|
||||
return alias_width, classes, location_table, root_id, payload
|
||||
|
||||
|
||||
def read_object(obj_id, alias_width, classes, location_table, payload, cache=None):
|
||||
"""Read a single capsule and return its class name and field data."""
|
||||
if cache is None:
|
||||
cache = {}
|
||||
|
||||
if obj_id in cache:
|
||||
return cache[obj_id]
|
||||
|
||||
if obj_id not in location_table:
|
||||
cache[obj_id] = ('unknown', {})
|
||||
return cache[obj_id]
|
||||
|
||||
loc = location_table[obj_id]
|
||||
rdr = J3OReader(payload)
|
||||
rdr.offset = loc
|
||||
|
||||
alias = rdr.read_bytes(alias_width)
|
||||
bco = classes.get(alias)
|
||||
|
||||
data_len = rdr.read_be_int32()
|
||||
|
||||
if bco is None:
|
||||
cache[obj_id] = ('unknown', {})
|
||||
return cache[obj_id]
|
||||
|
||||
cap_rdr = CapsuleReader(rdr, data_len)
|
||||
fields = cap_rdr.read_all_fields(bco)
|
||||
|
||||
result = (bco.name, fields)
|
||||
cache[obj_id] = result
|
||||
return result
|
||||
|
||||
|
||||
def resolve_savable(obj_id, alias_width, classes, location_table, payload, cache):
|
||||
"""Resolve a Savable reference, returning (class_name, fields)."""
|
||||
if obj_id <= 0 or obj_id == NULL_OBJECT or obj_id == DEFAULT_OBJECT:
|
||||
return None
|
||||
return read_object(obj_id, alias_width, classes, location_table, payload, cache)
|
||||
|
||||
|
||||
# ── Vertex buffer decoding ──────────────────────────────────────────────────
|
||||
|
||||
def decode_floatbuffer(raw, count):
|
||||
"""Decode LE float buffer. raw is bytes."""
|
||||
if len(raw) != count * 4:
|
||||
return [0.0] * count
|
||||
return list(struct.unpack(f'<{count}f', raw))
|
||||
|
||||
def decode_shortbuffer(raw, count):
|
||||
return list(struct.unpack(f'<{count}h', raw))
|
||||
|
||||
def decode_bytebuffer(raw, count):
|
||||
return list(raw[:count])
|
||||
|
||||
def decode_intbuffer(raw, count):
|
||||
return list(struct.unpack(f'<{count}i', raw))
|
||||
|
||||
|
||||
# ── Mesh extraction ─────────────────────────────────────────────────────────
|
||||
|
||||
def extract_mesh_geometry(obj_id, alias_width, classes, location_table, payload, cache):
|
||||
obj = resolve_savable(obj_id, alias_width, classes, location_table, payload, cache)
|
||||
if obj is None:
|
||||
return None
|
||||
|
||||
cls_name, fields = obj
|
||||
if 'Mesh' not in cls_name:
|
||||
return None
|
||||
|
||||
vert_count = fields.get('vertCount', 0)
|
||||
if vert_count == 0:
|
||||
return None
|
||||
|
||||
buffers_map = fields.get('buffers', {})
|
||||
element_count = fields.get('elementCount', 0)
|
||||
|
||||
positions = []
|
||||
normals = []
|
||||
texcoords = []
|
||||
indices = []
|
||||
bone_weights_raw = b''
|
||||
bone_indices_raw = b''
|
||||
bone_idx_bytes_per_elem = 2
|
||||
weight_comps = 4
|
||||
idx_comps = 4
|
||||
|
||||
for usage_code, vb_id in buffers_map.items():
|
||||
vb_obj = resolve_savable(vb_id, alias_width, classes, location_table, payload, cache)
|
||||
if vb_obj is None:
|
||||
continue
|
||||
|
||||
_, vb_fields = vb_obj
|
||||
comp = vb_fields.get('components', 0)
|
||||
format_code = vb_fields.get('format', -1)
|
||||
|
||||
raw_data = None
|
||||
is_float = False
|
||||
is_short = False
|
||||
is_byte = False
|
||||
for dk, fl, sh, bt in [
|
||||
('dataFloat', True, False, False),
|
||||
('dataUnsignedShort', False, True, False),
|
||||
('dataUnsignedByte', False, False, True),
|
||||
('dataShort', False, True, False),
|
||||
('dataByte', False, False, True),
|
||||
('dataInt', False, False, False),
|
||||
('data', False, False, False),
|
||||
]:
|
||||
if dk in vb_fields:
|
||||
raw_data = vb_fields[dk]
|
||||
is_float = fl
|
||||
is_short = sh
|
||||
is_byte = bt
|
||||
break
|
||||
|
||||
if raw_data is None:
|
||||
continue
|
||||
|
||||
if usage_code == VB_USAGE['Position']:
|
||||
elem_count = len(raw_data) // 4
|
||||
positions = decode_floatbuffer(raw_data, elem_count)
|
||||
actual_vert_count = elem_count // comp
|
||||
elif usage_code == VB_USAGE['Normal']:
|
||||
elem_count = len(raw_data) // 4
|
||||
normals = decode_floatbuffer(raw_data, elem_count)
|
||||
elif usage_code == VB_USAGE['TexCoord']:
|
||||
elem_count = len(raw_data) // 4
|
||||
texcoords = decode_floatbuffer(raw_data, elem_count)
|
||||
elif usage_code == VB_USAGE['BoneWeight']: # 10
|
||||
bone_weights_raw = raw_data
|
||||
weight_comps = comp
|
||||
elif usage_code == VB_USAGE['BoneIndex']: # 9
|
||||
bone_indices_raw = raw_data
|
||||
bone_idx_bytes_per_elem = 2 if is_short else 1
|
||||
idx_comps = comp
|
||||
elif usage_code == VB_USAGE['Misc']: # 8
|
||||
if is_short:
|
||||
indices = list(struct.unpack(f'<{len(raw_data)//2}h', raw_data))
|
||||
elif len(raw_data) >= 2:
|
||||
indices = list(struct.unpack(f'<{len(raw_data)//2}H', raw_data))
|
||||
elif raw_data:
|
||||
indices = list(raw_data)
|
||||
|
||||
if not positions:
|
||||
return None
|
||||
|
||||
# Decode bone data now that we know actual_vert_count
|
||||
actual_vert_count = len(positions) // 3 if positions else vert_count
|
||||
|
||||
bone_indices = []
|
||||
if bone_indices_raw:
|
||||
if bone_idx_bytes_per_elem == 2:
|
||||
count = len(bone_indices_raw) // 2
|
||||
total = min(count, actual_vert_count * idx_comps)
|
||||
bone_indices = list(struct.unpack(f'<{total}H', bone_indices_raw[:total*2]))
|
||||
else:
|
||||
total = min(len(bone_indices_raw), actual_vert_count * idx_comps)
|
||||
bone_indices = list(bone_indices_raw[:total])
|
||||
|
||||
bone_weights = []
|
||||
if bone_weights_raw:
|
||||
total = min(len(bone_weights_raw) // 4, actual_vert_count * weight_comps)
|
||||
bone_weights = decode_floatbuffer(bone_weights_raw, total)
|
||||
|
||||
if not indices and element_count > 0:
|
||||
indices = list(range(element_count))
|
||||
|
||||
return {
|
||||
'vertexCount': actual_vert_count,
|
||||
'positions': positions,
|
||||
'normals': normals,
|
||||
'texcoords': texcoords,
|
||||
'indices': indices,
|
||||
'boneWeights': bone_weights,
|
||||
'boneIndices': bone_indices,
|
||||
}
|
||||
|
||||
|
||||
# ── Skeleton extraction ─────────────────────────────────────────────────────
|
||||
|
||||
def extract_skeleton(skeleton_id, alias_width, classes, location_table, payload, cache):
|
||||
"""Extract skeleton data."""
|
||||
obj = resolve_savable(skeleton_id, alias_width, classes, location_table, payload, cache)
|
||||
if obj is None:
|
||||
return None
|
||||
|
||||
cls_name, fields = obj
|
||||
if 'Skeleton' not in cls_name:
|
||||
return None
|
||||
|
||||
root_bones_ids = fields.get('rootBones', [])
|
||||
bone_list_ids = fields.get('boneList', [])
|
||||
|
||||
bones = []
|
||||
bid_to_idx = {}
|
||||
|
||||
for i, bid in enumerate(bone_list_ids):
|
||||
bone_obj = resolve_savable(bid, alias_width, classes, location_table, payload, cache)
|
||||
if bone_obj is None:
|
||||
continue
|
||||
_, bf = bone_obj
|
||||
bones.append({
|
||||
'id': i,
|
||||
'name': bf.get('name', f'bone_{i}'),
|
||||
'position': bf.get('bindPos', [0,0,0]) if 'bindPos' in bf else [0,0,0],
|
||||
'rotation': bf.get('bindRot', [0,0,0,1]) if 'bindRot' in bf else [0,0,0,1],
|
||||
'scale': bf.get('bindScale', [1,1,1]) if 'bindScale' in bf else [1,1,1],
|
||||
})
|
||||
bid_to_idx[bid] = i
|
||||
|
||||
# Hierarchy
|
||||
hierarchy = {}
|
||||
for bid in bone_list_ids:
|
||||
bone_obj = resolve_savable(bid, alias_width, classes, location_table, payload, cache)
|
||||
if bone_obj is None:
|
||||
continue
|
||||
_, bf = bone_obj
|
||||
children_ids = bf.get('children', [])
|
||||
parent_idx = bid_to_idx.get(bid)
|
||||
for cid in children_ids:
|
||||
child_idx = bid_to_idx.get(cid)
|
||||
if child_idx is not None and parent_idx is not None:
|
||||
hierarchy[bones[child_idx]['name']] = bones[parent_idx]['name']
|
||||
|
||||
roots = [bid_to_idx[rid] for rid in root_bones_ids if rid in bid_to_idx]
|
||||
|
||||
return {
|
||||
'bones': bones,
|
||||
'hierarchy': hierarchy,
|
||||
'roots': roots,
|
||||
}
|
||||
|
||||
|
||||
# ── Animation extraction ────────────────────────────────────────────────────
|
||||
|
||||
def extract_animations(anim_map, alias_width, classes, location_table, payload, cache):
|
||||
"""Extract animation data from StringSavableMap of anim_name -> Animation."""
|
||||
if anim_map is None:
|
||||
return {}
|
||||
|
||||
animations = {}
|
||||
for anim_name, anim_id in anim_map.items():
|
||||
anim_obj = resolve_savable(anim_id, alias_width, classes, location_table, payload, cache)
|
||||
if anim_obj is None:
|
||||
continue
|
||||
_, af = anim_obj
|
||||
tracks_ids = af.get('tracks', [])
|
||||
anim_len = af.get('length', 1.0)
|
||||
anim_name_str = af.get('name', anim_name) or anim_name
|
||||
|
||||
tracks = []
|
||||
for tid in tracks_ids:
|
||||
track_obj = resolve_savable(tid, alias_width, classes, location_table, payload, cache)
|
||||
if track_obj is None:
|
||||
continue
|
||||
ct, tf = track_obj
|
||||
bone_idx = tf.get('boneIndex', 0)
|
||||
|
||||
# times: float array
|
||||
times = tf.get('times', [])
|
||||
|
||||
# translations: savable CompactVector3Array
|
||||
trans_id = tf.get('translations', None)
|
||||
translations = []
|
||||
if trans_id is not None:
|
||||
trans_obj = resolve_savable(trans_id, alias_width, classes, location_table, payload, cache)
|
||||
if trans_obj:
|
||||
_, trf = trans_obj
|
||||
trans_data = trf.get('array', None)
|
||||
if trans_data:
|
||||
translations = list(struct.unpack(f'<{len(trans_data)//4}f', trans_data)) if trans_data else []
|
||||
|
||||
# rotations: savable CompactQuaternionArray
|
||||
rot_id = tf.get('rotations', None)
|
||||
rotations = []
|
||||
if rot_id is not None:
|
||||
rot_obj = resolve_savable(rot_id, alias_width, classes, location_table, payload, cache)
|
||||
if rot_obj:
|
||||
_, rf = rot_obj
|
||||
rot_data = rf.get('array', None)
|
||||
if rot_data:
|
||||
rotations = list(struct.unpack(f'<{len(rot_data)//4}f', rot_data)) if rot_data else []
|
||||
|
||||
# scales: savable CompactVector3Array
|
||||
scale_id = tf.get('scales', None)
|
||||
scales = []
|
||||
if scale_id is not None:
|
||||
scale_obj = resolve_savable(scale_id, alias_width, classes, location_table, payload, cache)
|
||||
if scale_obj:
|
||||
_, sf = scale_obj
|
||||
scale_data = sf.get('array', None)
|
||||
if scale_data:
|
||||
scales = list(struct.unpack(f'<{len(scale_data)//4}f', scale_data)) if scale_data else []
|
||||
|
||||
tracks.append({
|
||||
'boneIndex': bone_idx,
|
||||
'times': times,
|
||||
'translations': translations,
|
||||
'rotations': rotations,
|
||||
'scales': scales,
|
||||
})
|
||||
|
||||
animations[anim_name_str] = {
|
||||
'length': anim_len,
|
||||
'tracks': tracks,
|
||||
}
|
||||
|
||||
return animations
|
||||
|
||||
|
||||
# ── Main extraction ─────────────────────────────────────────────────────────
|
||||
|
||||
def extract_j3o(filepath):
|
||||
"""Extract mesh, skeleton, and animation data from a .j3o file."""
|
||||
alias_width, classes, location_table, root_id, payload = parse_j3o_header(filepath)
|
||||
cache = {}
|
||||
result = {
|
||||
'file': filepath,
|
||||
'geometries': [],
|
||||
'skeletons': [],
|
||||
'animations': {},
|
||||
}
|
||||
|
||||
# Walk object graph looking for mesh/skeleton/animation data
|
||||
def walk_obj(obj_id, depth=0):
|
||||
if depth > 50 or obj_id <= 0:
|
||||
return
|
||||
try:
|
||||
obj = resolve_savable(obj_id, alias_width, classes, location_table, payload, cache)
|
||||
except Exception:
|
||||
return
|
||||
if obj is None:
|
||||
return
|
||||
cls_name, fields = obj
|
||||
|
||||
# Check for Geometry / Mesh data
|
||||
if 'Geometry' in cls_name:
|
||||
mesh_id = fields.get('mesh', None)
|
||||
if mesh_id and mesh_id > 0:
|
||||
try:
|
||||
geo = extract_mesh_geometry(mesh_id, alias_width, classes, location_table, payload, cache)
|
||||
if geo:
|
||||
result['geometries'].append({
|
||||
'name': fields.get('name', 'unknown'),
|
||||
'geometry': geo,
|
||||
})
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Check for AnimControl
|
||||
if 'AnimControl' in cls_name:
|
||||
skeleton_id = fields.get('skeleton', None)
|
||||
if skeleton_id and skeleton_id > 0:
|
||||
skel = extract_skeleton(skeleton_id, alias_width, classes, location_table, payload, cache)
|
||||
if skel:
|
||||
result['skeletons'].append(skel)
|
||||
|
||||
anim_map = fields.get('animations', None)
|
||||
if isinstance(anim_map, dict):
|
||||
anims = extract_animations(anim_map, alias_width, classes, location_table, payload, cache)
|
||||
result['animations'].update(anims)
|
||||
|
||||
# Check for SkeletonControl
|
||||
if 'SkeletonControl' in cls_name:
|
||||
skeleton_id = fields.get('skeleton', None)
|
||||
if skeleton_id and skeleton_id > 0 and not result['skeletons']:
|
||||
skel = extract_skeleton(skeleton_id, alias_width, classes, location_table, payload, cache)
|
||||
if skel:
|
||||
result['skeletons'].append(skel)
|
||||
|
||||
# Recurse into children and controls
|
||||
for list_key in ['children', 'controlsList']:
|
||||
items = fields.get(list_key, None)
|
||||
if isinstance(items, list):
|
||||
for item in items:
|
||||
if isinstance(item, int) and item > 0:
|
||||
walk_obj(item, depth + 1)
|
||||
|
||||
walk_obj(root_id)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
# ── Convert to glTF / JSON ──────────────────────────────────────────────────
|
||||
|
||||
def j3o_to_gltf(j3o_data, output_path):
|
||||
"""Convert extracted J3O data to glTF or OBJ-like JSON."""
|
||||
geos = j3o_data['geometries']
|
||||
skels = j3o_data['skeletons']
|
||||
anims = j3o_data['animations']
|
||||
|
||||
if not geos:
|
||||
print(f" No geometry found in {j3o_data['file']}")
|
||||
return
|
||||
|
||||
# For now, output as simple OBJ-like JSON (positions + indices)
|
||||
for geo_entry in geos:
|
||||
g = geo_entry['geometry']
|
||||
name = geo_entry['name'] or os.path.basename(j3o_data['file']).replace('.j3o', '')
|
||||
|
||||
out = {
|
||||
'name': name,
|
||||
'vertexCount': g['vertexCount'],
|
||||
'positions': g['positions'],
|
||||
'normals': g['normals'],
|
||||
'indices': g['indices'],
|
||||
'hasBones': len(g['boneWeights']) > 0,
|
||||
'boneWeights': g['boneWeights'],
|
||||
'boneIndices': g['boneIndices'],
|
||||
}
|
||||
|
||||
# Add skeleton if exists
|
||||
if skels:
|
||||
out['skeleton'] = skels[0]
|
||||
if anims:
|
||||
out['animations'] = anims
|
||||
|
||||
out_path = os.path.join(output_path, f'{name}.json')
|
||||
with open(out_path, 'w') as f:
|
||||
json.dump(out, f, separators=(',', ':'))
|
||||
|
||||
print(f" -> {out_path} ({g['vertexCount']} verts, {len(g['indices'])//3} faces)")
|
||||
|
||||
|
||||
# ── Main ────────────────────────────────────────────────────────────────────
|
||||
|
||||
def main():
|
||||
base = '/var/home/nico/Gamedev/Wackelpeter/extracted/Models'
|
||||
out_dir = '/var/home/nico/Gamedev/Wackelpeter/web/public/models'
|
||||
|
||||
j3o_files = [
|
||||
('spider', 'spider/spider.mesh.j3o'),
|
||||
('staff', 'staff/staff.mesh.j3o'),
|
||||
('cross', 'kross/kross.mesh.j3o'),
|
||||
]
|
||||
|
||||
for name, path in j3o_files:
|
||||
full = os.path.join(base, path)
|
||||
if not os.path.exists(full):
|
||||
print(f'Skipping {name}: not found')
|
||||
continue
|
||||
print(f'Extracting {name}...')
|
||||
try:
|
||||
data = extract_j3o(full)
|
||||
j3o_to_gltf(data, out_dir)
|
||||
print(f" Skeletons: {len(data['skeletons'])}, Animations: {len(data['animations'])}")
|
||||
except Exception as e:
|
||||
print(f' ERROR: {e}')
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,485 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Convert Ogre3D .mesh.xml + .skeleton.xml to glTF 2.0 (.glb) with all animations."""
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
import json
|
||||
import struct
|
||||
import math
|
||||
import os
|
||||
|
||||
# ── Math ────────────────────────────────────────────────────────────────────
|
||||
|
||||
def axis_angle_to_quat(ax, ay, az, angle):
|
||||
s = math.sin(angle / 2)
|
||||
return (ax * s, ay * s, az * s, math.cos(angle / 2))
|
||||
|
||||
def mat4_from_trs(tx, ty, tz, qx, qy, qz, qw, sx, sy, sz):
|
||||
x, y, z, w = qx, qy, qz, qw
|
||||
xx, yy, zz = x*x, y*y, z*z
|
||||
xy, xz, yz = x*y, x*z, y*z
|
||||
wx, wy, wz = w*x, w*y, w*z
|
||||
return [
|
||||
(1 - 2*(yy + zz))*sx, (2*(xy + wz))*sx, (2*(xz - wy))*sx, 0,
|
||||
(2*(xy - wz))*sy, (1 - 2*(xx + zz))*sy, (2*(yz + wx))*sy, 0,
|
||||
(2*(xz + wy))*sz, (2*(yz - wx))*sz, (1 - 2*(xx + yy))*sz, 0,
|
||||
tx, ty, tz, 1,
|
||||
]
|
||||
|
||||
def mat4_mult(a, b):
|
||||
r = [0]*16
|
||||
for i in range(4):
|
||||
for j in range(4):
|
||||
for k in range(4):
|
||||
r[j*4 + i] += a[k*4 + i] * b[j*4 + k]
|
||||
return r
|
||||
|
||||
def lerp_keyframes(kf_times, kf_vals, query_times):
|
||||
"""Linearly interpolate keyframe values to query times."""
|
||||
if len(kf_times) == 1:
|
||||
v = kf_vals[0]
|
||||
result = []
|
||||
for _ in query_times:
|
||||
result.extend(v)
|
||||
return result
|
||||
result = []
|
||||
for qt in query_times:
|
||||
if qt <= kf_times[0]:
|
||||
result.extend(kf_vals[0])
|
||||
elif qt >= kf_times[-1]:
|
||||
result.extend(kf_vals[-1])
|
||||
else:
|
||||
for i in range(len(kf_times) - 1):
|
||||
if kf_times[i] <= qt <= kf_times[i+1]:
|
||||
a = (qt - kf_times[i]) / (kf_times[i+1] - kf_times[i])
|
||||
v = [kf_vals[i][j] + a*(kf_vals[i+1][j] - kf_vals[i][j])
|
||||
for j in range(len(kf_vals[0]))]
|
||||
result.extend(v)
|
||||
break
|
||||
else:
|
||||
result.extend(kf_vals[-1])
|
||||
return result
|
||||
|
||||
def pad(data, alignment=4):
|
||||
"""Pad binary data with null bytes to alignment (for BIN chunk)."""
|
||||
while len(data) % alignment:
|
||||
data += b'\x00'
|
||||
return data
|
||||
|
||||
# ── Parsing ─────────────────────────────────────────────────────────────────
|
||||
|
||||
def parse_skeleton(filepath):
|
||||
tree = ET.parse(filepath)
|
||||
root = tree.getroot()
|
||||
bones = []
|
||||
for b in root.find('bones').findall('bone'):
|
||||
bid = int(b.get('id', '0'))
|
||||
name = b.get('name', '')
|
||||
pos = b.find('position')
|
||||
px, py, pz = float(pos.get('x','0')), float(pos.get('y','0')), float(pos.get('z','0'))
|
||||
rot = b.find('rotation')
|
||||
angle = float(rot.get('angle','0'))
|
||||
axis = rot.find('axis')
|
||||
ax, ay, az = float(axis.get('x','1')), float(axis.get('y','0')), float(axis.get('z','0'))
|
||||
bones.append({'id': bid, 'name': name, 'pos': (px, py, pz),
|
||||
'rot_axis': (ax, ay, az), 'rot_angle': angle, 'scale': (1,1,1)})
|
||||
hierarchy = {}
|
||||
hier = root.find('bonehierarchy')
|
||||
if hier is not None:
|
||||
for bp in hier.findall('boneparent'):
|
||||
hierarchy[bp.get('bone')] = bp.get('parent')
|
||||
animations = {}
|
||||
anims = root.find('animations')
|
||||
if anims is not None:
|
||||
for anim in anims.findall('animation'):
|
||||
name = anim.get('name')
|
||||
tracks = {}
|
||||
for track in anim.find('tracks').findall('track'):
|
||||
bn = track.get('bone')
|
||||
kfs = []
|
||||
for kf in track.find('keyframes').findall('keyframe'):
|
||||
t = float(kf.get('time'))
|
||||
tr = kf.find('translate')
|
||||
tx, ty, tz = float(tr.get('x','0')), float(tr.get('y','0')), float(tr.get('z','0'))
|
||||
ro = kf.find('rotate')
|
||||
ang = float(ro.get('angle','0'))
|
||||
ax_e = ro.find('axis')
|
||||
ax, ay, az = float(ax_e.get('x','1')), float(ax_e.get('y','0')), float(ax_e.get('z','0'))
|
||||
q = axis_angle_to_quat(ax, ay, az, ang)
|
||||
sc = kf.find('scale')
|
||||
sx = float(sc.get('x','1')) if sc is not None else 1.0
|
||||
sy = float(sc.get('y','1')) if sc is not None else 1.0
|
||||
sz = float(sc.get('z','1')) if sc is not None else 1.0
|
||||
kfs.append((t, tx, ty, tz, q[0], q[1], q[2], q[3], sx, sy, sz))
|
||||
tracks[bn] = kfs
|
||||
animations[name] = tracks
|
||||
return bones, hierarchy, animations
|
||||
|
||||
def parse_mesh(filepath):
|
||||
tree = ET.parse(filepath)
|
||||
root = tree.getroot()
|
||||
verts = []
|
||||
sg = root.find('sharedgeometry')
|
||||
if sg is not None:
|
||||
for vb in sg.findall('vertexbuffer'):
|
||||
for v in vb.findall('vertex'):
|
||||
pos = v.find('position')
|
||||
norm = v.find('normal')
|
||||
vt = {'x': float(pos.get('x','0')), 'y': float(pos.get('y','0')), 'z': float(pos.get('z','0')),
|
||||
'nx': 0, 'ny': 0, 'nz': 1}
|
||||
if norm is not None:
|
||||
vt['nx'] = float(norm.get('x','0')); vt['ny'] = float(norm.get('y','0')); vt['nz'] = float(norm.get('z','0'))
|
||||
verts.append(vt)
|
||||
faces = []
|
||||
for sm in root.findall('.//submesh'):
|
||||
for fe in sm.findall('faces'):
|
||||
for f in fe.findall('face'):
|
||||
faces.append((int(f.get('v1','0')), int(f.get('v2','0')), int(f.get('v3','0'))))
|
||||
bw = [{} for _ in range(len(verts))]
|
||||
ba = root.find('boneassignments')
|
||||
if ba is not None:
|
||||
for a in ba.findall('vertexboneassignment'):
|
||||
vi = int(a.get('vertexindex','0'))
|
||||
bi = int(a.get('boneindex','0'))
|
||||
w = float(a.get('weight','1.0'))
|
||||
if vi < len(bw):
|
||||
bw[vi][bi] = w
|
||||
return verts, faces, bw
|
||||
|
||||
# ── glTF builder ────────────────────────────────────────────────────────────
|
||||
|
||||
def build_gltf(mesh_path, skeleton_path, output_path):
|
||||
verts, faces, bw = parse_mesh(mesh_path)
|
||||
bones, hierarchy, animations = parse_skeleton(skeleton_path)
|
||||
|
||||
# Topological sort of bones
|
||||
bone_by_name = {b['name']: b for b in bones}
|
||||
bone_by_id = {b['id']: b for b in bones}
|
||||
|
||||
roots = [b for b in bones if b['name'] not in hierarchy]
|
||||
order = []
|
||||
visited = set()
|
||||
def dfs(name):
|
||||
if name in visited: return
|
||||
visited.add(name)
|
||||
order.append(name)
|
||||
for child, parent in hierarchy.items():
|
||||
if parent == name:
|
||||
dfs(child)
|
||||
for root in roots:
|
||||
dfs(root['name'])
|
||||
|
||||
name_to_idx = {name: i for i, name in enumerate(order)}
|
||||
|
||||
# glTF nodes (bones only for now)
|
||||
nodes = []
|
||||
for name in order:
|
||||
b = bone_by_name[name]
|
||||
q = axis_angle_to_quat(*b['rot_axis'], b['rot_angle'])
|
||||
nodes.append({
|
||||
'name': name,
|
||||
'translation': list(b['pos']),
|
||||
'rotation': [q[0], q[1], q[2], q[3]],
|
||||
'scale': list(b['scale']),
|
||||
})
|
||||
for child, parent in hierarchy.items():
|
||||
if child in name_to_idx and parent in name_to_idx:
|
||||
ci, pi = name_to_idx[child], name_to_idx[parent]
|
||||
nodes[pi].setdefault('children', []).append(ci)
|
||||
|
||||
# Add a skeleton root node (identity matrix, all bones as children)
|
||||
skeleton_root_idx = len(nodes)
|
||||
nodes.append({'name': '__skeleton_root__', 'children': []})
|
||||
# Only add bones that are NOT children of other bones in hierarchy
|
||||
for i, name in enumerate(order):
|
||||
is_child = any(i in nodes[idx].get('children', []) for idx in range(len(nodes)))
|
||||
if not is_child:
|
||||
nodes[skeleton_root_idx]['children'].append(i)
|
||||
|
||||
# Inverse bind matrices
|
||||
def world_transform(name):
|
||||
b = bone_by_name[name]
|
||||
q = axis_angle_to_quat(*b['rot_axis'], b['rot_angle'])
|
||||
local = mat4_from_trs(b['pos'][0], b['pos'][1], b['pos'][2],
|
||||
q[0], q[1], q[2], q[3], 1, 1, 1)
|
||||
parent = hierarchy.get(name)
|
||||
if parent and parent in bone_by_name:
|
||||
return mat4_mult(world_transform(parent), local)
|
||||
return local
|
||||
|
||||
ibm_flat = []
|
||||
for name in order:
|
||||
w = world_transform(name)
|
||||
# Invert 4x4 matrix
|
||||
m = [w[i + j*4] for j in range(4) for i in range(4)] # transpose to row-major for inversion
|
||||
inv = invert_4x4(m)
|
||||
ibm_flat.extend(inv) # back in row-major? No - glTF uses column-major
|
||||
|
||||
# Actually glTF stores matrices column-major in the linear buffer
|
||||
# The inverse bind matrix in column-major form: each 4 floats = 1 column
|
||||
ibm_col_major = []
|
||||
for name in order:
|
||||
w = world_transform(name)
|
||||
# w is column-major: [c0x, c0y, c0z, c0w, c1x, ..., c3w]
|
||||
# Invert it
|
||||
inv = invert_4x4_colmajor(w)
|
||||
ibm_col_major.extend(inv)
|
||||
|
||||
# Joint indices for skin
|
||||
skin_joints = [name_to_idx[name] for name in order]
|
||||
|
||||
# Vertex data
|
||||
positions = []
|
||||
nrm = []
|
||||
joints0 = []
|
||||
weights0 = []
|
||||
for i, v in enumerate(verts):
|
||||
positions.extend([v['x'], v['y'], v['z']])
|
||||
nrm.extend([v['nx'], v['ny'], v['nz']])
|
||||
wmap = bw[i] if i < len(bw) else {}
|
||||
sw = sorted(wmap.items(), key=lambda x: x[1], reverse=True)[:4]
|
||||
j = [0, 0, 0, 0]
|
||||
wgt = [0.0, 0.0, 0.0, 0.0]
|
||||
for k, (bid, weight) in enumerate(sw):
|
||||
if bid in bone_by_id:
|
||||
jname = bone_by_id[bid]['name']
|
||||
j[k] = name_to_idx.get(jname, 0)
|
||||
wgt[k] = weight
|
||||
total = sum(wgt)
|
||||
if total > 0:
|
||||
wgt = [x / total for x in wgt]
|
||||
joints0.extend(j)
|
||||
weights0.extend(wgt)
|
||||
|
||||
indices = []
|
||||
for f in faces:
|
||||
indices.extend(list(f))
|
||||
|
||||
# Bounding box
|
||||
all_x = [v['x'] for v in verts]
|
||||
all_y = [v['y'] for v in verts]
|
||||
all_z = [v['z'] for v in verts]
|
||||
bbox_min = [min(all_x), min(all_y), min(all_z)]
|
||||
bbox_max = [max(all_x), max(all_y), max(all_z)]
|
||||
|
||||
# Model node (has mesh and skin)
|
||||
model_node_idx = len(nodes)
|
||||
nodes.append({'name': os.path.basename(mesh_path).replace('.mesh.xml', '') + '_mesh',
|
||||
'mesh': 0, 'skin': 0})
|
||||
|
||||
# Scene root
|
||||
scene_node_idx = len(nodes)
|
||||
nodes.append({'name': 'scene_root', 'children': [skeleton_root_idx, model_node_idx]})
|
||||
|
||||
# ── Build buffer ────────────────────────────────────────────────────────
|
||||
pos_bytes = pad(struct.pack(f'<{len(positions)}f', *positions))
|
||||
nrm_bytes = pad(struct.pack(f'<{len(nrm)}f', *nrm))
|
||||
idx_bytes = pad(struct.pack(f'<{len(indices)}H', *indices))
|
||||
jnt_bytes = pad(struct.pack(f'<{len(joints0)}H', *joints0))
|
||||
wgt_bytes = pad(struct.pack(f'<{len(weights0)}f', *weights0))
|
||||
ibm_bytes = pad(struct.pack(f'<{len(ibm_col_major)}f', *ibm_col_major))
|
||||
|
||||
# Base data end
|
||||
base_end = len(pos_bytes) + len(nrm_bytes) + len(idx_bytes) + len(jnt_bytes) + len(wgt_bytes) + len(ibm_bytes)
|
||||
|
||||
# ── Animations ──────────────────────────────────────────────────────────
|
||||
anim_data = bytearray()
|
||||
gltf_animations = []
|
||||
buf_views = []
|
||||
accessors_list = []
|
||||
|
||||
# Fill static buffer views and accessors first
|
||||
off = 0
|
||||
bv_pos = {'buffer': 0, 'byteOffset': off, 'byteLength': len(pos_bytes), 'target': 34962}; off += len(pos_bytes)
|
||||
bv_nrm = {'buffer': 0, 'byteOffset': off, 'byteLength': len(nrm_bytes), 'target': 34962}; off += len(nrm_bytes)
|
||||
bv_idx = {'buffer': 0, 'byteOffset': off, 'byteLength': len(idx_bytes), 'target': 34963}; off += len(idx_bytes)
|
||||
bv_jnt = {'buffer': 0, 'byteOffset': off, 'byteLength': len(jnt_bytes), 'target': 34962}; off += len(jnt_bytes)
|
||||
bv_wgt = {'buffer': 0, 'byteOffset': off, 'byteLength': len(wgt_bytes), 'target': 34962}; off += len(wgt_bytes)
|
||||
bv_ibm = {'buffer': 0, 'byteOffset': off, 'byteLength': len(ibm_bytes)}; off += len(ibm_bytes)
|
||||
|
||||
buffer_views = [bv_pos, bv_nrm, bv_idx, bv_jnt, bv_wgt, bv_ibm]
|
||||
|
||||
accessors = [
|
||||
{'bufferView': 0, 'componentType': 5126, 'count': len(verts), 'type': 'VEC3',
|
||||
'min': bbox_min, 'max': bbox_max},
|
||||
{'bufferView': 1, 'componentType': 5126, 'count': len(verts), 'type': 'VEC3'},
|
||||
{'bufferView': 2, 'componentType': 5123, 'count': len(indices), 'type': 'SCALAR'},
|
||||
{'bufferView': 3, 'componentType': 5123, 'count': len(verts), 'type': 'VEC4'},
|
||||
{'bufferView': 4, 'componentType': 5126, 'count': len(verts), 'type': 'VEC4'},
|
||||
{'bufferView': 5, 'componentType': 5126, 'count': len(order), 'type': 'MAT4'},
|
||||
]
|
||||
|
||||
anim_base = off # animations start here
|
||||
|
||||
for anim_name, tracks in animations.items():
|
||||
all_times = set()
|
||||
for bn, kfs in tracks.items():
|
||||
for t, *_ in kfs:
|
||||
all_times.add(t)
|
||||
sorted_times = sorted(all_times)
|
||||
if len(sorted_times) < 2:
|
||||
continue
|
||||
|
||||
time_data = pad(struct.pack(f'<{len(sorted_times)}f', *sorted_times))
|
||||
time_off = len(anim_data)
|
||||
anim_data.extend(time_data)
|
||||
|
||||
# Buffer view for time
|
||||
tv_idx = len(buffer_views)
|
||||
buffer_views.append({'buffer': 0, 'byteOffset': anim_base + time_off, 'byteLength': len(time_data)})
|
||||
|
||||
# Accessor for time (shared per animation)
|
||||
t_acc_idx = len(accessors)
|
||||
accessors.append({'bufferView': tv_idx, 'componentType': 5126, 'count': len(sorted_times),
|
||||
'type': 'SCALAR', 'min': [sorted_times[0]], 'max': [sorted_times[-1]]})
|
||||
|
||||
samplers = []
|
||||
channels = []
|
||||
|
||||
for bone_name, keyframes in tracks.items():
|
||||
if bone_name not in name_to_idx:
|
||||
continue
|
||||
node_idx = name_to_idx[bone_name]
|
||||
|
||||
kf_times = [t for t, *_ in keyframes]
|
||||
kf_trans = [list(kf[1:4]) for kf in keyframes]
|
||||
kf_rot = [list(kf[4:8]) for kf in keyframes]
|
||||
kf_scale = [list(kf[8:11]) for kf in keyframes]
|
||||
|
||||
for data, acc_type, path in [
|
||||
(lerp_keyframes(kf_times, kf_trans, sorted_times), 'VEC3', 'translation'),
|
||||
(lerp_keyframes(kf_times, kf_rot, sorted_times), 'VEC4', 'rotation'),
|
||||
(lerp_keyframes(kf_times, kf_scale, sorted_times), 'VEC3', 'scale'),
|
||||
]:
|
||||
raw = pad(struct.pack(f'<{len(data)}f', *data))
|
||||
off2 = len(anim_data)
|
||||
anim_data.extend(raw)
|
||||
|
||||
dv_idx = len(buffer_views)
|
||||
buffer_views.append({'buffer': 0, 'byteOffset': anim_base + off2, 'byteLength': len(raw)})
|
||||
|
||||
acc_idx = len(accessors)
|
||||
accessors.append({'bufferView': dv_idx, 'componentType': 5126,
|
||||
'count': len(sorted_times), 'type': acc_type})
|
||||
|
||||
samp_idx = len(samplers)
|
||||
samplers.append({'input': t_acc_idx, 'interpolation': 'LINEAR', 'output': acc_idx})
|
||||
|
||||
channels.append({'sampler': samp_idx, 'target': {'node': node_idx, 'path': path}})
|
||||
|
||||
if samplers and channels:
|
||||
gltf_animations.append({'name': anim_name, 'samplers': samplers, 'channels': channels})
|
||||
|
||||
anim_bin = bytes(anim_data)
|
||||
|
||||
# Combine
|
||||
all_bin = pos_bytes + nrm_bytes + idx_bytes + jnt_bytes + wgt_bytes + ibm_bytes + anim_bin
|
||||
|
||||
# ── Build JSON ──────────────────────────────────────────────────────────
|
||||
meshes = [{'name': 'mesh', 'primitives': [{
|
||||
'attributes': {'POSITION': 0, 'NORMAL': 1, 'JOINTS_0': 3, 'WEIGHTS_0': 4},
|
||||
'indices': 2,
|
||||
'material': 0,
|
||||
}]}]
|
||||
|
||||
materials = [{
|
||||
'name': 'default',
|
||||
'pbrMetallicRoughness': {
|
||||
'baseColorFactor': [1.0, 1.0, 1.0, 1.0],
|
||||
'metallicFactor': 0.0,
|
||||
'roughnessFactor': 0.5,
|
||||
},
|
||||
}]
|
||||
|
||||
skin = [{'inverseBindMatrices': 5, 'joints': skin_joints, 'name': 'skin'}]
|
||||
|
||||
gltf = {
|
||||
'asset': {'version': '2.0', 'generator': 'ogre2gltf.py'},
|
||||
'scene': 0,
|
||||
'scenes': [{'nodes': [scene_node_idx]}],
|
||||
'nodes': nodes,
|
||||
'meshes': meshes,
|
||||
'materials': materials,
|
||||
'skins': skin,
|
||||
'animations': gltf_animations,
|
||||
'accessors': accessors,
|
||||
'bufferViews': buffer_views,
|
||||
'buffers': [{'byteLength': len(all_bin)}],
|
||||
}
|
||||
|
||||
# ── Write GLB ───────────────────────────────────────────────────────────
|
||||
gltf_json = json.dumps(gltf, separators=(',', ':'), allow_nan=False).encode('utf-8')
|
||||
# GLB spec: JSON chunk padded with spaces (0x20), BIN chunk padded with nulls (0x00)
|
||||
while len(gltf_json) % 4:
|
||||
gltf_json += b' '
|
||||
while len(all_bin) % 4:
|
||||
all_bin += b'\x00'
|
||||
|
||||
header = struct.pack('<III', 0x46546C67, 2, 12 + 8 + len(gltf_json) + 8 + len(all_bin))
|
||||
json_chunk = struct.pack('<II', len(gltf_json), 0x4E4F534A)
|
||||
bin_chunk = struct.pack('<II', len(all_bin), 0x004E4942)
|
||||
|
||||
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
||||
with open(output_path, 'wb') as f:
|
||||
f.write(header + json_chunk + gltf_json + bin_chunk + all_bin)
|
||||
|
||||
print(f' -> {output_path} ({len(verts)}v {len(faces)}f {len(bones)}b {len(gltf_animations)}a)')
|
||||
|
||||
# ── Matrix inversion ───────────────────────────────────────────────────────
|
||||
|
||||
def invert_4x4(m):
|
||||
"""Invert 4x4 row-major matrix, return row-major."""
|
||||
a, b, c, d, e, f, g, h, i, j, k, l, m_, n, o, p = m
|
||||
det = (a * (f*k*p + g*l*n + h*j*o - h*k*n - f*l*o - g*j*p) -
|
||||
b * (e*k*p + g*l*m_ + h*i*o - h*k*m_ - e*l*o - g*i*p) +
|
||||
c * (e*j*p + f*l*m_ + h*i*n - h*j*m_ - e*l*n - f*i*p) -
|
||||
d * (e*j*o + f*k*m_ + g*i*n - g*j*m_ - e*k*n - f*i*o))
|
||||
if abs(det) < 1e-12:
|
||||
return [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]
|
||||
inv_det = 1.0 / det
|
||||
return [
|
||||
(f*k*p + g*l*n + h*j*o - h*k*n - f*l*o - g*j*p) * inv_det,
|
||||
(b*k*p + c*l*n + d*j*o - d*k*n - b*l*o - c*j*p) * inv_det,
|
||||
(b*g*p + c*h*n + d*f*o - d*g*n - b*h*o - c*f*p) * inv_det,
|
||||
(b*g*l + c*h*j + d*f*k - d*g*j - b*h*k - c*f*l) * inv_det,
|
||||
(e*k*p + g*l*m_ + h*i*o - h*k*m_ - e*l*o - g*i*p) * inv_det,
|
||||
(a*k*p + c*l*m_ + d*i*o - d*k*m_ - a*l*o - c*i*p) * inv_det,
|
||||
(a*g*p + c*h*m_ + d*e*o - d*g*m_ - a*h*o - c*e*p) * inv_det,
|
||||
(a*g*l + c*h*i + d*e*k - d*g*i - a*h*k - c*e*l) * inv_det,
|
||||
(e*j*p + f*l*m_ + h*i*n - h*j*m_ - e*l*n - f*i*p) * inv_det,
|
||||
(a*j*p + b*l*m_ + d*i*n - d*j*m_ - a*l*n - b*i*p) * inv_det,
|
||||
(a*f*p + b*h*m_ + d*e*n - d*f*m_ - a*h*n - b*e*p) * inv_det,
|
||||
(a*f*l + b*h*i + d*e*j - d*f*i - a*h*j - b*e*l) * inv_det,
|
||||
(e*j*o + f*k*m_ + g*i*n - g*j*m_ - e*k*n - f*i*o) * inv_det,
|
||||
(a*j*o + b*k*m_ + c*i*n - c*j*m_ - a*k*n - b*i*o) * inv_det,
|
||||
(a*f*o + b*g*m_ + c*e*n - c*f*m_ - a*g*n - b*e*o) * inv_det,
|
||||
(a*f*k + b*g*i + c*e*j - c*f*i - a*g*j - b*e*k) * inv_det,
|
||||
]
|
||||
|
||||
def invert_4x4_colmajor(m):
|
||||
"""Invert 4x4 column-major matrix, return column-major."""
|
||||
# Convert col-major to row-major, invert, convert back
|
||||
rowm = [m[i + j*4] for j in range(4) for i in range(4)]
|
||||
inv_rowm = invert_4x4(rowm)
|
||||
return [inv_rowm[j + i*4] for i in range(4) for j in range(4)]
|
||||
|
||||
# ── Main ────────────────────────────────────────────────────────────────────
|
||||
|
||||
def main():
|
||||
base = '/var/home/nico/Gamedev/Wackelpeter/extracted/Models'
|
||||
out = '/var/home/nico/Gamedev/Wackelpeter/web/public/models'
|
||||
|
||||
for name, mesh, skel in [
|
||||
('blob', 'blob/Blob', 'blob/Blob'),
|
||||
('sword', 'sword/sword', 'sword/sword'),
|
||||
('shield', 'shield/shield', 'shield/shield'),
|
||||
]:
|
||||
mf = os.path.join(base, f'{mesh}.mesh.xml')
|
||||
sf = os.path.join(base, f'{skel}.skeleton.xml')
|
||||
if os.path.exists(mf) and os.path.exists(sf):
|
||||
print(f'Converting {name}...')
|
||||
build_gltf(mf, sf, os.path.join(out, f'{name}.glb'))
|
||||
else:
|
||||
print(f'Skipping {name} (missing XML)')
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,117 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Wackelpeter</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
html, body { width: 100%; height: 100%; overflow: hidden; background: #000; }
|
||||
canvas { display: block; }
|
||||
#ui-overlay {
|
||||
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
|
||||
pointer-events: none; font-family: monospace; z-index: 10;
|
||||
}
|
||||
#health-bar-bg {
|
||||
position: absolute; top: 16px; left: 16px; width: 250px; height: 20px;
|
||||
background: rgba(0,0,0,0.6); border: 2px solid #555;
|
||||
}
|
||||
#health-bar {
|
||||
position: absolute; top: 16px; left: 16px; width: 250px; height: 20px;
|
||||
background: #cc0000;
|
||||
transition: width 0.1s linear;
|
||||
}
|
||||
#health-text {
|
||||
position: absolute; top: 16px; left: 16px; width: 250px; height: 20px;
|
||||
text-align: center; line-height: 20px; color: #fff; font-size: 12px;
|
||||
}
|
||||
#wave-label {
|
||||
position: absolute; top: 48px; left: 16px; color: #ffcc00; font-size: 18px;
|
||||
font-family: monospace; text-shadow: 2px 2px 4px #000;
|
||||
}
|
||||
#kills-label {
|
||||
position: absolute; top: 72px; left: 16px; color: #ffcc00; font-size: 16px;
|
||||
font-family: monospace; text-shadow: 2px 2px 4px #000;
|
||||
}
|
||||
.cooldown-container {
|
||||
position: absolute; left: 16px; width: 200px; height: 16px;
|
||||
}
|
||||
.cooldown-bg {
|
||||
width: 100%; height: 100%; background: rgba(0,0,0,0.6); border: 1px solid #555;
|
||||
}
|
||||
.cooldown-fill {
|
||||
height: 100%; background: #2266dd; transition: width 0.05s linear;
|
||||
}
|
||||
.cooldown-label {
|
||||
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
|
||||
text-align: left; line-height: 16px; color: #fff; font-size: 11px;
|
||||
padding-left: 4px; font-family: monospace;
|
||||
}
|
||||
#fireball-cooldown { bottom: 72px; }
|
||||
#sword-cooldown { bottom: 48px; }
|
||||
#teleport-cooldown { bottom: 24px; }
|
||||
#wave-bar-container {
|
||||
position: absolute; top: 16px; right: 16px; width: 250px; height: 16px;
|
||||
}
|
||||
#wave-bar-bg {
|
||||
width: 100%; height: 100%; background: rgba(0,0,0,0.6); border: 1px solid #555;
|
||||
}
|
||||
#wave-bar-fill {
|
||||
height: 100%; background: #ccaa00; transition: width 0.1s linear;
|
||||
}
|
||||
#wave-bar-label {
|
||||
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
|
||||
text-align: center; line-height: 16px; color: #fff; font-size: 11px;
|
||||
font-family: monospace;
|
||||
}
|
||||
#game-over {
|
||||
position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
|
||||
font-size: 100px; color: #fff; font-family: monospace; opacity: 0;
|
||||
text-shadow: 4px 4px 8px #000;
|
||||
transition: opacity 2s ease-in;
|
||||
}
|
||||
#instructions {
|
||||
position: absolute; bottom: 16px; left: 50%; transform: translateX(-50%);
|
||||
color: rgba(255,255,255,0.5); font-size: 11px; font-family: monospace;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="ui-overlay">
|
||||
<div id="health-bar-bg"></div>
|
||||
<div id="health-bar"></div>
|
||||
<div id="health-text">100%</div>
|
||||
<div id="wave-label">Wave 0</div>
|
||||
<div id="kills-label">Kills: 0</div>
|
||||
|
||||
<div id="fireball-cooldown" class="cooldown-container">
|
||||
<div class="cooldown-bg"></div>
|
||||
<div class="cooldown-fill"></div>
|
||||
<div class="cooldown-label">Fireball</div>
|
||||
</div>
|
||||
<div id="sword-cooldown" class="cooldown-container">
|
||||
<div class="cooldown-bg"></div>
|
||||
<div class="cooldown-fill"></div>
|
||||
<div class="cooldown-label">Sword</div>
|
||||
</div>
|
||||
<div id="teleport-cooldown" class="cooldown-container">
|
||||
<div class="cooldown-bg"></div>
|
||||
<div class="cooldown-fill"></div>
|
||||
<div class="cooldown-label">Teleport</div>
|
||||
</div>
|
||||
|
||||
<div id="wave-bar-container">
|
||||
<div id="wave-bar-bg"></div>
|
||||
<div id="wave-bar-fill"></div>
|
||||
<div id="wave-bar-label">Next Wave...</div>
|
||||
</div>
|
||||
|
||||
<div id="game-over">Verloren!</div>
|
||||
<div id="instructions">
|
||||
WASD: Move · Space: Jump · Mouse: Aim · Left Click: Fireball · Right Click: Sword · M3: Swap hands · E: Teleport
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "wackelpeter",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"three": "^0.170.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/three": "^0.170.0",
|
||||
"typescript": "^5.7.0",
|
||||
"vite": "^6.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"skeletons":[],"geometries":[{"texcoords":[0.120075,0.879188,0.360224,0.879925,0.360961,0.639776,0.120812,0.639039,0.120812,0.158003,0.120075,0.398152,0.360224,0.398889,0.360961,0.15874,0.620443,0.399512,0.619706,0.159363,0.601111,0.15942,0.601848,0.399569,0.879188,0.399569,0.879925,0.15942,0.86133,0.159363,0.860593,0.399512,0.619706,0.879925,0.620443,0.639776,0.601848,0.639719,0.601111,0.879868,0.879188,0.879925,0.879925,0.639776,0.86133,0.639719,0.860593,0.879868],"indices":[0,1,2,0,2,3,4,5,6,4,6,7,8,9,10,8,10,11,12,13,14,12,14,15,16,17,18,16,18,19,20,21,22,20,22,23],"name":"tile-geom-1","elementCount":36,"positions":[1.0,0.0,-1.0,1.0,0.0,1.0,-1.0,0.0,1.0,-1.0,0.0,-1.0,-1.0,0.154864,1.0,1.0,0.154864,1.0,1.0,0.154864,-1.0,-1.0,0.154864,-1.0,1.0,0.0,-1.0,-1.0,0.0,-1.0,-1.0,0.154864,-1.0,1.0,0.154864,-1.0,1.0,0.0,1.0,1.0,0.0,-1.0,1.0,0.154864,-1.0,1.0,0.154864,1.0,-1.0,0.0,-1.0,-1.0,0.0,1.0,-1.0,0.154864,1.0,-1.0,0.154864,-1.0,-1.0,0.0,1.0,1.0,0.0,1.0,1.0,0.154864,1.0,-1.0,0.154864,1.0],"triangleCount":12,"normals":[0.0,-1.0,-0.0,0.0,-1.0,-0.0,0.0,-1.0,-0.0,0.0,-1.0,-0.0,0.0,1.0,-0.0,0.0,1.0,-0.0,0.0,1.0,-0.0,0.0,1.0,-0.0,0.0,0.0,-1.0,0.0,0.0,-1.0,0.0,0.0,-1.0,0.0,0.0,-1.0,1.0,0.0,-0.0,1.0,0.0,-0.0,1.0,0.0,-0.0,1.0,0.0,-0.0,-1.0,-0.0,-0.0,-1.0,-0.0,-0.0,-1.0,-0.0,-0.0,-1.0,-0.0,-0.0,0.0,-0.0,1.0,0.0,-0.0,1.0,0.0,-0.0,1.0,0.0,-0.0,1.0],"vertexCount":24}],"animations":{}}
|
||||
@@ -0,0 +1 @@
|
||||
{"skeletons":[],"geometries":[{"texcoords":[0.120075,0.879188,0.360224,0.879925,0.360961,0.639776,0.120812,0.639039,0.120812,0.158003,0.120075,0.398152,0.360224,0.398889,0.360961,0.15874,0.620443,0.399512,0.619706,0.159363,0.601111,0.15942,0.601848,0.399569,0.879188,0.399569,0.879925,0.15942,0.86133,0.159363,0.860593,0.399512,0.619706,0.879925,0.620443,0.639776,0.601848,0.639719,0.601111,0.879868,0.879188,0.879925,0.879925,0.639776,0.86133,0.639719,0.860593,0.879868],"indices":[0,1,2,0,2,3,4,5,6,4,6,7,8,9,10,8,10,11,12,13,14,12,14,15,16,17,18,16,18,19,20,21,22,20,22,23],"name":"tile-geom-1","elementCount":36,"positions":[1.0,0.0,-1.0,1.0,0.0,1.0,-1.0,0.0,1.0,-1.0,0.0,-1.0,-1.0,0.154864,1.0,1.0,0.154864,1.0,1.0,0.154864,-1.0,-1.0,0.154864,-1.0,1.0,0.0,-1.0,-1.0,0.0,-1.0,-1.0,0.154864,-1.0,1.0,0.154864,-1.0,1.0,0.0,1.0,1.0,0.0,-1.0,1.0,0.154864,-1.0,1.0,0.154864,1.0,-1.0,0.0,-1.0,-1.0,0.0,1.0,-1.0,0.154864,1.0,-1.0,0.154864,-1.0,-1.0,0.0,1.0,1.0,0.0,1.0,1.0,0.154864,1.0,-1.0,0.154864,1.0],"triangleCount":12,"normals":[0.0,-1.0,-0.0,0.0,-1.0,-0.0,0.0,-1.0,-0.0,0.0,-1.0,-0.0,0.0,1.0,-0.0,0.0,1.0,-0.0,0.0,1.0,-0.0,0.0,1.0,-0.0,0.0,0.0,-1.0,0.0,0.0,-1.0,0.0,0.0,-1.0,0.0,0.0,-1.0,1.0,0.0,-0.0,1.0,0.0,-0.0,1.0,0.0,-0.0,1.0,0.0,-0.0,-1.0,-0.0,-0.0,-1.0,-0.0,-0.0,-1.0,-0.0,-0.0,-1.0,-0.0,-0.0,0.0,-0.0,1.0,0.0,-0.0,1.0,0.0,-0.0,1.0,0.0,-0.0,1.0],"vertexCount":24}],"animations":{}}
|
||||
@@ -0,0 +1 @@
|
||||
{"skeletons":[],"geometries":[{"texcoords":[0.998999,0.998999,0.667668,0.998999,0.667668,0.667668,0.998999,0.667668,0.332332,0.998999,0.001001,0.998999,0.001001,0.667668,0.332332,0.667668,0.332332,0.665666,0.001001,0.665666,0.001001,0.334334,0.332332,0.334334,0.665666,0.998999,0.334334,0.998999,0.334334,0.667668,0.665666,0.667668,0.665666,0.665666,0.334334,0.665666,0.334334,0.334334,0.665666,0.334334,0.668438,0.666433,0.666901,0.335105,0.998229,0.333567,0.999766,0.664895],"indices":[0,1,2,0,2,3,4,5,6,4,6,7,8,9,10,8,10,11,12,13,14,12,14,15,16,17,18,16,18,19,20,21,22,20,22,23],"name":"wall1","elementCount":36,"positions":[-0.999999,1.991615,1.000001,-0.999999,-0.008385,1.000002,1.000001,-0.008385,0.999999,1.000001,1.991615,0.999998,-1.000002,1.991613,-0.999999,0.999999,1.991613,-1.000002,0.999999,-0.008386,-1.000001,-1.000001,-0.008387,-0.999998,-0.999999,1.991615,1.000001,-1.000002,1.991613,-0.999999,-1.000001,-0.008387,-0.999998,-0.999999,-0.008385,1.000002,-0.999999,-0.008385,1.000002,-1.000001,-0.008387,-0.999998,0.999999,-0.008386,-1.000001,1.000001,-0.008385,0.999999,1.000001,-0.008385,0.999999,0.999999,-0.008386,-1.000001,0.999999,1.991613,-1.000002,1.000001,1.991615,0.999998,-1.000002,1.991613,-0.999999,-0.999999,1.991615,1.000001,1.000001,1.991615,0.999998,0.999999,1.991613,-1.000002],"triangleCount":12,"normals":[1.0E-6,1.0E-6,1.0,1.0E-6,1.0E-6,1.0,1.0E-6,1.0E-6,1.0,1.0E-6,1.0E-6,1.0,-1.0E-6,-1.0E-6,-1.0,-1.0E-6,-1.0E-6,-1.0,-1.0E-6,-1.0E-6,-1.0,-1.0E-6,-1.0E-6,-1.0,-1.0,-0.0,1.0E-6,-1.0,-0.0,1.0E-6,-1.0,-0.0,1.0E-6,-1.0,-0.0,1.0E-6,0.0,-1.0,1.0E-6,0.0,-1.0,1.0E-6,0.0,-1.0,1.0E-6,0.0,-1.0,1.0E-6,1.0,0.0,-1.0E-6,1.0,0.0,-1.0E-6,1.0,0.0,-1.0E-6,1.0,0.0,-1.0E-6,-0.0,1.0,-1.0E-6,-0.0,1.0,-1.0E-6,-0.0,1.0,-1.0E-6,-0.0,1.0,-1.0E-6],"vertexCount":24}],"animations":{}}
|
||||
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 140 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 102 KiB |
|
After Width: | Height: | Size: 102 KiB |
|
After Width: | Height: | Size: 122 KiB |
|
After Width: | Height: | Size: 206 KiB |
|
After Width: | Height: | Size: 333 KiB |
@@ -0,0 +1,50 @@
|
||||
import * as THREE from 'three';
|
||||
|
||||
export class Arena extends THREE.Group {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
const tileSize = 10;
|
||||
const half = tileSize / 2;
|
||||
|
||||
for (let x = -45; x < 50; x += tileSize) {
|
||||
for (let z = -45; z < 50; z += tileSize) {
|
||||
const isDark = ((x / tileSize + z / tileSize) & 1) === 0;
|
||||
const color = isDark ? 0x668866 : 0x557755;
|
||||
|
||||
const tileGeom = new THREE.BoxGeometry(9.5, 0.3, 9.5);
|
||||
const tileMat = new THREE.MeshStandardMaterial({
|
||||
color,
|
||||
roughness: 0.7,
|
||||
metalness: 0.0,
|
||||
});
|
||||
|
||||
const tile = new THREE.Mesh(tileGeom, tileMat);
|
||||
tile.position.set(x, -0.15, z);
|
||||
tile.receiveShadow = true;
|
||||
this.add(tile);
|
||||
}
|
||||
}
|
||||
|
||||
// Walls
|
||||
const wallGeom = new THREE.BoxGeometry(tileSize - 0.5, 2, 1);
|
||||
const wallMat = new THREE.MeshToonMaterial({ color: 0x776677 });
|
||||
|
||||
for (let coord = -50; coord <= 50; coord += tileSize) {
|
||||
this.createWall(coord, 49.5, wallGeom, wallMat);
|
||||
this.createWall(coord, -49.5, wallGeom, wallMat);
|
||||
}
|
||||
for (let coord = -50; coord <= 50; coord += tileSize) {
|
||||
this.createWall(49.5, coord, new THREE.BoxGeometry(1, 2, tileSize - 0.5), wallMat);
|
||||
this.createWall(-49.5, coord, new THREE.BoxGeometry(1, 2, tileSize - 0.5), wallMat);
|
||||
}
|
||||
}
|
||||
|
||||
private createWall(x: number, z: number, geom: THREE.BoxGeometry, mat: THREE.Material) {
|
||||
const wall = new THREE.Mesh(geom, mat);
|
||||
wall.position.set(x, 1, z);
|
||||
wall.castShadow = true;
|
||||
wall.receiveShadow = true;
|
||||
this.add(wall);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
import * as THREE from 'three';
|
||||
import { loadGLB } from './MeshLoader';
|
||||
|
||||
let crossGeometry: THREE.BufferGeometry | null = null;
|
||||
let crossParticlesTemplate: THREE.Points | null = null;
|
||||
|
||||
export async function initCrossModel() {
|
||||
try {
|
||||
const gltf = await loadGLB('cross');
|
||||
gltf.scene.traverse((child) => {
|
||||
if (child instanceof THREE.Mesh) {
|
||||
crossGeometry = child.geometry;
|
||||
}
|
||||
});
|
||||
} catch {
|
||||
console.warn('Failed to load cross model');
|
||||
}
|
||||
}
|
||||
|
||||
function createGreenParticles(): THREE.Points {
|
||||
const count = 50;
|
||||
const tex = new THREE.TextureLoader().load('./textures/flame.png');
|
||||
tex.colorSpace = THREE.SRGBColorSpace;
|
||||
const geom = new THREE.BufferGeometry();
|
||||
const positions = new Float32Array(count * 3);
|
||||
geom.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
||||
const mat = new THREE.PointsMaterial({
|
||||
map: tex,
|
||||
color: 0x66ff66,
|
||||
size: 0.12,
|
||||
blending: THREE.AdditiveBlending,
|
||||
depthWrite: false,
|
||||
transparent: true,
|
||||
});
|
||||
const points = new THREE.Points(geom, mat);
|
||||
points.renderOrder = 999;
|
||||
points.userData = {
|
||||
velocities: [] as THREE.Vector3[],
|
||||
lifetimes: [] as number[],
|
||||
ages: [] as number[],
|
||||
timer: 0,
|
||||
};
|
||||
for (let i = 0; i < count; i++) {
|
||||
positions[i * 3] = (Math.random() - 0.5) * 0.8;
|
||||
positions[i * 3 + 1] = Math.random() * 1.2;
|
||||
positions[i * 3 + 2] = (Math.random() - 0.5) * 0.8;
|
||||
points.userData.velocities.push(new THREE.Vector3(
|
||||
(Math.random() - 0.5) * 0.5,
|
||||
1.5 + Math.random() * 1.5,
|
||||
(Math.random() - 0.5) * 0.5
|
||||
));
|
||||
points.userData.lifetimes.push(1 + Math.random());
|
||||
points.userData.ages.push(Math.random() * 2);
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
export class Cross {
|
||||
body: THREE.Group;
|
||||
private particles: THREE.Points;
|
||||
|
||||
constructor(position: THREE.Vector3) {
|
||||
this.body = new THREE.Group();
|
||||
this.body.position.copy(position);
|
||||
this.body.position.y = 0.3;
|
||||
|
||||
if (crossGeometry) {
|
||||
// Original material: red cross (from j3o material extraction)
|
||||
const mesh = new THREE.Mesh(crossGeometry, new THREE.MeshToonMaterial({
|
||||
color: 0xff0000, emissive: 0x440000,
|
||||
}));
|
||||
mesh.castShadow = true;
|
||||
mesh.scale.set(0.25, 0.25, 0.25);
|
||||
this.body.add(mesh);
|
||||
} else {
|
||||
// Fallback cross
|
||||
const crossMaterial = new THREE.MeshToonMaterial({
|
||||
color: 0xff0000, emissive: 0x440000,
|
||||
});
|
||||
const vertical = new THREE.Mesh(new THREE.BoxGeometry(0.15, 0.8, 0.15), crossMaterial);
|
||||
vertical.position.y = 0.4;
|
||||
this.body.add(vertical);
|
||||
const horizontal = new THREE.Mesh(new THREE.BoxGeometry(0.6, 0.15, 0.15), crossMaterial);
|
||||
horizontal.position.y = 0.5;
|
||||
this.body.add(horizontal);
|
||||
}
|
||||
|
||||
// Green glow
|
||||
const glowGeom = new THREE.SphereGeometry(0.6, 16, 16);
|
||||
const glowMat = new THREE.MeshBasicMaterial({
|
||||
color: 0x00ff00, transparent: true, opacity: 0.15,
|
||||
});
|
||||
const glow = new THREE.Mesh(glowGeom, glowMat);
|
||||
this.body.add(glow);
|
||||
|
||||
// Upward-flying light green particles (like the original)
|
||||
this.particles = createGreenParticles();
|
||||
this.body.add(this.particles);
|
||||
}
|
||||
|
||||
update(dt: number) {
|
||||
const attr = this.particles.geometry.getAttribute('position') as THREE.BufferAttribute;
|
||||
const arr = attr.array as Float32Array;
|
||||
const ud = this.particles.userData;
|
||||
const count = ud.velocities.length;
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
ud.ages[i] += dt;
|
||||
const life = ud.lifetimes[i];
|
||||
if (ud.ages[i] >= life) {
|
||||
// Respawn at bottom
|
||||
ud.ages[i] = 0;
|
||||
arr[i * 3] = (Math.random() - 0.5) * 0.8;
|
||||
arr[i * 3 + 1] = 0;
|
||||
arr[i * 3 + 2] = (Math.random() - 0.5) * 0.8;
|
||||
continue;
|
||||
}
|
||||
const v = ud.velocities[i];
|
||||
arr[i * 3] += v.x * dt;
|
||||
arr[i * 3 + 1] += v.y * dt;
|
||||
arr[i * 3 + 2] += v.z * dt;
|
||||
}
|
||||
attr.needsUpdate = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
import * as THREE from 'three';
|
||||
import type { Game } from './Game';
|
||||
import { playSound, playLoopingSound } from './SoundManager';
|
||||
import { loadGLB } from './MeshLoader';
|
||||
import type { GLTF } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
||||
import { clone as cloneSkeleton } from 'three/examples/jsm/utils/SkeletonUtils.js';
|
||||
|
||||
let spiderGLTF: GLTF | null = null;
|
||||
const ANIMATIONS_ENABLED = true;
|
||||
export async function initSpiderModel() {
|
||||
try {
|
||||
spiderGLTF = await loadGLB('spider');
|
||||
} catch {
|
||||
console.warn('Failed to load spider model');
|
||||
}
|
||||
}
|
||||
|
||||
export class Enemy {
|
||||
body: THREE.Group;
|
||||
dead = false;
|
||||
deathTimer = 0;
|
||||
knockback = 0;
|
||||
health = 100;
|
||||
mixer: THREE.AnimationMixer | null = null;
|
||||
private currentAnim = '';
|
||||
private clips: Map<string, THREE.AnimationAction> = new Map();
|
||||
private stopWalkSound: (() => void) | null = null;
|
||||
|
||||
constructor(_playerPos: THREE.Vector3) {
|
||||
this.body = new THREE.Group();
|
||||
this.stopWalkSound = playLoopingSound('spiderwalking', 0.3);
|
||||
|
||||
if (spiderGLTF) {
|
||||
const model = cloneSkeleton(spiderGLTF.scene);
|
||||
// Original game scaled the spider model by 0.5 (WalkingEnemy.setupModel)
|
||||
model.scale.set(0.5, 0.5, 0.5);
|
||||
const spiderTexture = new THREE.TextureLoader().load('./textures/spider_animation2.png');
|
||||
spiderTexture.flipY = false;
|
||||
spiderTexture.colorSpace = THREE.SRGBColorSpace;
|
||||
model.traverse((child) => {
|
||||
if (child instanceof THREE.Mesh) {
|
||||
child.castShadow = true;
|
||||
child.material = new THREE.MeshToonMaterial({ map: spiderTexture });
|
||||
}
|
||||
});
|
||||
this.body.add(model);
|
||||
|
||||
if (spiderGLTF.animations.length > 0 && ANIMATIONS_ENABLED) {
|
||||
this.mixer = new THREE.AnimationMixer(model);
|
||||
for (const clip of spiderGLTF.animations) {
|
||||
const action = this.mixer.clipAction(clip);
|
||||
this.clips.set(clip.name, action);
|
||||
}
|
||||
this.playAnim('stand');
|
||||
}
|
||||
} else {
|
||||
// Fallback
|
||||
const abdomenGeom = new THREE.SphereGeometry(0.5, 8, 8);
|
||||
const bodyMat = new THREE.MeshToonMaterial({ color: 0x333333 });
|
||||
const abdomen = new THREE.Mesh(abdomenGeom, bodyMat);
|
||||
abdomen.scale.set(1, 0.6, 1.3);
|
||||
abdomen.position.y = 0.6;
|
||||
abdomen.castShadow = true;
|
||||
this.body.add(abdomen);
|
||||
|
||||
const cephalothoraxGeom = new THREE.SphereGeometry(0.3, 8, 8);
|
||||
const cephalothorax = new THREE.Mesh(cephalothoraxGeom, bodyMat);
|
||||
cephalothorax.position.y = 0.6;
|
||||
cephalothorax.position.z = 0.5;
|
||||
cephalothorax.castShadow = true;
|
||||
this.body.add(cephalothorax);
|
||||
|
||||
const eyeGeom = new THREE.SphereGeometry(0.06, 6, 6);
|
||||
const eyeMat = new THREE.MeshBasicMaterial({ color: 0xff0000 });
|
||||
for (const sign of [-1, 1]) {
|
||||
const eye = new THREE.Mesh(eyeGeom, eyeMat);
|
||||
eye.position.set(sign * 0.12, 0.85, 0.7);
|
||||
this.body.add(eye);
|
||||
}
|
||||
|
||||
const legGeom = new THREE.CylinderGeometry(0.04, 0.04, 0.8, 4);
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const leg = new THREE.Mesh(legGeom, new THREE.MeshToonMaterial({ color: 0x222222 }));
|
||||
const angle = (i / 8) * Math.PI * 2;
|
||||
const side = i < 4 ? 1 : -1;
|
||||
leg.position.set(Math.cos(angle) * 0.35, 0.3, Math.sin(angle) * 0.35);
|
||||
leg.rotation.z = side * 0.6;
|
||||
leg.rotation.x = angle;
|
||||
leg.castShadow = true;
|
||||
this.body.add(leg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
playAnim(name: string, loop = true, speed = 1) {
|
||||
if (!this.clips.has(name) || this.currentAnim === name) return;
|
||||
// Stop the previous animation so it doesn't blend with the new one
|
||||
if (this.currentAnim) {
|
||||
const prev = this.clips.get(this.currentAnim);
|
||||
if (prev) prev.stop();
|
||||
}
|
||||
this.currentAnim = name;
|
||||
const action = this.clips.get(name)!;
|
||||
action.reset();
|
||||
action.setLoop(loop ? THREE.LoopRepeat : THREE.LoopOnce, loop ? Infinity : 1);
|
||||
action.clampWhenFinished = !loop;
|
||||
action.setEffectiveTimeScale(speed);
|
||||
action.play();
|
||||
}
|
||||
|
||||
updateAnimation(dt: number) {
|
||||
if (this.mixer) this.mixer.update(dt);
|
||||
}
|
||||
|
||||
takeDamage(damage: number, game: Game) {
|
||||
if (this.dead) return;
|
||||
this.health -= damage;
|
||||
this.knockback = 0.5;
|
||||
|
||||
playSound('damage', 0.7);
|
||||
|
||||
if (this.health <= 0) {
|
||||
this.die(game);
|
||||
}
|
||||
}
|
||||
|
||||
private die(game: Game) {
|
||||
if (this.dead) return;
|
||||
this.dead = true;
|
||||
this.playAnim('die', false);
|
||||
if (this.stopWalkSound) {
|
||||
this.stopWalkSound();
|
||||
this.stopWalkSound = null;
|
||||
}
|
||||
game.removeEnemy(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
import * as THREE from 'three';
|
||||
import { playSound } from './SoundManager';
|
||||
|
||||
const flameTexture = new THREE.TextureLoader().load('./textures/flame.png');
|
||||
flameTexture.colorSpace = THREE.SRGBColorSpace;
|
||||
|
||||
export class Fireball {
|
||||
body: THREE.Group;
|
||||
light: THREE.PointLight;
|
||||
direction: THREE.Vector3;
|
||||
lifetime = 1;
|
||||
exploding = false;
|
||||
timer = 0;
|
||||
damageDone = false;
|
||||
private trailParticles: THREE.Points;
|
||||
private trailVelocities: Float32Array;
|
||||
private trailLives: Float32Array;
|
||||
private trailMaxLife = 0.6;
|
||||
|
||||
constructor(start: THREE.Vector3, direction: THREE.Vector3) {
|
||||
this.direction = direction.clone();
|
||||
this.body = new THREE.Group();
|
||||
this.body.position.copy(start);
|
||||
|
||||
// Fireball core (additive glow)
|
||||
const coreGeom = new THREE.SphereGeometry(0.45, 16, 16);
|
||||
const coreMat = new THREE.MeshBasicMaterial({
|
||||
color: 0xffcc44,
|
||||
blending: THREE.AdditiveBlending,
|
||||
depthWrite: false,
|
||||
transparent: true,
|
||||
});
|
||||
const core = new THREE.Mesh(coreGeom, coreMat);
|
||||
this.body.add(core);
|
||||
|
||||
const glowGeom = new THREE.SphereGeometry(0.75, 16, 16);
|
||||
const glowMat = new THREE.MeshBasicMaterial({
|
||||
color: 0xff6600, transparent: true, opacity: 0.45,
|
||||
blending: THREE.AdditiveBlending,
|
||||
depthWrite: false,
|
||||
});
|
||||
const glow = new THREE.Mesh(glowGeom, glowMat);
|
||||
this.body.add(glow);
|
||||
|
||||
// Sprite with flame texture
|
||||
const spriteMat = new THREE.SpriteMaterial({
|
||||
map: flameTexture,
|
||||
color: 0xffaa33,
|
||||
blending: THREE.AdditiveBlending,
|
||||
depthWrite: false,
|
||||
transparent: true,
|
||||
});
|
||||
const sprite = new THREE.Sprite(spriteMat);
|
||||
sprite.scale.set(2.2, 2.2, 1);
|
||||
this.body.add(sprite);
|
||||
|
||||
// Trail particles (flame sprites behind the fireball)
|
||||
const trailCount = 30;
|
||||
const trailGeom = new THREE.BufferGeometry();
|
||||
const positions = new Float32Array(trailCount * 3);
|
||||
trailGeom.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
||||
const trailMat = new THREE.PointsMaterial({
|
||||
map: flameTexture,
|
||||
color: 0xff8833,
|
||||
size: 1.0,
|
||||
blending: THREE.AdditiveBlending,
|
||||
depthWrite: false,
|
||||
transparent: true,
|
||||
opacity: 0.7,
|
||||
});
|
||||
this.trailParticles = new THREE.Points(trailGeom, trailMat);
|
||||
this.body.add(this.trailParticles);
|
||||
this.trailVelocities = new Float32Array(trailCount * 3);
|
||||
this.trailLives = new Float32Array(trailCount).fill(0);
|
||||
|
||||
// Strong dynamic light (original: radius 100, orange, with shadow renderer)
|
||||
this.light = new THREE.PointLight(0xff7722, 80, 45, 1.5);
|
||||
this.light.position.set(0, 0.5, 0);
|
||||
this.light.castShadow = true;
|
||||
this.light.shadow.mapSize.width = 256;
|
||||
this.light.shadow.mapSize.height = 256;
|
||||
this.light.shadow.camera.near = 0.5;
|
||||
this.light.shadow.camera.far = 45;
|
||||
this.light.shadow.bias = -0.005;
|
||||
this.body.add(this.light);
|
||||
}
|
||||
|
||||
updateTrail(dt: number, pos: THREE.Vector3) {
|
||||
const attr = this.trailParticles.geometry.getAttribute('position') as THREE.BufferAttribute;
|
||||
const arr = attr.array as Float32Array;
|
||||
|
||||
for (let i = 0; i < this.trailLives.length; i++) {
|
||||
if (this.trailLives[i] > 0) {
|
||||
this.trailLives[i] -= dt;
|
||||
arr[i * 3] += this.trailVelocities[i * 3] * dt;
|
||||
arr[i * 3 + 1] += this.trailVelocities[i * 3 + 1] * dt;
|
||||
arr[i * 3 + 2] += this.trailVelocities[i * 3 + 2] * dt;
|
||||
if (this.trailLives[i] <= 0) {
|
||||
arr[i * 3] = pos.x;
|
||||
arr[i * 3 + 1] = pos.y;
|
||||
arr[i * 3 + 2] = pos.z;
|
||||
}
|
||||
} else {
|
||||
// Spawn new trail particle
|
||||
arr[i * 3] = pos.x;
|
||||
arr[i * 3 + 1] = pos.y;
|
||||
arr[i * 3 + 2] = pos.z;
|
||||
this.trailVelocities[i * 3] = (Math.random() - 0.5) * 1.5;
|
||||
this.trailVelocities[i * 3 + 1] = (Math.random() - 0.5) * 1.5;
|
||||
this.trailVelocities[i * 3 + 2] = (Math.random() - 0.5) * 1.5;
|
||||
this.trailLives[i] = this.trailMaxLife * (0.5 + Math.random());
|
||||
}
|
||||
}
|
||||
attr.needsUpdate = true;
|
||||
}
|
||||
|
||||
explode(scene: THREE.Scene) {
|
||||
if (this.exploding) return;
|
||||
this.exploding = true;
|
||||
this.timer = 0;
|
||||
this.damageDone = false;
|
||||
|
||||
playSound('explosion', 0.6);
|
||||
|
||||
// Hide the fireball core and trail
|
||||
for (const child of [...this.body.children]) {
|
||||
if (child instanceof THREE.Mesh || child instanceof THREE.Sprite || child instanceof THREE.Points) {
|
||||
child.visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Bright flash
|
||||
const flashGeom = new THREE.SphereGeometry(3, 16, 16);
|
||||
const flashMat = new THREE.MeshBasicMaterial({
|
||||
color: 0xffdd66, transparent: true, opacity: 0.9,
|
||||
blending: THREE.AdditiveBlending,
|
||||
depthWrite: false,
|
||||
});
|
||||
const flash = new THREE.Mesh(flashGeom, flashMat);
|
||||
flash.position.copy(this.body.position);
|
||||
scene.add(flash);
|
||||
|
||||
// Big explosion light pulse
|
||||
const flashLight = new THREE.PointLight(0xff8833, 200, 35, 1.5);
|
||||
flashLight.position.copy(this.body.position);
|
||||
scene.add(flashLight);
|
||||
|
||||
const startTime = performance.now();
|
||||
const updateFlash = () => {
|
||||
const elapsed = (performance.now() - startTime) / 1000;
|
||||
if (elapsed > 0.6) {
|
||||
flash.removeFromParent();
|
||||
flash.geometry.dispose();
|
||||
flashMat.dispose();
|
||||
scene.remove(flashLight);
|
||||
return;
|
||||
}
|
||||
const scale = 1 + elapsed * 8;
|
||||
flash.scale.set(scale, scale, scale);
|
||||
flashMat.opacity = 0.9 * (1 - elapsed / 0.6);
|
||||
flashLight.intensity = 200 * (1 - elapsed / 0.6);
|
||||
requestAnimationFrame(updateFlash);
|
||||
};
|
||||
updateFlash();
|
||||
|
||||
// Fire particle burst (original: 50 flame particles)
|
||||
this.spawnExplosionParticles(scene, './textures/flame.png', 0xffaa33, 60, 8, 1.5);
|
||||
|
||||
// Debris stones (original: stone particles with gravity)
|
||||
this.spawnExplosionParticles(scene, './textures/flame.png', 0x555544, 25, 6, 1.0, true);
|
||||
}
|
||||
|
||||
private spawnExplosionParticles(
|
||||
scene: THREE.Scene,
|
||||
texPath: string,
|
||||
color: number,
|
||||
count: number,
|
||||
speed: number,
|
||||
size: number,
|
||||
withGravity = false
|
||||
) {
|
||||
const tex = new THREE.TextureLoader().load(texPath);
|
||||
tex.colorSpace = THREE.SRGBColorSpace;
|
||||
const geom = new THREE.BufferGeometry();
|
||||
const positions = new Float32Array(count * 3);
|
||||
const velocities: THREE.Vector3[] = [];
|
||||
const lives: number[] = [];
|
||||
const maxLives: number[] = [];
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
positions[i * 3] = this.body.position.x;
|
||||
positions[i * 3 + 1] = this.body.position.y;
|
||||
positions[i * 3 + 2] = this.body.position.z;
|
||||
velocities.push(new THREE.Vector3(
|
||||
(Math.random() - 0.5) * speed * 2,
|
||||
Math.random() * speed,
|
||||
(Math.random() - 0.5) * speed * 2
|
||||
));
|
||||
const life = 0.5 + Math.random() * 1.2;
|
||||
lives.push(life);
|
||||
maxLives.push(life);
|
||||
}
|
||||
|
||||
geom.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
||||
const mat = new THREE.PointsMaterial({
|
||||
map: tex,
|
||||
color,
|
||||
size,
|
||||
blending: THREE.AdditiveBlending,
|
||||
depthWrite: false,
|
||||
transparent: true,
|
||||
});
|
||||
const points = new THREE.Points(geom, mat);
|
||||
points.renderOrder = 999;
|
||||
scene.add(points);
|
||||
|
||||
const startTime = performance.now();
|
||||
const update = () => {
|
||||
const elapsed = (performance.now() - startTime) / 1000;
|
||||
if (elapsed > 2) {
|
||||
points.removeFromParent();
|
||||
geom.dispose();
|
||||
mat.dispose();
|
||||
tex.dispose();
|
||||
return;
|
||||
}
|
||||
const attr = geom.getAttribute('position') as THREE.BufferAttribute;
|
||||
const arr = attr.array as Float32Array;
|
||||
let allDead = true;
|
||||
for (let i = 0; i < count; i++) {
|
||||
lives[i] -= 0.016;
|
||||
if (lives[i] <= 0) continue;
|
||||
allDead = false;
|
||||
const v = velocities[i];
|
||||
if (withGravity) v.y -= 9.8 * 0.016;
|
||||
arr[i * 3] += v.x * 0.016;
|
||||
arr[i * 3 + 1] += v.y * 0.016;
|
||||
arr[i * 3 + 2] += v.z * 0.016;
|
||||
}
|
||||
attr.needsUpdate = true;
|
||||
mat.opacity = Math.min(1, elapsed / 2) * 0.9;
|
||||
if (allDead) {
|
||||
points.removeFromParent();
|
||||
geom.dispose();
|
||||
mat.dispose();
|
||||
tex.dispose();
|
||||
return;
|
||||
}
|
||||
requestAnimationFrame(update);
|
||||
};
|
||||
update();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,585 @@
|
||||
import * as THREE from 'three';
|
||||
import { Player } from './Player';
|
||||
import { Enemy, initSpiderModel } from './Enemy';
|
||||
import { Sword } from './Sword';
|
||||
import { Staff } from './Staff';
|
||||
import { Fireball } from './Fireball';
|
||||
import { Cross, initCrossModel } from './Cross';
|
||||
import { Arena } from './Arena';
|
||||
import { initSounds, playSound, resumeContext } from './SoundManager';
|
||||
|
||||
const SPAWN_TIME = 10;
|
||||
|
||||
export class Game {
|
||||
scene: THREE.Scene;
|
||||
camera: THREE.PerspectiveCamera;
|
||||
renderer: THREE.WebGLRenderer;
|
||||
clock: THREE.Clock;
|
||||
|
||||
player!: Player;
|
||||
enemies: Enemy[] = [];
|
||||
fireballs: Fireball[] = [];
|
||||
crosses: Cross[] = [];
|
||||
|
||||
keys: Set<string> = new Set();
|
||||
mouseButtons: Set<number> = new Set();
|
||||
private prevMouseButtons: Set<number> = new Set();
|
||||
mouseX = 0;
|
||||
mouseY = 0;
|
||||
groundPoint = new THREE.Vector3();
|
||||
|
||||
spawnTimer = 0;
|
||||
spawns = 0;
|
||||
killed = 0;
|
||||
gameOver = false;
|
||||
|
||||
// Debug hitzone visualization
|
||||
private showHitzones = false;
|
||||
private playerHitRing!: THREE.Mesh;
|
||||
private enemyHitRings: Map<Enemy, THREE.Mesh> = new Map();
|
||||
private fireballHitRings: Map<Fireball, THREE.Mesh> = new Map();
|
||||
private crossHitRings: Map<Cross, THREE.Mesh> = new Map();
|
||||
|
||||
// UI elements
|
||||
private uiHealthBar!: HTMLElement;
|
||||
private uiHealthText!: HTMLElement;
|
||||
private uiWaveLabel!: HTMLElement;
|
||||
private uiKillsLabel!: HTMLElement;
|
||||
private uiFireballCD!: HTMLElement;
|
||||
private uiSwordCD!: HTMLElement;
|
||||
private uiTeleportCD!: HTMLElement;
|
||||
private uiWaveBarFill!: HTMLElement;
|
||||
private uiWaveBarLabel!: HTMLElement;
|
||||
private uiGameOver!: HTMLElement;
|
||||
private mouseOnCanvas = false;
|
||||
|
||||
constructor() {
|
||||
this.scene = new THREE.Scene();
|
||||
this.scene.background = new THREE.Color(0x0d1117);
|
||||
this.scene.fog = new THREE.Fog(0x0d1117, 25, 70);
|
||||
|
||||
this.camera = new THREE.PerspectiveCamera(
|
||||
60, window.innerWidth / window.innerHeight, 1, 150
|
||||
);
|
||||
this.camera.position.set(0, 25, -15);
|
||||
this.camera.lookAt(0, 0, 0);
|
||||
|
||||
this.renderer = new THREE.WebGLRenderer({ antialias: true });
|
||||
this.renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
this.renderer.shadowMap.enabled = true;
|
||||
this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
||||
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
||||
document.body.prepend(this.renderer.domElement);
|
||||
|
||||
this.clock = new THREE.Clock();
|
||||
|
||||
this.setupUI();
|
||||
this.setupInput();
|
||||
this.setupLighting();
|
||||
}
|
||||
|
||||
async init() {
|
||||
this.setupArena();
|
||||
this.setupPlayer();
|
||||
this.player.setGame(this);
|
||||
|
||||
initSounds();
|
||||
|
||||
await Promise.all([
|
||||
this.player.init(),
|
||||
this.player.rightHandWeapon.init(),
|
||||
this.player.leftHandWeapon.init(),
|
||||
initSpiderModel(),
|
||||
initCrossModel(),
|
||||
]);
|
||||
}
|
||||
|
||||
start() {
|
||||
window.addEventListener('resize', () => {
|
||||
this.camera.aspect = window.innerWidth / window.innerHeight;
|
||||
this.camera.updateProjectionMatrix();
|
||||
this.renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
});
|
||||
|
||||
this.animate();
|
||||
}
|
||||
|
||||
private setupUI() {
|
||||
this.uiHealthBar = document.getElementById('health-bar')!;
|
||||
this.uiHealthText = document.getElementById('health-text')!;
|
||||
this.uiWaveLabel = document.getElementById('wave-label')!;
|
||||
this.uiKillsLabel = document.getElementById('kills-label')!;
|
||||
this.uiFireballCD = document.querySelector('#fireball-cooldown .cooldown-fill')!;
|
||||
this.uiSwordCD = document.querySelector('#sword-cooldown .cooldown-fill')!;
|
||||
this.uiTeleportCD = document.querySelector('#teleport-cooldown .cooldown-fill')!;
|
||||
this.uiWaveBarFill = document.getElementById('wave-bar-fill')!;
|
||||
this.uiWaveBarLabel = document.getElementById('wave-bar-label')!;
|
||||
this.uiGameOver = document.getElementById('game-over')!;
|
||||
|
||||
const canvas = this.renderer.domElement;
|
||||
canvas.addEventListener('mouseenter', () => this.mouseOnCanvas = true);
|
||||
canvas.addEventListener('mouseleave', () => this.mouseOnCanvas = false);
|
||||
canvas.addEventListener('contextmenu', e => e.preventDefault());
|
||||
}
|
||||
|
||||
private setupInput() {
|
||||
window.addEventListener('keydown', (e) => {
|
||||
this.keys.add(e.code);
|
||||
if (e.code === 'Space') {
|
||||
this.player.jump();
|
||||
e.preventDefault();
|
||||
}
|
||||
if (e.code === 'KeyH') {
|
||||
this.showHitzones = !this.showHitzones;
|
||||
}
|
||||
});
|
||||
window.addEventListener('keyup', (e) => {
|
||||
this.keys.delete(e.code);
|
||||
if (e.code === 'KeyE' && !this.gameOver) {
|
||||
this.player.leftHandWeapon.skill2(this.groundPoint, this.player);
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('mousedown', (e) => {
|
||||
resumeContext();
|
||||
this.mouseButtons.add(e.button);
|
||||
this.prevMouseButtons.add(e.button);
|
||||
});
|
||||
window.addEventListener('mouseup', (e) => {
|
||||
resumeContext();
|
||||
this.mouseButtons.delete(e.button);
|
||||
this.handleMouseRelease(e.button);
|
||||
});
|
||||
window.addEventListener('mousemove', (e) => {
|
||||
this.mouseX = (e.clientX / window.innerWidth) * 2 - 1;
|
||||
this.mouseY = -(e.clientY / window.innerHeight) * 2 + 1;
|
||||
});
|
||||
}
|
||||
|
||||
private handleMouseRelease(button: number) {
|
||||
if (this.gameOver) return;
|
||||
if (button === 0) {
|
||||
this.player.leftHandWeapon.skill1(this.groundPoint, this.player);
|
||||
} else if (button === 1) {
|
||||
this.player.switchHands();
|
||||
} else if (button === 2) {
|
||||
this.player.rightHandWeapon.skill1(this.groundPoint, this.player);
|
||||
}
|
||||
}
|
||||
|
||||
private setupLighting() {
|
||||
const ambient = new THREE.AmbientLight(0x556688, 0.35);
|
||||
this.scene.add(ambient);
|
||||
|
||||
const dirLight = new THREE.DirectionalLight(0xccddff, 0.55);
|
||||
dirLight.position.set(30, 40, 20);
|
||||
dirLight.castShadow = true;
|
||||
dirLight.shadow.mapSize.width = 1024;
|
||||
dirLight.shadow.mapSize.height = 1024;
|
||||
dirLight.shadow.camera.near = 1;
|
||||
dirLight.shadow.camera.far = 150;
|
||||
dirLight.shadow.camera.left = -60;
|
||||
dirLight.shadow.camera.right = 60;
|
||||
dirLight.shadow.camera.top = 60;
|
||||
dirLight.shadow.camera.bottom = -60;
|
||||
this.scene.add(dirLight);
|
||||
}
|
||||
|
||||
private setupArena() {
|
||||
const arena = new Arena();
|
||||
this.scene.add(arena);
|
||||
}
|
||||
|
||||
private setupPlayer() {
|
||||
this.player = new Player();
|
||||
this.player.body.position.set(0, 0.5, 0);
|
||||
this.scene.add(this.player.body);
|
||||
|
||||
// Player hitzone ring (contact damage radius = 2)
|
||||
this.playerHitRing = this.createHitRing(2, 0xff0000);
|
||||
this.scene.add(this.playerHitRing);
|
||||
}
|
||||
|
||||
private createHitRing(radius: number, color: number): THREE.Mesh {
|
||||
const geom = new THREE.RingGeometry(radius - 0.05, radius, 48);
|
||||
const mat = new THREE.MeshBasicMaterial({
|
||||
color,
|
||||
transparent: true,
|
||||
opacity: 0.5,
|
||||
side: THREE.DoubleSide,
|
||||
depthWrite: false,
|
||||
});
|
||||
const ring = new THREE.Mesh(geom, mat);
|
||||
ring.rotation.x = -Math.PI / 2;
|
||||
ring.position.y = 0.05;
|
||||
ring.renderOrder = 999;
|
||||
return ring;
|
||||
}
|
||||
|
||||
private updateHitzones() {
|
||||
this.playerHitRing.position.x = this.player.body.position.x;
|
||||
this.playerHitRing.position.z = this.player.body.position.z;
|
||||
this.playerHitRing.visible = this.showHitzones;
|
||||
|
||||
// Enemy rings
|
||||
for (const enemy of this.enemies) {
|
||||
let ring = this.enemyHitRings.get(enemy);
|
||||
if (!ring) {
|
||||
ring = this.createHitRing(2, 0xff8800);
|
||||
this.scene.add(ring);
|
||||
this.enemyHitRings.set(enemy, ring);
|
||||
}
|
||||
ring.position.x = enemy.body.position.x;
|
||||
ring.position.z = enemy.body.position.z;
|
||||
ring.visible = this.showHitzones && !enemy.dead;
|
||||
}
|
||||
// Clean up removed enemies
|
||||
for (const [enemy, ring] of this.enemyHitRings) {
|
||||
if (!this.enemies.includes(enemy)) {
|
||||
this.scene.remove(ring);
|
||||
ring.geometry.dispose();
|
||||
(ring.material as THREE.Material).dispose();
|
||||
this.enemyHitRings.delete(enemy);
|
||||
}
|
||||
}
|
||||
|
||||
// Fireball rings (impact radius 1.5 / AoE 5)
|
||||
for (const fb of this.fireballs) {
|
||||
let ring = this.fireballHitRings.get(fb);
|
||||
if (!ring) {
|
||||
ring = this.createHitRing(fb.exploding ? 5 : 1.5, fb.exploding ? 0xffff00 : 0xff6600);
|
||||
this.scene.add(ring);
|
||||
this.fireballHitRings.set(fb, ring);
|
||||
}
|
||||
ring.position.x = fb.body.position.x;
|
||||
ring.position.z = fb.body.position.z;
|
||||
ring.visible = this.showHitzones;
|
||||
}
|
||||
for (const [fb, ring] of this.fireballHitRings) {
|
||||
if (!this.fireballs.includes(fb)) {
|
||||
this.scene.remove(ring);
|
||||
ring.geometry.dispose();
|
||||
(ring.material as THREE.Material).dispose();
|
||||
this.fireballHitRings.delete(fb);
|
||||
}
|
||||
}
|
||||
|
||||
// Cross rings (pickup radius 2)
|
||||
for (const cross of this.crosses) {
|
||||
let ring = this.crossHitRings.get(cross);
|
||||
if (!ring) {
|
||||
ring = this.createHitRing(2, 0x00ff00);
|
||||
this.scene.add(ring);
|
||||
this.crossHitRings.set(cross, ring);
|
||||
}
|
||||
ring.position.x = cross.body.position.x;
|
||||
ring.position.z = cross.body.position.z;
|
||||
ring.visible = this.showHitzones;
|
||||
}
|
||||
for (const [cross, ring] of this.crossHitRings) {
|
||||
if (!this.crosses.includes(cross)) {
|
||||
this.scene.remove(ring);
|
||||
ring.geometry.dispose();
|
||||
(ring.material as THREE.Material).dispose();
|
||||
this.crossHitRings.delete(cross);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private spawnEnemy() {
|
||||
const x = (Math.random() - 0.5) * 80;
|
||||
const z = (Math.random() - 0.5) * 80;
|
||||
const enemy = new Enemy(this.player.body.position);
|
||||
enemy.body.position.set(x, 0.1, z);
|
||||
this.scene.add(enemy.body);
|
||||
this.enemies.push(enemy);
|
||||
}
|
||||
|
||||
private spawnCross(position: THREE.Vector3) {
|
||||
if (Math.random() > 0.9) {
|
||||
const cross = new Cross(position.clone());
|
||||
this.scene.add(cross.body);
|
||||
this.crosses.push(cross);
|
||||
}
|
||||
}
|
||||
|
||||
private updateGroundPoint() {
|
||||
const raycaster = new THREE.Raycaster();
|
||||
raycaster.setFromCamera(new THREE.Vector2(this.mouseX, this.mouseY), this.camera);
|
||||
const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0);
|
||||
const intersection = new THREE.Vector3();
|
||||
if (raycaster.ray.intersectPlane(plane, intersection)) {
|
||||
intersection.y = 0;
|
||||
intersection.clamp(
|
||||
new THREE.Vector3(-48, 0, -48),
|
||||
new THREE.Vector3(48, 0, 48)
|
||||
);
|
||||
this.groundPoint.copy(intersection);
|
||||
}
|
||||
}
|
||||
|
||||
private updatePlayerMovement(dt: number) {
|
||||
if (this.gameOver) {
|
||||
this.player.body.position.y = 0.5;
|
||||
return;
|
||||
}
|
||||
|
||||
const camFwd = new THREE.Vector3();
|
||||
this.camera.getWorldDirection(camFwd);
|
||||
camFwd.y = 0;
|
||||
camFwd.normalize();
|
||||
|
||||
const camLeft = new THREE.Vector3();
|
||||
camLeft.crossVectors(new THREE.Vector3(0, 1, 0), camFwd).normalize();
|
||||
|
||||
const moveDir = new THREE.Vector3();
|
||||
if (this.keys.has('KeyW') || this.keys.has('ArrowUp')) moveDir.add(camFwd);
|
||||
if (this.keys.has('KeyS') || this.keys.has('ArrowDown')) moveDir.sub(camFwd);
|
||||
if (this.keys.has('KeyA') || this.keys.has('ArrowLeft')) moveDir.add(camLeft);
|
||||
if (this.keys.has('KeyD') || this.keys.has('ArrowRight')) moveDir.sub(camLeft);
|
||||
|
||||
let speed = 7.5;
|
||||
let isMoving = false;
|
||||
if (moveDir.length() > 0) {
|
||||
moveDir.normalize();
|
||||
this.player.velocity.set(moveDir.x * speed, 0, moveDir.z * speed);
|
||||
isMoving = true;
|
||||
} else {
|
||||
this.player.velocity.set(0, 0, 0);
|
||||
}
|
||||
|
||||
// Play walk/stand animations
|
||||
if (isMoving && this.player.getCurrentAnimation() !== 'walk') {
|
||||
this.player.playAnimation('walk');
|
||||
} else if (!isMoving && this.player.getCurrentAnimation() !== 'stand') {
|
||||
this.player.playAnimation('stand');
|
||||
}
|
||||
|
||||
// Jump / gravity
|
||||
if (!this.player.onGround) {
|
||||
this.player.jumpVelocity -= 20 * dt;
|
||||
this.player.body.position.y += this.player.jumpVelocity * dt;
|
||||
if (this.player.body.position.y <= 0.5) {
|
||||
this.player.body.position.y = 0.5;
|
||||
this.player.jumpVelocity = 0;
|
||||
this.player.onGround = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply horizontal movement
|
||||
this.player.body.position.x += this.player.velocity.x * dt;
|
||||
this.player.body.position.z += this.player.velocity.z * dt;
|
||||
this.clampToArena(this.player.body.position);
|
||||
|
||||
// Look at ground point
|
||||
this.player.body.lookAt(
|
||||
this.groundPoint.x,
|
||||
this.player.body.position.y,
|
||||
this.groundPoint.z
|
||||
);
|
||||
}
|
||||
|
||||
private clampToArena(pos: THREE.Vector3) {
|
||||
pos.x = Math.max(-47, Math.min(47, pos.x));
|
||||
pos.z = Math.max(-47, Math.min(47, pos.z));
|
||||
}
|
||||
|
||||
private updateEnemies(dt: number) {
|
||||
for (const enemy of this.enemies) {
|
||||
if (enemy.dead) {
|
||||
enemy.updateAnimation(dt);
|
||||
continue;
|
||||
}
|
||||
|
||||
const toPlayer = new THREE.Vector3()
|
||||
.subVectors(this.player.body.position, enemy.body.position);
|
||||
toPlayer.y = 0;
|
||||
|
||||
const dist = toPlayer.length();
|
||||
if (dist > 0.1) {
|
||||
const dir = toPlayer.normalize();
|
||||
enemy.body.lookAt(
|
||||
enemy.body.position.x + dir.x,
|
||||
enemy.body.position.y,
|
||||
enemy.body.position.z + dir.z
|
||||
);
|
||||
|
||||
enemy.playAnim('walk');
|
||||
|
||||
let moveDir = dir.multiplyScalar(6);
|
||||
if (enemy.knockback > 0) {
|
||||
moveDir = dir.multiplyScalar(-6);
|
||||
enemy.knockback -= dt;
|
||||
}
|
||||
enemy.body.position.x += moveDir.x * dt;
|
||||
enemy.body.position.z += moveDir.z * dt;
|
||||
this.clampToArena(enemy.body.position);
|
||||
} else {
|
||||
enemy.playAnim('stand');
|
||||
}
|
||||
|
||||
enemy.updateAnimation(dt);
|
||||
}
|
||||
}
|
||||
|
||||
private updateFireballs(dt: number) {
|
||||
for (let i = this.fireballs.length - 1; i >= 0; i--) {
|
||||
const fb = this.fireballs[i];
|
||||
fb.lifetime -= dt;
|
||||
|
||||
if (fb.exploding) {
|
||||
fb.timer += dt;
|
||||
if (fb.timer > 0.2 && !fb.damageDone) {
|
||||
fb.damageDone = true;
|
||||
for (const enemy of this.enemies) {
|
||||
if (enemy.dead) continue;
|
||||
const dist = enemy.body.position.distanceTo(fb.body.position);
|
||||
if (dist < 8) {
|
||||
enemy.takeDamage(100, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fb.timer > 2) {
|
||||
this.scene.remove(fb.body);
|
||||
this.fireballs.splice(i, 1);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
fb.body.position.x += fb.direction.x * dt;
|
||||
fb.body.position.z += fb.direction.z * dt;
|
||||
fb.updateTrail(dt, fb.body.position);
|
||||
|
||||
for (const enemy of this.enemies) {
|
||||
if (enemy.dead) continue;
|
||||
const dist = enemy.body.position.distanceTo(fb.body.position);
|
||||
if (dist < 1.8) {
|
||||
fb.explode(this.scene);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (fb.lifetime <= 0) {
|
||||
fb.explode(this.scene);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private updateUI() {
|
||||
const healthPct = this.player.health / this.player.MAX_HEALTH;
|
||||
this.uiHealthBar.style.width = `${Math.max(0, healthPct * 250)}px`;
|
||||
this.uiHealthText.textContent = `${Math.ceil(this.player.health)}%`;
|
||||
|
||||
this.uiWaveLabel.textContent = `Wave ${this.spawns}`;
|
||||
this.uiKillsLabel.textContent = `Kills: ${this.killed}`;
|
||||
|
||||
const sword = this.player.rightHandWeapon as Sword;
|
||||
const staff = this.player.leftHandWeapon as Staff;
|
||||
|
||||
const fbPct = Math.max(0, (staff.COOLDOWN1_TIME - staff.cooldown1) / staff.COOLDOWN1_TIME);
|
||||
this.uiFireballCD.style.width = `${fbPct * 100}%`;
|
||||
|
||||
const swordPct = Math.max(0, (sword.COOLDOWN1_TIME - sword.cooldown1) / sword.COOLDOWN1_TIME);
|
||||
this.uiSwordCD.style.width = `${swordPct * 100}%`;
|
||||
|
||||
const tpPct = Math.max(0, (staff.COOLDOWN2_TIME - staff.cooldown2) / staff.COOLDOWN2_TIME);
|
||||
this.uiTeleportCD.style.width = `${tpPct * 100}%`;
|
||||
|
||||
const wavePct = this.spawnTimer / SPAWN_TIME;
|
||||
this.uiWaveBarFill.style.width = `${wavePct * 100}%`;
|
||||
this.uiWaveBarLabel.textContent = `Next Wave: ${Math.ceil(SPAWN_TIME - this.spawnTimer)}s`;
|
||||
|
||||
if (this.player.health <= 0 && !this.gameOver) {
|
||||
this.gameOver = true;
|
||||
this.player.die();
|
||||
this.uiGameOver.style.opacity = '1';
|
||||
playSound('gameover', 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
private animate() {
|
||||
requestAnimationFrame(() => this.animate());
|
||||
|
||||
const dt = Math.min(this.clock.getDelta(), 0.1);
|
||||
|
||||
this.updateGroundPoint();
|
||||
this.updatePlayerMovement(dt);
|
||||
this.updateEnemies(dt);
|
||||
this.updateFireballs(dt);
|
||||
this.updateHitzones();
|
||||
|
||||
// Update animations
|
||||
this.player.updateAnimations(dt);
|
||||
this.player.rightHandWeapon.updateAnimation(dt);
|
||||
|
||||
// Spawn enemies in waves
|
||||
if (!this.gameOver) {
|
||||
this.spawnTimer += dt;
|
||||
if (this.spawnTimer >= SPAWN_TIME) {
|
||||
this.spawns++;
|
||||
for (let i = 0; i < this.spawns; i++) {
|
||||
this.spawnEnemy();
|
||||
}
|
||||
this.spawnTimer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Check close encounters (spider touching player)
|
||||
for (const enemy of this.enemies) {
|
||||
if (enemy.dead) continue;
|
||||
const dist = enemy.body.position.distanceTo(this.player.body.position);
|
||||
if (dist < 2) {
|
||||
this.player.health -= 25 * dt;
|
||||
}
|
||||
}
|
||||
|
||||
// Cross collisions
|
||||
for (let i = this.crosses.length - 1; i >= 0; i--) {
|
||||
const cross = this.crosses[i];
|
||||
cross.update(dt);
|
||||
const dist = cross.body.position.distanceTo(this.player.body.position);
|
||||
if (dist < 2) {
|
||||
this.player.heal(25);
|
||||
this.scene.remove(cross.body);
|
||||
this.crosses.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Update cooldowns
|
||||
this.player.rightHandWeapon.reduceCooldowns(dt);
|
||||
this.player.leftHandWeapon.reduceCooldowns(dt);
|
||||
|
||||
// Camera follows player
|
||||
this.camera.position.set(
|
||||
this.player.body.position.x,
|
||||
25,
|
||||
this.player.body.position.z - 15
|
||||
);
|
||||
this.camera.lookAt(
|
||||
this.player.body.position.x,
|
||||
0,
|
||||
this.player.body.position.z
|
||||
);
|
||||
|
||||
// Clean dead enemies
|
||||
for (let i = this.enemies.length - 1; i >= 0; i--) {
|
||||
const enemy = this.enemies[i];
|
||||
if (enemy.dead) {
|
||||
enemy.deathTimer += dt;
|
||||
if (enemy.deathTimer > 10) {
|
||||
this.scene.remove(enemy.body);
|
||||
this.enemies.splice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.updateUI();
|
||||
this.renderer.render(this.scene, this.camera);
|
||||
}
|
||||
|
||||
removeEnemy(enemy: Enemy) {
|
||||
enemy.dead = true;
|
||||
this.killed++;
|
||||
this.spawnCross(enemy.body.position);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import * as THREE from 'three';
|
||||
import { GLTFLoader, type GLTF } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
||||
|
||||
const loader = new GLTFLoader();
|
||||
|
||||
export async function loadGLB(name: string): Promise<GLTF> {
|
||||
return new Promise((resolve, reject) => {
|
||||
loader.load(
|
||||
`./models/${name}.glb`,
|
||||
(gltf) => resolve(gltf),
|
||||
undefined,
|
||||
(err) => reject(err)
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
import * as THREE from 'three';
|
||||
import { Sword } from './Sword';
|
||||
import { Staff } from './Staff';
|
||||
import { loadGLB } from './MeshLoader';
|
||||
import type { Game } from './Game';
|
||||
|
||||
export class Player {
|
||||
body: THREE.Group;
|
||||
leftHandWeapon: Staff;
|
||||
rightHandWeapon: Sword;
|
||||
velocity = new THREE.Vector3();
|
||||
health = 100;
|
||||
MAX_HEALTH = 100;
|
||||
game!: Game;
|
||||
jumpVelocity = 0;
|
||||
onGround = true;
|
||||
mixer!: THREE.AnimationMixer;
|
||||
private clips: Map<string, THREE.AnimationAction> = new Map();
|
||||
private currentAnim = '';
|
||||
|
||||
constructor() {
|
||||
this.body = new THREE.Group();
|
||||
|
||||
// Helmet
|
||||
const helmetGeom = new THREE.SphereGeometry(0.45, 8, 8, 0, Math.PI * 2, 0, Math.PI / 2);
|
||||
const helmetMat = new THREE.MeshToonMaterial({ color: 0x888888 });
|
||||
const helmet = new THREE.Mesh(helmetGeom, helmetMat);
|
||||
helmet.position.y = 0.9;
|
||||
helmet.castShadow = true;
|
||||
this.body.add(helmet);
|
||||
|
||||
// Try loading the real helmet model (replaces the placeholder sphere)
|
||||
loadGLB('helmet').then((gltf) => {
|
||||
const realHelmet = gltf.scene;
|
||||
const helmetTexture = new THREE.TextureLoader().load('./textures/helmet.png');
|
||||
helmetTexture.flipY = false;
|
||||
helmetTexture.colorSpace = THREE.SRGBColorSpace;
|
||||
realHelmet.traverse((child) => {
|
||||
if (child instanceof THREE.Mesh) {
|
||||
child.castShadow = true;
|
||||
child.material = new THREE.MeshToonMaterial({ map: helmetTexture });
|
||||
}
|
||||
});
|
||||
realHelmet.scale.set(0.35, 0.35, 0.35);
|
||||
realHelmet.position.set(0, 0.9, -0.2);
|
||||
realHelmet.rotation.set(-0.4, 0, 0);
|
||||
this.body.remove(helmet);
|
||||
this.body.add(realHelmet);
|
||||
}).catch(() => {
|
||||
// Keep placeholder helmet
|
||||
});
|
||||
|
||||
// Shadow disc
|
||||
const shadowGeom = new THREE.CircleGeometry(0.4, 16);
|
||||
const shadowMat = new THREE.MeshBasicMaterial({
|
||||
color: 0x000000, transparent: true, opacity: 0.3,
|
||||
});
|
||||
const shadow = new THREE.Mesh(shadowGeom, shadowMat);
|
||||
shadow.rotation.x = -Math.PI / 2;
|
||||
shadow.position.y = -0.48;
|
||||
shadow.renderOrder = 999;
|
||||
this.body.add(shadow);
|
||||
|
||||
// Weapons
|
||||
this.leftHandWeapon = new Staff();
|
||||
this.rightHandWeapon = new Sword();
|
||||
this.body.add(this.leftHandWeapon.mesh);
|
||||
this.body.add(this.rightHandWeapon.mesh);
|
||||
}
|
||||
|
||||
async init() {
|
||||
try {
|
||||
const gltf = await loadGLB('blob');
|
||||
const model = gltf.scene;
|
||||
model.scale.set(0.5, 0.5, 0.5);
|
||||
model.traverse((child) => {
|
||||
if (child instanceof THREE.Mesh) {
|
||||
child.castShadow = true;
|
||||
child.receiveShadow = true;
|
||||
const mat = new THREE.MeshToonMaterial({
|
||||
color: 0x44aa44,
|
||||
});
|
||||
child.material = mat;
|
||||
}
|
||||
});
|
||||
this.body.add(model);
|
||||
|
||||
if (gltf.animations.length > 0) {
|
||||
this.mixer = new THREE.AnimationMixer(model);
|
||||
for (const clip of gltf.animations) {
|
||||
const action = this.mixer.clipAction(clip);
|
||||
this.clips.set(clip.name, action);
|
||||
}
|
||||
this.playAnimation('stand');
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failed to load blob model, using fallback', e);
|
||||
// Fallback capsule
|
||||
const bodyGeom = new THREE.CapsuleGeometry(0.5, 0.5, 8, 16);
|
||||
const bodyMat = new THREE.MeshToonMaterial({ color: 0x44aa44 });
|
||||
const bodyMesh = new THREE.Mesh(bodyGeom, bodyMat);
|
||||
bodyMesh.position.y = 0.75;
|
||||
bodyMesh.castShadow = true;
|
||||
this.body.add(bodyMesh);
|
||||
}
|
||||
}
|
||||
|
||||
playAnimation(name: string, loop = true, speed = 1) {
|
||||
if (!this.clips.has(name)) return;
|
||||
if (this.currentAnim === name) return;
|
||||
// Stop the previous animation so it doesn't blend with the new one
|
||||
if (this.currentAnim) {
|
||||
const prev = this.clips.get(this.currentAnim);
|
||||
if (prev) prev.stop();
|
||||
}
|
||||
this.currentAnim = name;
|
||||
const action = this.clips.get(name)!;
|
||||
action.reset();
|
||||
action.setLoop(loop ? THREE.LoopRepeat : THREE.LoopOnce, loop ? Infinity : 1);
|
||||
action.clampWhenFinished = !loop;
|
||||
action.setEffectiveTimeScale(speed);
|
||||
action.weight = 1;
|
||||
action.play();
|
||||
return action;
|
||||
}
|
||||
|
||||
stopAnimation(name: string) {
|
||||
const action = this.clips.get(name);
|
||||
if (action) action.stop();
|
||||
}
|
||||
|
||||
getCurrentAnimation(): string {
|
||||
return this.currentAnim;
|
||||
}
|
||||
|
||||
updateAnimations(dt: number) {
|
||||
if (this.mixer) this.mixer.update(dt);
|
||||
}
|
||||
|
||||
setGame(game: Game) {
|
||||
this.game = game;
|
||||
}
|
||||
|
||||
jump() {
|
||||
if (this.onGround) {
|
||||
this.jumpVelocity = 8;
|
||||
this.onGround = false;
|
||||
this.playAnimation('jump', false);
|
||||
}
|
||||
}
|
||||
|
||||
switchHands() {
|
||||
const temp = this.leftHandWeapon;
|
||||
this.leftHandWeapon = this.rightHandWeapon as unknown as Staff;
|
||||
this.rightHandWeapon = temp as unknown as Sword;
|
||||
|
||||
const leftPos = this.leftHandWeapon.mesh.position.clone();
|
||||
const rightPos = this.rightHandWeapon.mesh.position.clone();
|
||||
this.leftHandWeapon.mesh.position.copy(rightPos);
|
||||
this.rightHandWeapon.mesh.position.copy(leftPos);
|
||||
|
||||
const leftRot = this.leftHandWeapon.mesh.rotation.clone();
|
||||
const rightRot = this.rightHandWeapon.mesh.rotation.clone();
|
||||
this.leftHandWeapon.mesh.rotation.copy(rightRot);
|
||||
this.rightHandWeapon.mesh.rotation.copy(leftRot);
|
||||
}
|
||||
|
||||
heal(amount: number) {
|
||||
this.health = Math.min(this.health + amount, this.MAX_HEALTH);
|
||||
}
|
||||
|
||||
die() {
|
||||
this.playAnimation('die', false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
const audioCtx = new (window.AudioContext || (window as any).webkitAudioContext)();
|
||||
|
||||
const sounds: Record<string, AudioBuffer> = {};
|
||||
|
||||
async function loadSound(name: string): Promise<AudioBuffer> {
|
||||
const response = await fetch(`./sounds/${name}.ogg`);
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
return audioCtx.decodeAudioData(arrayBuffer);
|
||||
}
|
||||
|
||||
export async function initSounds() {
|
||||
const names = ['fireball', 'explosion', 'sword_hit1', 'teleport', 'damage', 'spiderwalking', 'spawn', 'gameover'];
|
||||
for (const name of names) {
|
||||
try {
|
||||
sounds[name] = await loadSound(name);
|
||||
} catch {
|
||||
console.warn(`Sound ${name} not found`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function resumeContext() {
|
||||
if (audioCtx.state === 'suspended') {
|
||||
audioCtx.resume();
|
||||
}
|
||||
}
|
||||
|
||||
export function playSound(name: string, volume = 1) {
|
||||
const buffer = sounds[name];
|
||||
if (!buffer) return;
|
||||
if (audioCtx.state === 'suspended') return;
|
||||
const source = audioCtx.createBufferSource();
|
||||
source.buffer = buffer;
|
||||
const gain = audioCtx.createGain();
|
||||
gain.gain.value = volume;
|
||||
source.connect(gain);
|
||||
gain.connect(audioCtx.destination);
|
||||
source.start(0);
|
||||
}
|
||||
|
||||
export function playLoopingSound(name: string, volume = 1): (() => void) | null {
|
||||
const buffer = sounds[name];
|
||||
if (!buffer) return null;
|
||||
if (audioCtx.state === 'suspended') return null;
|
||||
const source = audioCtx.createBufferSource();
|
||||
source.buffer = buffer;
|
||||
source.loop = true;
|
||||
const gain = audioCtx.createGain();
|
||||
gain.gain.value = volume;
|
||||
source.connect(gain);
|
||||
gain.connect(audioCtx.destination);
|
||||
source.start(0);
|
||||
return () => {
|
||||
try {
|
||||
source.stop();
|
||||
} catch {
|
||||
// already stopped
|
||||
}
|
||||
source.disconnect();
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import * as THREE from 'three';
|
||||
import { Weapon } from './Weapon';
|
||||
import { Player } from './Player';
|
||||
import { Fireball } from './Fireball';
|
||||
import { playSound } from './SoundManager';
|
||||
import { loadGLB } from './MeshLoader';
|
||||
|
||||
export class Staff extends Weapon {
|
||||
constructor() {
|
||||
super();
|
||||
this.COOLDOWN1_TIME = 5;
|
||||
this.COOLDOWN2_TIME = 50;
|
||||
|
||||
// Fallback geometry
|
||||
const shaftGeom = new THREE.CylinderGeometry(0.04, 0.05, 1.0, 8);
|
||||
const crystalGeom = new THREE.OctahedronGeometry(0.08);
|
||||
const woodMat = new THREE.MeshToonMaterial({ color: 0x8B4513 });
|
||||
const crystalMat = new THREE.MeshToonMaterial({ color: 0x4488ff });
|
||||
const shaft = new THREE.Mesh(shaftGeom, woodMat);
|
||||
shaft.position.y = 0.3;
|
||||
shaft.castShadow = true;
|
||||
const crystal = new THREE.Mesh(crystalGeom, crystalMat);
|
||||
crystal.position.y = 0.9;
|
||||
this.mesh.add(shaft);
|
||||
this.mesh.add(crystal);
|
||||
|
||||
this.mesh.position.set(0.35, 0.3, 0.35);
|
||||
this.mesh.scale.set(0.35, 0.35, 0.35);
|
||||
this.mesh.rotation.set(-0.2, 0, -0.2);
|
||||
}
|
||||
|
||||
async init() {
|
||||
try {
|
||||
const gltf = await loadGLB('staff');
|
||||
const model = gltf.scene;
|
||||
const staffTexture = new THREE.TextureLoader().load('./textures/staff1.png');
|
||||
staffTexture.flipY = false;
|
||||
staffTexture.colorSpace = THREE.SRGBColorSpace;
|
||||
model.traverse((child) => {
|
||||
if (child instanceof THREE.Mesh) {
|
||||
child.castShadow = true;
|
||||
child.material = new THREE.MeshToonMaterial({ map: staffTexture });
|
||||
}
|
||||
});
|
||||
while (this.mesh.children.length > 0) {
|
||||
this.mesh.remove(this.mesh.children[0]);
|
||||
}
|
||||
this.mesh.add(model);
|
||||
} catch {
|
||||
// Keep fallback
|
||||
}
|
||||
}
|
||||
|
||||
skill1(groundPoint: THREE.Vector3, player: Player): void {
|
||||
if (this.cooldown1 > 0) return;
|
||||
this.cooldown1 = this.COOLDOWN1_TIME;
|
||||
|
||||
// Spawn at staff height, fly toward the cursor point
|
||||
const spawnPos = player.body.position.clone();
|
||||
spawnPos.y += 0.8;
|
||||
|
||||
const dir = new THREE.Vector3()
|
||||
.subVectors(groundPoint, spawnPos)
|
||||
.normalize()
|
||||
.multiplyScalar(25);
|
||||
dir.setY(0);
|
||||
|
||||
const fb = new Fireball(spawnPos, dir);
|
||||
player.game.scene.add(fb.body);
|
||||
player.game.fireballs.push(fb);
|
||||
playSound('fireball', 0.5);
|
||||
}
|
||||
|
||||
skill2(groundPoint: THREE.Vector3, player: Player): void {
|
||||
if (this.cooldown2 > 0) return;
|
||||
this.cooldown2 = this.COOLDOWN2_TIME;
|
||||
|
||||
const newPos = groundPoint.clone();
|
||||
newPos.y = 0.5;
|
||||
newPos.x = Math.max(-47, Math.min(47, newPos.x));
|
||||
newPos.z = Math.max(-47, Math.min(47, newPos.z));
|
||||
player.body.position.copy(newPos);
|
||||
playSound('teleport', 0.5);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
import * as THREE from 'three';
|
||||
import { Weapon } from './Weapon';
|
||||
import { Player } from './Player';
|
||||
import { loadGLB } from './MeshLoader';
|
||||
import { playSound } from './SoundManager';
|
||||
|
||||
export class Sword extends Weapon {
|
||||
private mixer: THREE.AnimationMixer | null = null;
|
||||
private clips: Map<string, THREE.AnimationAction> = new Map();
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.COOLDOWN1_TIME = 0.5;
|
||||
|
||||
this.mesh.position.set(-0.35, 0.3, 0.35);
|
||||
this.mesh.scale.set(0.2, 0.2, 0.2);
|
||||
this.mesh.rotation.set(0.3, 0, 0.3);
|
||||
|
||||
// Fallback geometry
|
||||
const bladeGeom = new THREE.BoxGeometry(0.08, 1.0, 0.15);
|
||||
const handleGeom = new THREE.CylinderGeometry(0.05, 0.05, 0.2, 8);
|
||||
const guardGeom = new THREE.BoxGeometry(0.25, 0.06, 0.06);
|
||||
const metalMat = new THREE.MeshStandardMaterial({ color: 0xcccccc, metalness: 0.9, roughness: 0.3 });
|
||||
const darkMat = new THREE.MeshStandardMaterial({ color: 0x444444, roughness: 0.6 });
|
||||
|
||||
const blade = new THREE.Mesh(bladeGeom, metalMat);
|
||||
blade.position.y = 0.4;
|
||||
blade.castShadow = true;
|
||||
const guard = new THREE.Mesh(guardGeom, metalMat);
|
||||
guard.position.y = -0.1;
|
||||
const handle = new THREE.Mesh(handleGeom, darkMat);
|
||||
handle.position.y = -0.25;
|
||||
this.mesh.add(blade);
|
||||
this.mesh.add(guard);
|
||||
this.mesh.add(handle);
|
||||
this._fallback = true;
|
||||
}
|
||||
|
||||
async init() {
|
||||
try {
|
||||
const gltf = await loadGLB('sword');
|
||||
const model = gltf.scene;
|
||||
model.scale.set(1, 1, 1);
|
||||
const swordTexture = new THREE.TextureLoader().load('./textures/sword.png');
|
||||
swordTexture.flipY = false;
|
||||
swordTexture.colorSpace = THREE.SRGBColorSpace;
|
||||
model.traverse((child) => {
|
||||
if (child instanceof THREE.Mesh) {
|
||||
child.castShadow = true;
|
||||
child.material = new THREE.MeshToonMaterial({ map: swordTexture });
|
||||
}
|
||||
});
|
||||
|
||||
// Remove fallback geometry
|
||||
while (this.mesh.children.length > 0) {
|
||||
this.mesh.remove(this.mesh.children[0]);
|
||||
}
|
||||
this._fallback = false;
|
||||
this.mesh.add(model);
|
||||
|
||||
if (gltf.animations.length > 0) {
|
||||
this.mixer = new THREE.AnimationMixer(model);
|
||||
for (const clip of gltf.animations) {
|
||||
const action = this.mixer.clipAction(clip);
|
||||
this.clips.set(clip.name, action);
|
||||
}
|
||||
const normal = this.clips.get('normal');
|
||||
if (normal) {
|
||||
normal.play();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failed to load sword model', e);
|
||||
}
|
||||
}
|
||||
|
||||
private _fallback = false;
|
||||
|
||||
updateAnimation(dt: number) {
|
||||
if (this.mixer) this.mixer.update(dt);
|
||||
}
|
||||
|
||||
skill1(_groundPoint: THREE.Vector3, player: Player): void {
|
||||
if (this.cooldown1 > 0) return;
|
||||
this.cooldown1 = this.COOLDOWN1_TIME;
|
||||
|
||||
playSound('sword_hit1', 0.5);
|
||||
|
||||
// Play skill1 animation
|
||||
const action = this.clips.get('skill1');
|
||||
if (action) {
|
||||
action.reset();
|
||||
action.setLoop(THREE.LoopOnce, 1);
|
||||
action.setEffectiveTimeScale(2);
|
||||
action.play();
|
||||
}
|
||||
|
||||
const { game } = player;
|
||||
|
||||
for (const enemy of game.enemies) {
|
||||
if (enemy.dead) continue;
|
||||
const dist = enemy.body.position.distanceTo(player.body.position);
|
||||
if (dist < 3) {
|
||||
const toEnemy = new THREE.Vector3()
|
||||
.subVectors(enemy.body.position, player.body.position).normalize();
|
||||
const playerFwd = new THREE.Vector3(0, 0, 1);
|
||||
playerFwd.applyQuaternion(player.body.quaternion);
|
||||
const dot = playerFwd.dot(toEnemy);
|
||||
if (dot > 0.3) {
|
||||
enemy.takeDamage(50, game);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
skill2(): void {
|
||||
// Not implemented
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import * as THREE from 'three';
|
||||
import { Player } from './Player';
|
||||
|
||||
export abstract class Weapon {
|
||||
mesh: THREE.Group;
|
||||
cooldown1 = 0;
|
||||
cooldown2 = 0;
|
||||
cooldown3 = 0;
|
||||
COOLDOWN1_TIME = 0;
|
||||
COOLDOWN2_TIME = 0;
|
||||
COOLDOWN3_TIME = 0;
|
||||
|
||||
constructor() {
|
||||
this.mesh = new THREE.Group();
|
||||
}
|
||||
|
||||
reduceCooldowns(tpf: number) {
|
||||
if (this.cooldown1 > 0) this.cooldown1 -= tpf;
|
||||
if (this.cooldown2 > 0) this.cooldown2 -= tpf;
|
||||
if (this.cooldown3 > 0) this.cooldown3 -= tpf;
|
||||
}
|
||||
|
||||
abstract skill1(groundPoint: THREE.Vector3, player: Player): void;
|
||||
abstract skill2(groundPoint: THREE.Vector3, player: Player): void;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Game } from './Game';
|
||||
|
||||
async function main() {
|
||||
const game = new Game();
|
||||
await game.init();
|
||||
game.start();
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,8 @@
|
||||
declare module '*.glb' {
|
||||
const src: string;
|
||||
export default src;
|
||||
}
|
||||
declare module '*.gltf' {
|
||||
const src: string;
|
||||
export default src;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"declaration": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
export default defineConfig({
|
||||
base: './',
|
||||
build: {
|
||||
outDir: 'dist',
|
||||
assetsDir: 'assets',
|
||||
},
|
||||
server: {
|
||||
host: true,
|
||||
port: 3000,
|
||||
},
|
||||
});
|
||||