/*******************************************************************************
* Copyright 2015 See AUTHORS file.
* <p/>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p/>
* http://www.apache.org/licenses/LICENSE-2.0
* <p/>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.mygdx.game.scene;
import com.badlogic.gdx.graphics.Mesh;
import com.badlogic.gdx.graphics.g3d.Model;
import com.badlogic.gdx.graphics.g3d.ModelInstance;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.physics.bullet.Bullet;
import com.badlogic.gdx.physics.bullet.collision.*;
import com.badlogic.gdx.utils.*;
import com.mygdx.game.GameEngine;
import com.mygdx.game.blender.objects.BlenderEmpty;
import com.mygdx.game.blender.objects.BlenderModel;
import com.mygdx.game.blender.objects.BlenderObject;
import com.mygdx.game.objects.GameModel;
import java.nio.FloatBuffer;
/**
* @author jsjolund
*/
public class GameObjectBlueprint implements Disposable {
private static final Vector3 tmpScale = new Vector3();
public static String blenderCollisionShapeField = "collision_shape";
public static String blenderMassField = "mass";
public static String blenderPfxField = "pfx";
public String type;
public String name;
public Vector3 position;
public Vector3 rotation;
public Vector3 scale;
public boolean[] layers;
public ArrayMap<String, String> custom_properties;
public Model model;
public String shapeType;
public btCollisionShape shape;
public float mass;
public short belongsToFlag;
public short collidesWithFlag;
public boolean callback;
public boolean noDeactivate;
public Array<BlenderEmpty> ragdollEmpties;
public String armatureNodeId;
public Bits visibleOnLayers;
public String pfx;
public GameObjectBlueprint() {
}
public GameObjectBlueprint(BlenderModel blenderModel, Model model,
btCollisionShape shape, float mass, String shapeType) {
this.shape = shape;
this.mass = mass;
this.shapeType = shapeType;
this.model = model;
setFromObject(blenderModel);
setCollisionFlags(this.mass);
}
public GameObjectBlueprint(BlenderEmpty blenderEmpty) {
setFromObject(blenderEmpty);
if (blenderEmpty.custom_properties.containsKey(blenderPfxField)) {
pfx = blenderEmpty.custom_properties.get(blenderPfxField);
return;
}
if (blenderEmpty.custom_properties.containsKey(blenderCollisionShapeField)
&& blenderEmpty.custom_properties.containsKey(blenderMassField)) {
setRigidBody(blenderEmpty, null, blenderEmpty.scale);
return;
}
throw new GdxRuntimeException("Cannot load collision shape data from '" + blenderEmpty.name + "'");
}
public GameObjectBlueprint(BlenderModel blenderModel, Model model) {
this.model = model;
setFromObject(blenderModel);
this.mass = 0;
ModelInstance modelInstance = new ModelInstance(model);
GameModel.applyTransform(position, rotation, blenderModel.scale, modelInstance);
this.shape = Bullet.obtainStaticNodeShape(modelInstance.nodes);
this.shapeType = "static_node_shape_" + blenderModel.name;
setCollisionFlags(this.mass);
}
public GameObjectBlueprint(BlenderModel blenderModel, Model model, BlenderEmpty blenderEmpty) {
this.model = model;
setFromObject(blenderModel);
if (blenderEmpty.custom_properties.containsKey(blenderPfxField)) {
pfx = blenderEmpty.custom_properties.get(blenderPfxField);
return;
}
if (blenderEmpty.custom_properties.containsKey(blenderCollisionShapeField)
&& blenderEmpty.custom_properties.containsKey(blenderMassField)) {
setRigidBody(blenderEmpty, model, blenderModel.scale);
return;
}
throw new GdxRuntimeException("Cannot load collision shape data for " + blenderModel.name + " from '" + blenderEmpty.name + "'");
}
private void setFromObject(BlenderObject object) {
this.name = object.name;
this.position = object.position;
this.rotation = object.rotation;
this.scale = object.scale;
this.visibleOnLayers = new Bits();
for (int i = 0; i < object.layers.length; i++) {
if (object.layers[i]) {
this.visibleOnLayers.set(i);
}
}
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("GameObjectBlueprint{");
sb.append("armatureNodeId='").append(armatureNodeId).append('\'');
sb.append(", name='").append(name).append('\'');
sb.append(", model=").append(model);
sb.append(", rotation=").append(rotation);
sb.append(", scale=").append(scale);
sb.append(", position=").append(position);
sb.append(", shapeType='").append(shapeType).append('\'');
sb.append(", mass=").append(mass);
sb.append(", belongsToFlag=").append(belongsToFlag);
sb.append(", collidesWithFlag=").append(collidesWithFlag);
sb.append(", callback=").append(callback);
sb.append(", noDeactivate=").append(noDeactivate);
sb.append(", shape=").append(shape);
sb.append(", visibleOnLayers=").append(visibleOnLayers);
sb.append('}');
return sb.toString();
}
private void setRigidBody(BlenderEmpty blenderEmpty, Model model, Vector3 scale) {
this.shapeType = blenderEmpty.custom_properties.get(blenderCollisionShapeField);
this.mass = Float.parseFloat(blenderEmpty.custom_properties.get(blenderMassField));
tmpScale.set(blenderEmpty.scale.x * scale.x,
blenderEmpty.scale.y * scale.y,
blenderEmpty.scale.z * scale.z);
if (shapeType.equals("capsule")) {
float radius = Math.max(tmpScale.x, tmpScale.z);
shape = new btCapsuleShape(radius, scale.y);
} else if (shapeType.equals("sphere")) {
float radius = Math.max(Math.max(tmpScale.x, tmpScale.y), tmpScale.z);
shape = new btSphereShape(radius);
} else if (shapeType.equals("box")) {
shape = new btBoxShape(tmpScale);
} else if (shapeType.equals("convex_hull")) {
// We need a model instance with the correct scale
ModelInstance modelInstance = new ModelInstance(model);
GameModel.applyTransform(position, rotation, scale, modelInstance);
// Copy the vertices to a work buffer, where we apply the model global transform to them
Matrix4 transform = new Matrix4(modelInstance.nodes.get(0).globalTransform);
Mesh mesh = modelInstance.model.meshes.get(0);
FloatBuffer workBuffer = BufferUtils.newFloatBuffer(mesh.getVerticesBuffer().capacity());
BufferUtils.copy(mesh.getVerticesBuffer(), workBuffer, mesh.getNumVertices() * mesh.getVertexSize() / 4);
BufferUtils.transform(workBuffer, 3, mesh.getVertexSize(), mesh.getNumVertices(), transform);
// First create a shape using all the vertices, then use the built in tool to reduce
// the number of vertices to a manageable amount.
btConvexShape convexShape = new btConvexHullShape(workBuffer, mesh.getNumVertices(), mesh.getVertexSize());
btShapeHull hull = new btShapeHull(convexShape);
hull.buildHull(convexShape.getMargin());
shape = new btConvexHullShape(hull);
convexShape.dispose();
hull.dispose();
} else if (shapeType.equals("none")) {
shape = null;
} else {
throw new GdxRuntimeException("Cannot load collision shape data for " + blenderEmpty.name + " from '" + blenderEmpty.name + "'");
}
setCollisionFlags(this.mass);
}
private void setCollisionFlags(float mass) {
if (mass > 0) {
belongsToFlag = GameEngine.OBJECT_FLAG;
collidesWithFlag = (short) (GameEngine.GROUND_FLAG
| GameEngine.OBJECT_FLAG | GameEngine.PC_FLAG);
} else {
belongsToFlag = GameEngine.GROUND_FLAG;
collidesWithFlag = (short) (GameEngine.OBJECT_FLAG | GameEngine.PC_FLAG);
}
}
@Override
public void dispose() {
if (shape != null) {
shape.dispose();
}
}
}