playcanvas / engine

JavaScript game engine built on WebGL, WebGPU, WebXR and glTF
https://playcanvas.com
MIT License
9.65k stars 1.34k forks source link

Add toJSON for the assets #1053

Open scarletsky opened 6 years ago

scarletsky commented 6 years ago

It is good for all assets to have toJSON method, so that we can save and init the asset by data easily.

Here is my StandardMaterial.prototype.toJSON implementation:

import constants from './constants';

pc.StandardMaterial.prototype.toJSON = (function() {

    var fields = [
        'alphaTest', 'alphaToCoverage',
        'ambient', 'ambientTint',
        'aoMap', 'aoMapChannel', 'aoMapOffset', 'aoMapTiling',
        'aoMapUv', 'aoMapVertexColor',
        'blendType',
        'bumpiness',
        'conserveEnergy',
        'cubeMap', 'cubeMapProjection', 'cubeMapProjectionBox',
        'cull',
        'depthTest', 'depthWrite', 'diffuse',
        'diffuseMap', 'diffuseMapChannel', 'diffuseMapOffset', 'diffuseMapTiling',
        'diffuseMapTint', 'diffuseMapUv', 'diffuseMapVertexColor',
        'emissive', 'emissiveIntensity', 'emissiveMap',
        'emissiveMapChannel', 'emissiveMapOffset', 'emissiveMapTiling',
        'emissiveMapTint', 'emissiveMapUv', 'emissiveMapVertexColor',
        'fresnelModel',
        'glossMap', 'glossMapChannel', 'glossMapOffset', 'glossMapTiling',
        'glossMapUv', 'glossMapVertexColor',
        'heightMap', 'heightMapChannel', 'heightMapFactor',
        'heightMapOffset', 'heightMapTiling', 'heightMapVertexColor', 'heightMapUv',
        'lightMap', 'lightMapChannel', 'lightMapOffset',
        'lightMapTiling', 'lightMapUv', 'lightMapVertexColor',
        'metalness', 'metalnessMap', 'metalnessMapChannel',
        'metalnessMapOffset', 'metalnessMapTiling',
        'metalnessMapUv', 'metalnessMapVertexColor',
        'name',
        'normalMap', 'normalMapOffset', 'normalMapTiling',
        'normalMapUv', 'normalMapVertexColor',
        'occludeSpecular', 'occludeSpecularIntensity',
        'opacity', 'opacityMap', 'opacityMapChannel',
        'opacityMapOffset', 'opacityMapTiling',
        'opacityMapUv', 'opacityMapVertexColor',
        'reflectivity', 'refraction', 'refractionIndex',
        'shadingModel',
        'shadowSampleType',
        'shininess',
        'specular', 'specularAntialias', 'specularMap',
        'specularMapChannel', 'specularMapOffset', 'specularMapTiling',
        'specularMapTint', 'specularMapUv', 'specularMapVertexColor', 'sphereMap',
        'useFog', 'useGammaTonemap', 'useLighting',
        'useMetalness', 'useSkybox',
    ];

    var defaultMaterial = toJSON.call(pc.ModelHandler.DEFAULT_MATERIAL);

    function toJSON(options) {
        var result = {};
        var self = this;

        if (typeof options === 'undefined') {
            options = {};
        }

        fields.forEach(function(field) {
            // texture
            if (constants.MATERIAL_TEXTURE_FIELDS2[field]) {

                if (self[field] !== null) {
                    result[field] = self[field].name;
                } else {
                    result[field] = null;
                }

            // color or vector
            } else if (constants.MATERIAL_ARRAY_FIELDS2[field]) {

                result[field] = Array.from(self[field].data);

            } else if (constants.MATERIAL_OBJECT_FIELDS2[field]) {

                result[field] = self[field] ? self[field].toJSON() : null;

            // primitive
            } else {

                // convert bumpiness to bumpMapFactor
                if (field === 'bumpiness') {
                    result['bumpMapFactor'] = self[field];
                } else {
                    result[field] = self[field];
                }
            }
        });

        if (options.diff) {
            fields.forEach(function(field) {

                if (field === 'bumpiness') {
                    field = 'bumpMapFactor';
                }

                var value1 = defaultMaterial[field];
                var value2 = result[field];

                // test two array is equal or not
                if (constants.MATERIAL_ARRAY_FIELDS2[field]) {
                    value1 = value1.toString();
                    value2 = value2.toString();
                }

                if (constants.MATERIAL_OBJECT_FIELDS2[field]) {
                    value1 = JSON.stringify(value1);
                    value2 = JSON.stringify(value2);
                }

                if (value1 === value2) {
                    delete result[field];
                }
            });
        }

        return result;
    }

    return toJSON;
})();

