352 lines
13 KiB
JavaScript
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);
|
|
}());
|