content/hifi-content/jedon/Game_Creater_Toolkit/AI_Nav/AI_Navigation.js
2022-02-13 23:57:50 +01:00

352 lines
13 KiB
JavaScript

//
// AI_Navigation.js
// unpublished/marketplace/
//
// Created by Je'Don (ROC) Carter on 8/23/2017
// Copyright 2017 High Fidelity, Inc.
//
// This app allows the user to place beacons and create a model that travels from beacon to beacon
// Use case...Having knights circle around your castle
//
// Distributed under the Apache License, Version 7.1.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function() {
// Every great app starts with a great name (keep it short so that it can fit in the tablet button)
var APP_NAME = "NPC NAV";
// Link to your app's HTML file
var APP_URL = Script.resolvePath("./Tablet_Navigation_App.html?" + Date.now());
//Link to Navigation ICON
var APP_ICON = Script.resolvePath("./npc-nav-i.svg");
// Get a reference to the tablet
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
//holds # of beacons
var numBeacons = 0;
//holds id's of beacons
var beacons = [];
//holds id of the model
var model;
//decides whether the object can move or not
var move = false;
//holds location of next beacon for movement
var next = 0;
//holds the entities speed
var speed;
//holds the time the idle animation plays between beacons
var idleTime;
var idleAnim;
var movingAnim;
var characterModel;
//holds the information of the event recieved
var info;
//distance between beacon and model
var dist;
//dimensions of the model
var modelDim;
// "Install" your cool new app to the tablet
// The following lines create a button on the tablet's menu screen
var button = tablet.addButton({
icon: APP_ICON,
text: APP_NAME
});
// When user click the app button, we'll display our app on the tablet screen
function onClicked() {
tablet.gotoWebScreen(APP_URL);
}
button.clicked.connect(onClicked);
// Handle the events we're recieving from the web UI
function onWebEventReceived(event) {
// Converts string event to object
info = JSON.parse(event);
if ((info.type =="Preview") && ((numBeacons <= 8) && (numBeacons > 1)) && (model == undefined)) {
Script.update.connect(MakeMove);
//set idle
characterModel = info.modelURL;
idleTime = info.idleTime;
idleAnim = info.idleAnimationURL;
movingAnim = info.movingAnimationURL;
speed = info.speed;
//get index of last beacon added to domain
var lastBeaconIndex = beacons.length - 1;
//Create the model
var modelProperties = {
"name": "NAV-AnimatedModel",
"type": "Model",
"position": Entities.getEntityProperties(beacons[lastBeaconIndex]).position,
"lifetime": -1,
"damping": .9,
"collisionless": true, //maybe change to false
"userData": "{\"grabbableKey\":{\"grabbable\":false}}",
animation: {
running: true,
url: info.movingAnimationURL
},
collidesWith: "",
modelURL: info.modelURL,
dynamic: true,
grabbable: false,
shapeType: "compound"
};
model = Entities.addEntity(modelProperties);
//Change the registration point of the model so that the model appears on the beacon instead of in the middle of it.
modelDim = Entities.getEntityProperties(model).dimensions;
var raiseModel = {
"registrationPoint": {
x: .5,
y: (modelDim.y / 2),
z: 0
}
};
Entities.editEntity(model, raiseModel);
//Allows model to begin moving
move = true;
} else if ((info.type =="Preview") && (numBeacons <= 8) && (numBeacons > 1) && (model != undefined)) {
//if a model is already created this allows it to get changed instantly
characterModel = info.modelURL;
idleTime = info.idleTime;
idleAnim = info.idleAnimationURL;
movingAnim = info.movingAnimationURL;
speed = info.speed;
move = true;
var modelProperties = {
animation: {
url: info.movingAnimationURL
},
modelURL: info.modelURL
};
Entities.editEntity(model, modelProperties);
} else if ((info.type =="Preview") && (numBeacons < 2)) {
print("not enough beacons");
} else if ((info.type =="Preview") && (numBeacons > 8)) {
print("too many beacons");
} else if ((info.type == "click") && (info.data == "Place Beacon") && (numBeacons < 8)) {
//increase the beacon amount
++numBeacons;
//Create the beacon
var beaconProperties = {
"type": "Model",
"lifetime": -1,
"position": getPositionToCreateEntity(),
"collisionless": true,
"dynamic": true,
"damping": .9,
"angularDamping": .9,
//"visible": false, //changed for testing
"userData": "{\"grabbableKey\":{\"grabbable\":true}}",
name: "NAV-Beacon_" + numBeacons,
"registrationPoint": {
x: .5,
y: 0,
z: .5
},
modelURL: Script.resolvePath("./beacon.fbx"),
dynamic: true,
shapeType: "Box"
};
beacons.push(Entities.addEntity(beaconProperties));
} else if ((info.type == "click") && (info.data == "Clear Beacons")) {
for (i = 0; i < beacons.length; i++) {
Entities.deleteEntity(beacons[i]);
}
beacons = [];
Entities.deleteEntity(model);
model = undefined;
move = false;
next = 0;
numBeacons = 0;
Script.update.disconnect(MakeMove);
}
//End the preview
if ((info == "DONE") && model != undefined) {
move = false;
var modelProperties = {
velocity: {
x: 0,
y: 0,
z: 0
},
animation: {
url: idleAnim
}
};
Entities.editEntity(model, modelProperties);
cleanup();
//Check to see if no model
} else if ((info == "DONE") && model == undefined) {
print("didnt meet criteria, please fill in all spaces provided.");
}
}
tablet.webEventReceived.connect(onWebEventReceived);
function MakeMove() {
//makes the model move from beacon to beacon
if ((next != numBeacons) && (numBeacons >= 2) && (model != undefined) && (move)) {
try {
//get the length between model and next beacon
var beaconPosition = Entities.getEntityProperties(beacons[next]).position;
var modelPosition = Entities.getEntityProperties(model).position;
var raise = (beaconPosition.y + (modelDim.y / 2));
//get distance between model and beacon
var dx = beaconPosition.x - modelPosition.x;
var dy = raise - modelPosition.y;
var dz = beaconPosition.z - modelPosition.z;
dist = getDistance(dx, dy, dz);
//rotate model so it appears to look at next beacon
var rot = getRotation(beaconPosition, modelPosition, raise);
//Make move
var newProperties = getVelocity(dx, dy, dz, dist, rot);
Entities.editEntity(model, newProperties);
} catch (err) {
print("You deleted something in world");
print("array was length " + beacons.length);
//removes beacon from array
beacons.splice(next, 1);
next = 0;
--numBeacons;
print("new array is length " + beacons.length);
//if there is only one beacon left then clear everything (must have at least two for app)
if (beacons.length == 1) {
move = false;
next = 0;
numBeacons = 0;
Entities.deleteEntity(beacons[0]);
beacons = [];
Entities.deleteEntity(model);
model = undefined;
}
}
}
//makes the model move from beacon to beacon
if (move) {
//make object go to first beacon placed if its currently at the last beacon
if ((next) == numBeacons)
{
next = 0;
//will be else if distance is less than some number I will change target to next beacon
} else if (dist < .25) {
Script.update.disconnect(MakeMove);
var modelProperties = {
velocity: {
x: 0,
y: 0,
z: 0
},
animation: {
url: idleAnim
}
};
Entities.editEntity(model, modelProperties);
next++;
Script.setTimeout(Wait, (idleTime * 1000));
}
}
}
function Wait() {
var modelProperties = {
animation: {
url: movingAnim
}
};
Entities.editEntity(model, modelProperties);
Script.update.connect(MakeMove);
}
function getVelocity(dx, dy, dz, dist, rot) {
var vectorProperties = {
velocity: {
x: (dx/dist) * speed,
y: (dy/dist) * speed,
z: (dz/dist) * speed
},
rotation: rot
};
return vectorProperties;
}
function getRotation(pointA, pointB, raise) {
// suppose we have a laser turret that we want to point at a
// known target location
pointA.y = raise;
var eye = pointB;
var center = pointA;
var up = Vec3.UP; // world-frame's up
// Quat.lookAt() will compute the orientation we want (MULTIPLIED TO MAKE LOOK BEHIND SINCE THATS HOW MODELS IMPORT)
var rot = Quat.multiply(Quat.lookAt(eye, center, up), {w: 0, x: 0, y: 1, z: 0});
return rot;
}
function getDistance(dx, dy, dz) {
//get distance between model and beacon
var dist = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2) + Math.pow(dz, 2));
return dist;
}
// Helper function that gives us a position right in front of the user
function getPositionToCreateEntity() {
var direction = Quat.getFront(MyAvatar.orientation);
var distance = 0.3;
var position = Vec3.sum(MyAvatar.position, Vec3.multiply(direction, distance));
position.y -= 1;
return position;
}
// Provide a way to "uninstall" the app
// Here, we write a function called "cleanup" which gets executed when
// this script stops running. It'll remove the app button from the tablet.
//MAKE SURE YOU ADD A DONE BUTTON
function cleanup() {
print("saving beacon positions");
//save all beacon positions
var pos = [];
for (i = 0; i < beacons.length; i++) {
pos.push(Entities.getEntityProperties(beacons[i]).position);
}
//set userdata properties
var set1 = {
"positions": pos,
"numBeacons": numBeacons,
"speed": speed,
"characterModel": characterModel,
"idleTime": idleTime,
"idleAnim": idleAnim,
"movingAnim": movingAnim
};
//send to model
Entities.editEntity(model, { userData: JSON.stringify(set1) });
var set2 = {
serverScripts: Script.resolvePath("./Movement.js?" + Date.now())
};
Entities.editEntity(model, set2);
print("deleting all unnecessary entities");
//delete everything I no longer need
for (i = 0; i < beacons.length; i++) {
Entities.deleteEntity(beacons[i]);
}
beacons.splice(0, beacons.length);
beacons = [];
model = undefined;
move = false;
next = 0;
numBeacons = 0;
Script.update.disconnect(MakeMove);
}
//Added this because sometimes I was seeing two buttons on reload
function removeButton() {
print("cleaning up");
tablet.removeButton(button);
}
Script.scriptEnding.connect(removeButton);
}());