And the constants:

var MATERIAL_TEXTURE_FIELDS = [
    'aoMap', 'diffuseMap', 'glossMap', 'metalnessMap',
    'specularMap', 'emissiveMap', 'opacityMap', 'lightMap',
    'normalMap', 'heightMap', 'sphereMap', 'cubeMap'
];

var MATERIAL_TEXTURE_FIELDS2 = MATERIAL_TEXTURE_FIELDS.reduce(function(prev, next) {
    prev[next] = true;
    return prev;
}, {});

var MATERIAL_COLOR_FIELDS = [
    'diffuse', 'specular', 'ambient', 'emissive'
];

var MATERIAL_VECTOR_FIELDS = [
    // vec2
    'diffuseMapOffset', 'diffuseMapTiling',
    'opacityMapOffset', 'opacityMapTiling',
    'aoMapOffset', 'aoMapTiling',
    'specularMapOffset', 'specularMapTiling',
    'metalnessMapOffset', 'metalnessMapTiling',
    'glossMapOffset', 'glossMapTiling',
    'heightMapOffset', 'heightMapTiling',
    'emissiveMapOffset', 'emissiveMapTiling',
    'normalMapOffset', 'normalMapTiling',
    'lightMapOffset', 'lightMapTiling',
];

var MATERIAL_OBJECT_FIELDS = [
    'cubeMapProjectionBox'
];
var MATERIAL_OBJECT_FIELDS2 = MATERIAL_OBJECT_FIELDS.reduce(function(prev, next) {
    prev[next] = true;
    return prev;
}, {});

var MATERIAL_ARRAY_FIELDS = MATERIAL_COLOR_FIELDS.concat(MATERIAL_VECTOR_FIELDS);
var MATERIAL_ARRAY_FIELDS2 = MATERIAL_ARRAY_FIELDS.reduce(function(prev, next) {
    prev[next] = true;
    return prev;
}, {});

export default {
    MATERIAL_TEXTURE_FIELDS: MATERIAL_TEXTURE_FIELDS,
    MATERIAL_TEXTURE_FIELDS2: MATERIAL_TEXTURE_FIELDS2,
    MATERIAL_ARRAY_FIELDS: MATERIAL_ARRAY_FIELDS,
    MATERIAL_ARRAY_FIELDS2: MATERIAL_ARRAY_FIELDS2,
    MATERIAL_OBJECT_FIELDS: MATERIAL_OBJECT_FIELDS,
    MATERIAL_OBJECT_FIELDS2: MATERIAL_OBJECT_FIELDS2,
};

We can call it by:

var mat = new pc.StandardMaterial();
mat.diffuse = new pc.Color(1,0,0,1);

mat.toJSON() // return the full json
/**
 * { diffuse: [1, 0, 0, 1], diffuseMap: null, blendType: 0 ... }
 */

mat.toJSON({ diff: true }); // return the json different from mat.toJSON()
/**
 * { diffuse: [1, 0, 0, 1] }
 */

We can add the same interface to other type of assets. 😃

felladrin commented 6 years ago

Good idea!

japrogramer commented 5 years ago

i would also like that. but something i can run

var func = ({type}) => type === 'model'  ;
app.assets._assets.filter(func).map(asset => ( saveToDisk(asset.toOBJ()));

Here i would like to be able to save obj files of 'models' to disk

willeastcott commented 5 years ago

I'm sure @Maksims wrote an Editor plugin that did that a few years back. I don't know what happened to the code though...