planck
Version:
2D JavaScript/TypeScript physics engine for cross-platform HTML5 game development
258 lines (229 loc) • 8.02 kB
text/typescript
import { World } from "../dynamics/World";
import { Body } from "../dynamics/Body";
import { Joint } from "../dynamics/Joint";
import { Fixture } from "../dynamics/Fixture";
import { Shape } from "../collision/Shape";
import { Vec2 } from "../common/Vec2";
import { Vec3 } from "../common/Vec3";
import { ChainShape } from "../collision/shape/ChainShape";
// import { BoxShape } from "../collision/shape/BoxShape";
import { EdgeShape } from "../collision/shape/EdgeShape";
import { PolygonShape } from "../collision/shape/PolygonShape";
import { CircleShape } from "../collision/shape/CircleShape";
import { DistanceJoint } from "../dynamics/joint/DistanceJoint";
import { FrictionJoint } from "../dynamics/joint/FrictionJoint";
import { GearJoint } from "../dynamics/joint/GearJoint";
import { MotorJoint } from "../dynamics/joint/MotorJoint";
import { MouseJoint } from "../dynamics/joint/MouseJoint";
import { PrismaticJoint } from "../dynamics/joint/PrismaticJoint";
import { PulleyJoint } from "../dynamics/joint/PulleyJoint";
import { RevoluteJoint } from "../dynamics/joint/RevoluteJoint";
import { RopeJoint } from "../dynamics/joint/RopeJoint";
import { WeldJoint } from "../dynamics/joint/WeldJoint";
import { WheelJoint } from "../dynamics/joint/WheelJoint";
let SID = 0;
// Classes to be serialized as reference objects
const SERIALIZE_REF_TYPES = {
"World": World,
"Body": Body,
"Joint": Joint,
"Fixture": Fixture,
"Shape": Shape,
};
// For deserializing reference objects by reference type
const DESERIALIZE_BY_REF_TYPE = {
"Vec2": Vec2,
"Vec3": Vec3,
"World": World,
"Body": Body,
"Joint": Joint,
"Fixture": Fixture,
"Shape": Shape,
};
// For deserializing data objects by type field
const DESERIALIZE_BY_TYPE_FIELD = {
[Body.STATIC]: Body,
[Body.DYNAMIC]: Body,
[Body.KINEMATIC]: Body,
[ChainShape.TYPE]: ChainShape,
// [BoxShape.TYPE]: BoxShape,
[PolygonShape.TYPE]: PolygonShape,
[EdgeShape.TYPE]: EdgeShape,
[CircleShape.TYPE]: CircleShape,
[DistanceJoint.TYPE]: DistanceJoint,
[FrictionJoint.TYPE]: FrictionJoint,
[GearJoint.TYPE]: GearJoint,
[MotorJoint.TYPE]: MotorJoint,
[MouseJoint.TYPE]: MouseJoint,
[PrismaticJoint.TYPE]: PrismaticJoint,
[PulleyJoint.TYPE]: PulleyJoint,
[RevoluteJoint.TYPE]: RevoluteJoint,
[RopeJoint.TYPE]: RopeJoint,
[WeldJoint.TYPE]: WeldJoint,
[WheelJoint.TYPE]: WheelJoint,
};
// dummy types
type DataType = any;
type ObjectType = any;
type ClassName = any;
type SerializedType = object[];
type RefType = {
refIndex: number;
refType: string;
};
type SerializerOptions = {
rootClass: ClassName;
preSerialize?: (obj: ObjectType) => DataType;
postSerialize?: (data: DataType, obj: any) => DataType;
preDeserialize?: (data: DataType) => DataType;
postDeserialize?: (obj: ObjectType, data: DataType) => ObjectType;
};
const DEFAULT_OPTIONS: SerializerOptions = {
rootClass: World,
preSerialize: function (obj) {
return obj;
},
postSerialize: function (data, obj) {
return data;
},
preDeserialize: function (data: DataType) {
return data;
},
postDeserialize: function (obj, data) {
return obj;
},
};
type DeserializeChildCallback = (classHint: any, obj: any, context: any) => any;
type ClassDeserializerMethod = (data: any, context: any, deserialize: DeserializeChildCallback) => any;
export class Serializer<T> {
private options: SerializerOptions;
constructor(options: SerializerOptions) {
this.options = {
...DEFAULT_OPTIONS,
...options,
};
}
toJson = (root: T): SerializedType => {
const preSerialize = this.options.preSerialize;
const postSerialize = this.options.postSerialize;
const json = [];
// Breadth-first ref serialization queue
const refQueue = [root];
const refMemoById: Record<number, RefType> = {};
function addToRefQueue(value: any, typeName: string) {
value.__sid = value.__sid || ++SID;
if (!refMemoById[value.__sid]) {
refQueue.push(value);
const index = json.length + refQueue.length;
const ref = {
refIndex: index,
refType: typeName,
};
refMemoById[value.__sid] = ref;
}
return refMemoById[value.__sid];
}
function serializeWithHooks(obj: ObjectType) {
obj = preSerialize(obj);
let data = obj._serialize();
data = postSerialize(data, obj);
return data;
}
// traverse the object graph
// ref objects are pushed into the queue
// other objects are serialize in-place
function traverse(value: any, noRefType = false) {
if (typeof value !== "object" || value === null) {
return value;
}
// object with _serialize function
if (typeof value._serialize === "function") {
if (!noRefType) {
for (const typeName in SERIALIZE_REF_TYPES) {
if (value instanceof SERIALIZE_REF_TYPES[typeName]) {
return addToRefQueue(value, typeName);
}
}
}
// object with _serialize function
value = serializeWithHooks(value);
}
// recursive for arrays any objects
if (Array.isArray(value)) {
const newValue = [];
for (let key = 0; key < value.length; key++) {
newValue[key] = traverse(value[key]);
}
value = newValue;
} else {
const newValue = {};
for (const key in value) {
if (value.hasOwnProperty(key)) {
newValue[key] = traverse(value[key]);
}
}
value = newValue;
}
return value;
}
while (refQueue.length) {
const obj = refQueue.shift();
const str = traverse(obj, true);
json.push(str);
}
return json;
};
fromJson = (json: SerializedType): T => {
const preDeserialize = this.options.preDeserialize;
const postDeserialize = this.options.postDeserialize;
const rootClass = this.options.rootClass;
const deserializedRefMemoByIndex: Record<number, any> = {};
function deserializeWithHooks(classHint: ClassName, data: DataType, context: any): ObjectType {
if (!classHint || !classHint._deserialize) {
classHint = DESERIALIZE_BY_TYPE_FIELD[data.type];
}
const deserializer = classHint && classHint._deserialize;
if (!deserializer) {
return;
}
data = preDeserialize(data);
const classDeserializeFn = classHint._deserialize as ClassDeserializerMethod;
let obj = classDeserializeFn(data, context, deserializeChild);
obj = postDeserialize(obj, data);
return obj;
}
/**
* Recursive callback function to deserialize a child data object or reference object.
*
* @param classHint suggested class to deserialize obj to
* @param dataOrRef data or reference object
* @param context for example world when deserializing bodies and joints
*/
function deserializeChild(classHint: ClassName, dataOrRef: DataType | RefType, context: any) {
const isRefObject = dataOrRef.refIndex && dataOrRef.refType;
if (!isRefObject) {
return deserializeWithHooks(classHint, dataOrRef, context);
}
const ref = dataOrRef as RefType;
if (DESERIALIZE_BY_REF_TYPE[ref.refType]) {
classHint = DESERIALIZE_BY_REF_TYPE[ref.refType];
}
const refIndex = ref.refIndex;
if (!deserializedRefMemoByIndex[refIndex]) {
const data = json[refIndex];
const obj = deserializeWithHooks(classHint, data, context);
deserializedRefMemoByIndex[refIndex] = obj;
}
return deserializedRefMemoByIndex[refIndex];
}
const root = deserializeWithHooks(rootClass, json[0], null);
return root;
};
static toJson: (root: World) => SerializedType;
static fromJson: (json: SerializedType) => World;
}
const worldSerializer = new Serializer<World>({
rootClass: World,
});
Serializer.fromJson = worldSerializer.fromJson;
Serializer.toJson = worldSerializer.toJson;