// TapToSpawn.js
// Version: 0.0.1
// Event: On Awake
// Description: Tap to spawn prefab on the plane object

//@input Component.Camera camera
/** @type {Camera} */
var camera = script.camera;

//@input Asset.ObjectPrefab[] prefabs
/** @type {ObjectPrefab[]} */
var prefabs = script.prefabs;

//@input int surfaceType = 2 {"widget":"combobox", "values":[{"label":"Both", "value":2}, {"label":"Horizontal", "value":0}, {"label":"Vertical", "value":1}]}
/** @type {int} */
var surfaceType = script.surfaceType;

//@input float minOffsetFromPlane = 0.0 {"widget":"slider", "min":0.0, "max":50, "step":1.0}
/** @type {float} */
var minOffsetFromPlane = script.minOffsetFromPlane;

//@input float maxOffsetFromPlane = 0.0 {"widget":"slider", "min":0.0, "max":50, "step":1.0}
/** @type {float} */
var maxOffsetFromPlane = script.maxOffsetFromPlane;

//@input string upAxis = "planeNormal" {"widget":"combobox", "values":[{"label":"Plane Normal", "value":"planeNormal"}, {"label":"Unchanged", "value":"unchanged"}]}
/** @type {string} */
var upAxis = script.upAxis;

//@input string fwdAxis = "random" {"widget":"combobox", "values":[{"label":"Random", "value":"random"}, {"label":"Unchanged", "value":"unchanged"}]}
/** @type {string} */
var fwdAxis = script.fwdAxis;

//@input float minScale = 0.8 {"widget":"slider", "min":0.01, "max":2.0, "step":0.01}
/** @type {float} */
var minScale = script.minScale;

//@input float maxScale = 1.2 {"widget":"slider", "min":0.01, "max":2.0, "step":0.01}
/** @type {float} */
var maxScale = script.maxScale;


var inputsValidated = false;
function validateInputs(){
    if(!camera){
        print("ERROR: Please assign the camera to this script.");
        return;
    }
    
    if(!prefabs || prefabs.length < 1){
        print("ERROR: Please assign a prefab to the object you would like to spawn.");
        return;
    }

    if(upAxis == "planeNormal" && fwdAxis == "planeNormal"){
        print("ERROR: Please do not assign the plane normal to both the Up Axis and the Fwd Axis.")
        return;
    }

    inputsValidated = true;
}

validateInputs();

// Create a probe to raycast through all worlds.
var probe = Physics.createGlobalProbe();
 
// Set some properties on it.
probe.debugDrawEnabled = false;
probe.filter.includeStatic = true;
probe.filter.includeDynamic = false;
probe.filter.includeIntangible = true;

var centerScreenPoint = new vec2(0.5, 0.5);

script.createEvent("TapEvent").bind(onTap);

/**
 * Sets the rotation of the object to spawn based on the Up Axis and Fwd Axis
 * @param {RayCastHit} hit 
 * @param {Transform} tr 
 */
function setRotation(hit, tr){
    if(upAxis == "unchanged" && fwdAxis == "random"){
        var rot = tr.getWorldRotation();
        var angle = randomRange(-Math.PI, Math.PI);
        rot = rot.multiply(quat.angleAxis(angle, vec3.up()));
        tr.setWorldRotation(rot);
    }else if(upAxis == "unchanged" && fwdAxis == "unchanged"){
        // No change
    }else if(upAxis == "planeNormal" && fwdAxis == "random"){
        var yRot = quat.fromEulerAngles(0, randomRange(0, Math.PI*2), 0);
        var xRot = quat.fromEulerAngles(Math.PI * 0.5, 0, 0);
        var normalRot = quat.lookAt(hit.normal, vec3.up());
        var rot = (normalRot.multiply(xRot.multiply(yRot)));
        tr.setWorldRotation(rot);
    }else if(upAxis == "planeNormal" && fwdAxis ==  "unchanged"){
        var xRot = quat.fromEulerAngles(Math.PI * 0.5, 0, 0);
        var normalRot = quat.lookAt(hit.normal, vec3.up());
        var rot = normalRot.multiply(xRot);
        tr.setWorldRotation(rot);
    }
}

/**
 * Instantiate and place prefab
 * @param {RayCastHit} hit 
 */
function instantiatePrefab(hit){
    // Instantiate prefab
    var newInstance = getRandomItem(prefabs).instantiate(null);
    var tr = newInstance.getTransform();

    // Set prefab position
    var pos = hit.position;
    var mult = randomRange(minOffsetFromPlane, maxOffsetFromPlane);
    var offset = hit.normal.uniformScale(mult);

    pos = pos.add(offset);
    tr.setWorldPosition(pos);

    // Set prefab rotation
    setRotation(hit, tr);

    // Set prefab scale
    var scale = tr.getLocalScale().uniformScale(randomRange(minScale, maxScale));
    tr.setLocalScale(scale);
}

/**
 * 
 * @param {TapEvent} eventData 
 */
function onTap(eventData) {

    if(!inputsValidated){
        return;
    }

    var rayStart = camera.getTransform().getWorldPosition();
    var rayEnd;
    
    // On Spectacles, raycast from camera 
    var screenPoint = centerScreenPoint;

    // On mobile, raycast from touchpoint
    if(global.deviceInfoSystem.isMobile){
        screenPoint = eventData.getTapPosition();
    }

    rayEnd = camera.screenSpaceToWorldSpace(screenPoint, 10000); // NOTE: you can edit this raycast distance to fit your scene. 

    // Find the first hit.
    probe.rayCast(rayStart, rayEnd, function (hit) {
        if (hit !== null) {
            /**
             * @type {PlaneObj}
             */
            var planeObj = hit.collider.getSceneObject().getParent().getComponent("Component.ScriptComponent");

            if(planeObj != null){

                // If we hit the back of the plane, return.
                if(hit.normal.dot(planeObj.collider.getTransform().up) < 0){
                    return;
                }

                switch(surfaceType){
                    case 0: // Horizontal
                        if(planeObj.getTrackedPlane().orientation == TrackedPlaneOrientation.Horizontal){
                            instantiatePrefab(hit);
                        }
                        break;
                    case 1: // Vertical
                        if(planeObj.getTrackedPlane().orientation == TrackedPlaneOrientation.Vertical){
                            instantiatePrefab(hit);
                        }
                        break;
                    case 2: // Both
                        instantiatePrefab(hit);
                        break;
                    default:
                }
            }
        }
    });
}

/**
* Returns a random value between `min` and `max`
* @param {number} min 
* @param {number} max 
* @returns {number} Random value between `min` and `max`
*/
function randomRange(min, max) {
    return min + Math.random() * (max-min);
}

/**
* @template T
* @param {T[]} list
* @returns {T}
*/
function getRandomItem(list) {
    var index = Math.floor(Math.random() * list.length);
    return list[index];
 }

