/*jshint esversion: 11 */
// @ts-check
/**
* CS559 3D World Framework Code
*
* Simple, automatic UI from a GrWorld
*
* @module WorldUI
* */
// we need to have the BaseClass definition
import { GrObject } from "./GrObject.js";
// we need to import the module to get its typedefs for the type checker
import * as InputHelpers from "../CS559/inputHelpers.js";
import { GrWorld } from "./GrWorld.js";
import * as T from "../CS559-Three/build/three.module.js";
import { panel } from "./AutoUI.js";
// allow for adding a "remote" button for grading
function remoteButton(button, url, world, where) {
// attempt to do "lazy loading"
// inspired by on https://blog.avenuecode.com/lazy-loading-es2015-modules-in-the-browser
// refer to...
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import
const but = InputHelpers.makeButton(button, where);
but.onclick = function() {
/* jshint ignore:start */
import(url)
.then(function(mod) {
mod.logme(world,T);
})
.catch(err => {
alert("Grading Script not Available - students don't need to worry about this.");
console.log(`error loading grading module ${err}`);
});
/* jshint ignore:end */
};
}
export class WorldUI {
/**
* Create a UI panel for a GrWorld - this mimics the AutoUI
* for GrObject.
*
* Note: this just creates controls for the world parameters.
* It does not create UIs for the objects in the world
*
* This does place the panel into the DOM (onto the web page)
* using `insertElement` in the CS559 helper library. The place
* it is placed is controlled the `where` parameter. If you don't
* pass a place, it puts it in a "Panel Panel" at the end of the DOM
* (see AutoUI)
*
* @param {GrWorld} world
* @param {number} [width=300]
* @param {InputHelpers.WhereSpec} [where] - where to place the panel in the DOM (at the end of the page by default)
*/
constructor(world, width = 500, where = undefined, grading=true) {
const self = this;
this.world = world;
/* if no where is provided, put it at the end of the panel panel - assuming there is one */
if (!where) {
where = panel();
}
this.div = InputHelpers.makeBoxDiv({ width: width }, where);
this.div.id = "world-ui-div";
InputHelpers.makeHead("World Controls", this.div, { tight: true });
// because in some cases, we lose this (because of scoping issues), make a variable
// that reliably refers to the world
const _world = this.world;
// run control
this.runbutton = InputHelpers.makeCheckbox("Run", this.div);
world.runbutton = this.runbutton;
world.runbutton.checked = true;
this.runslider = new InputHelpers.LabelSlider("speed", {
width: 250,
min: 0.1,
max: 3,
step: 0.1,
initial: 1,
where: this.div
});
world.speedcontrol = this.runslider.range;
// create "view solo" checkbox.
this.selectionChkList = InputHelpers.makeFlexDiv(this.div);
/**@type HTMLInputElement */
this.chkSolo = InputHelpers.makeCheckbox(
"chkSolo",
this.selectionChkList,
"View Solo Object"
);
this.chkSolo.onclick = function () {
// avoid this as it is ambiguous when reading the code and lacks type info
if (self.chkSolo.checked) {
// we need to have some active object - so update it!
_world.setActiveObject(self.selectLook.value);
_world.showSoloObject();
} else {
_world.showWorld();
}
};
if (grading) {
InputHelpers.makeSpan("replace",this.selectionChkList).innerHTML=" ";
remoteButton("Grader","https://graphics.cs.wisc.edu/test/grading.js",world,this.selectionChkList);
}
//
//
this.selectViewMode = InputHelpers.makeSelect(
["Orbit Camera", "Fly Camera", "Follow Object", "Drive Object"],
this.div
);
this.selectViewMode.onchange = function () {
// if we're driving or following make sure we have something to ride/follow
// note that we need to do this before setting the mode
if (
self.selectViewMode.value == "Drive Object" ||
self.selectViewMode.value == "Follow Object"
) {
_world.setActiveObject(self.selectRideable.value);
}
// avoid this as it is ambiguous when reading the code and lacks type info
_world.setViewMode(self.selectViewMode.value);
};
this.selectViewMode.onchange(null);
InputHelpers.makeBreak(this.div);
// create object selector for rideable
InputHelpers.makeSpan("Drive:", this.div);
const rideable = world.objects.filter(obj => obj.rideable);
this.selectRideable = InputHelpers.makeSelect(
rideable.map(ob => ob.name),
this.div
);
this.selectRideable.onchange = function () {
// avoid this as it is ambiguous when reading the code and lacks type info
_world.setActiveObject(self.selectRideable.value);
_world.setViewMode("Drive Object");
self.selectViewMode.value = "Drive Object";
};
// create a selector for isolate
// because there are often too many objects, we
// allow for some to be "highlighted" and included on a
// shorter list
InputHelpers.makeBreak(this.div);
InputHelpers.makeSpan("LookAt:", this.div);
this.selectLook = InputHelpers.makeSelect(
world.objects.map(ob => ob.name).sort(),
this.div
);
// this has to work for either lookat or highlight
function onSelectLook(event) {
// if we were driving, stop!
if (
world.view_mode == "Drive Object" ||
world.view_mode == "Follow Object"
) {
_world.setViewMode("Orbit Camera");
self.selectViewMode.value = "Orbit Camera";
}
const name = event.target.value;
_world.setActiveObject(name);
const obj = _world.objects.find(ob => ob.name === name);
const camparams = obj.lookFromLookAt();
world.camera.position.set(camparams[0], camparams[1], camparams[2]);
const lookAt = new T.Vector3(camparams[3], camparams[4], camparams[5]);
world.camera.lookAt(lookAt);
world.orbit_controls.target = new T.Vector3(
camparams[3],
camparams[4],
camparams[5]
);
}
this.selectLook.onchange = onSelectLook;
// get a list of the names of highlighted objects
let highObjs = world.objects.filter(ob => ob.highlighted);
if (highObjs.length) {
InputHelpers.makeBreak(this.div);
InputHelpers.makeSpan("LookAt (highlighted objects):", this.div);
this.selectLookHigh = InputHelpers.makeSelect(
highObjs.map(ob => ob.name).sort(),
this.div
);
this.selectLookHigh.onchange = onSelectLook;
}
}
}