Commit f5b793ab authored by Max Bebök's avatar Max Bebök

Merge branch 'issue-54' into 'develop'

#54 - Actor Search

See merge request !23
parents 669dd595 36b582e0
......@@ -72,6 +72,12 @@
<div class="btn-group ui-history-controls">
</div>
<div class="btn-group">
<button class="btn btn-default data-tool-openActorSearch">
<span class="icon icon-search"></span>
</button>
</div>
<div class="btn-group" style="position: absolute; right: 80px;">
<button onclick="mainApp.openCredits();" class="btn btn-default btn-theme active" title="Credits" style="width: 80px;">
<span class="icon icon-info-circled"> Credits</span>
......@@ -174,7 +180,7 @@
</div>
<div class="pane" id="main-sidebar-2">
<canvas class="shrine-canvas">Oops! here should be something nice in 3D</canvas>
<canvas class="shrine-canvas" tabindex="1">Oops! here should be something nice in 3D</canvas>
</div>
<div class="pane-sm sidebar" id="main-sidebar-3" style="max-width: none;">
......@@ -190,6 +196,8 @@
<h1 class="title data-footer"></h1>
</footer>
<div class="window-container"></div>
</div>
</body>
</html>
......@@ -150,7 +150,7 @@ module.exports = class App extends App_Base
files.forEach(file =>
{
if(shrineRegex.test(file))// || file.startsWith("Remains")) <- 4 main dungeons
if(shrineRegex.test(file))// || file.startsWith("Remains")) // <- 4 main dungeons
{
const shrineName = file.replace(".pack", "");
shrinesHtml += `<option value="${shrineName}">${shrineName}</option>`;
......@@ -168,13 +168,10 @@ module.exports = class App extends App_Base
{
if(files == null)return;
let modelsHtml = "";
let texHtml = "";
files.forEach(file => {
this.selectModel.innerHTML = files.reduce((modelsHtml, file) => {
// if(!file.includes(".Tex1") && !file.includes(".Tex2")) // @TODO make that an option
modelsHtml += `<option value="${modelDir + "/" + file}">${file}</option>`;
});
this.selectModel.innerHTML = modelsHtml;
return modelsHtml + `<option value="${modelDir + "/" + file}">${file}</option>`;
}, "");
});
}
......
......@@ -72,6 +72,12 @@
<div class="btn-group ui-history-controls">
</div>
<div class="btn-group">
<button class="btn btn-default data-tool-openActorSearch">
<span class="icon icon-search"></span>
</button>
</div>
<div class="btn-group" style="position: absolute; right: 80px;">
<button onclick="mainApp.openCredits();" class="btn btn-default btn-theme active" title="Credits" style="width: 80px;">
<span class="icon icon-info-circled"> Credits</span>
......@@ -151,7 +157,7 @@
</div>
<div class="pane" id="main-sidebar-2">
<canvas class="shrine-canvas">Oops! here should be something nice in 3D</canvas>
<canvas class="shrine-canvas" tabindex="1">Oops! here should be something nice in 3D</canvas>
</div>
<div class="pane-sm sidebar" id="main-sidebar-3" style="max-width: none;">
......@@ -167,6 +173,8 @@
<h1 class="title data-footer"></h1>
</footer>
<div class="window-container"></div>
</div>
</body>
</html>
<div id="searchWindow-actor">
<style>
#searchWindow-actor
{
position: absolute;
top: 50%;
left: 50%;
width: 600px;
height: 500px;
resize: both;
overflow: auto;
background-color: #21252b;
border: 1px solid black;
padding: 5px;
transition: opacity 0.2s ease-in-out;
}
#searchWindow-actor.isMoving
{
opacity: 0.7;
}
#searchWindow-actor > div {
height: 100%;
display: flex;
flex-direction: column;
flex-wrap: nowrap;
}
#searchWindow-actor .search-header {
flex-shrink: 0;
}
#searchWindow-actor .search-content {
flex-grow: 1;
padding: 5px;
overflow: auto;
}
#searchWindow-actor .search-footer {
flex-shrink: 0;
}
#searchWindow-actor .search-footer > span {
float: right;
}
#searchWindow-actor .drag-bar {
width: 100%;
font-size: 14px;
height: 26px;
background-color: #353b45;
border: 1px solid #000000;
cursor: move;
color: #c1a888;
padding-left: 4px;
margin-bottom: 5px;
}
#searchWindow-actor .window-buttons
{
position: absolute;
right: 5px;
}
#searchWindow-actor .window-buttons button
{
height: 24px;
border-width: 0 1px;
}
#searchWindow-actor h5 {
margin-top: 5px;
}
#searchWindow-actor .data-searchValue {
width: calc(100% - 60px);
margin-bottom: 5px;
}
#searchWindow-actor .data-limit {
width: 80px;
height: 25px;
margin-right: 6px;
}
#searchWindow-actor .btn-group:last-child {
float: right;
}
#searchWindow-actor .btn-group button {
cursor: pointer;
}
#searchWindow-actor .result-container > div {
border: 1px solid black;
width: 100%;
}
#searchWindow-actor table tr td {
border-color: #282c34;
border-width: 1px 0;
border-style: solid;
}
#searchWindow-actor table tr:active td,
#searchWindow-actor table tr.active td {
border-width: 1px 0 1px 0;
border-color: #73c990;
border-style: solid;
color: #73c990;
}
#searchWindow-actor .btn-regex {
margin-top: 1px;
height: 29px;
}
</style>
<div>
<div class="search-header">
<div class="drag-bar">
Actor-Search
<div class="btn-group window-buttons">
<button class="btn btn-default data-tool-close" title="Close">
<span class="icon icon-cancel-squared"></span>
</button>
</div>
</div>
<div>
<input type="text" class="form-control data-searchValue" placeholder='Search...' />
<div class="btn-group">
<button class="btn btn-default btn-regex" data-filter="regex">Regex</button>
</div>
</div>
<div>
<div class="btn-group">
<button class="btn btn-default" data-filter="id">ID</button>
<button class="btn btn-default" data-filter="srt">SRT</button>
<button class="btn btn-default active" data-filter="name">Name</button>
<button class="btn btn-default" data-filter="params">Params</button>
</div>
<div class="btn-group">
<button class="btn btn-default active" data-filter="static">Static</button>
<button class="btn btn-default active" data-filter="dynamic">Dynamic</button>
</div>
</div>
</div>
<div class="search-content">
<table>
<thead>
<tr>
<th>Name</th>
<th>HashId</th>
<th>SRTHash</th>
<th>Type</th>
</tr>
</thead>
<tbody class="data-resultTable">
</tbody>
</table>
</div>
<div class="search-footer">
<button class="btn btn btn-default data-tool-selectAll">Select All</button>
<span>
Limit: <input type="number" class="form-control data-limit" placeholder='∞' value="100" />
Actors found: <span class="data-resultCount">0</span>
</span>
</div>
</div>
</div>
\ No newline at end of file
......@@ -16,7 +16,6 @@ module.exports = class Renderer_Helper_FPS_Controls extends Base
this.camera = camera;
this.scene = scene;
this.canvasNode = canvasNode;
this.eventNode = document;
this.getFrameDurationScale = getFrameDurationScale;
this.raycaster = new THREE.Raycaster();
......@@ -56,8 +55,8 @@ module.exports = class Renderer_Helper_FPS_Controls extends Base
if(e.type != "mousemove")
{
this.mouseDown = e.type == "mousedown";
}else{
this.mouseDown = (e.type == "mousedown") && (e.path[0] == this.canvasNode);
}else if(this.mouseDown){
let canvasRect = this.canvasNode.getBoundingClientRect();
this.mousePosNorm.x = e.clientX - canvasRect.left;
......@@ -74,22 +73,21 @@ module.exports = class Renderer_Helper_FPS_Controls extends Base
this.mousePosNorm.x = (this.mousePosNorm.x / canvasRect.width) * 2 - 1;
this.mousePosNorm.y = -(this.mousePosNorm.y / canvasRect.height) * 2 + 1;
if(this.mouseDown)
{
let rotX = e.movementY * this.camRotSpeed * this.invertedX;
let rotY = e.movementX * this.camRotSpeed * this.invertedY;
let rotX = e.movementY * this.camRotSpeed * this.invertedX;
let rotY = e.movementX * this.camRotSpeed * this.invertedY;
if(rotX != 0.0)this.camera.rotation.x += rotX;
if(rotY != 0.0)this.camera.rotation.y += rotY;
}
if(rotX != 0.0)this.camera.rotation.x += rotX;
if(rotY != 0.0)this.camera.rotation.y += rotY;
}
}
_handlerKeys(e)
{
let isKeyDown = (e.type == "keydown");
let direction = [0.0, 0.0, 0.0];
if(e.path[0] != this.canvasNode) {
return;
}
let isKeyDown = (e.type == "keydown");
switch(e.code)
{
case "ArrowRight":
......@@ -131,10 +129,10 @@ module.exports = class Renderer_Helper_FPS_Controls extends Base
init()
{
for(let eventName of this.eventNamesKeys)
this.eventNode.addEventListener(eventName, this._handlerKeysWrapper);
this.canvasNode.addEventListener(eventName, this._handlerKeysWrapper);
for(let eventName of this.eventNamesMouse)
this.eventNode.addEventListener(eventName, this._handlerMouseWrapper);
document.addEventListener(eventName, this._handlerMouseWrapper);
this.camera.rotation.order = "YXZ";
}
......@@ -165,10 +163,10 @@ module.exports = class Renderer_Helper_FPS_Controls extends Base
clear()
{
for(let eventName of this.eventNamesKeys)
this.eventNode.removeEventListener(eventName, this._handlerKeysWrapper);
this.canvasNode.removeEventListener(eventName, this._handlerKeysWrapper);
for(let eventName of this.eventNamesMouse)
this.eventNode.removeEventListener(eventName, this._handlerMouseWrapper);
document.removeEventListener(eventName, this._handlerMouseWrapper);
this.camera.rotation.order = this.oldEulerOrder;
}
......
......@@ -14,13 +14,26 @@ module.exports = class Actor
this.params = params;
this.type = type;
this.gui = null;
this.objInstance = objInstance;
this.objInstance.setActor(this);
this.selected = false;
}
getName()
{
return this.params.UnitConfigName.value;
return (this.params.UnitConfigName && this.params.UnitConfigName.value) ? this.params.UnitConfigName.value : "";
}
getHashId()
{
return (this.params.HashId && this.params.HashId.value) ? this.params.HashId.value : 0;
}
getSRTHash()
{
return (this.params.SRTHash && this.params.SRTHash.value) ? this.params.SRTHash.value : 0;
}
setHandler(handler)
......
/**
* @copyright 2018 - Max Bebök
* @author Max Bebök
* @license GNU-GPLv3 - see the "LICENSE" file in the root directory
*/
module.exports = class Actor_Search_Finder
{
constructor(actorHandler, actorEditor)
{
this.actorHandler = actorHandler;
this.actorEditor = actorEditor;
this.result = [];
}
search(searchValue, filter)
{
searchValue = searchValue.toLowerCase();
this.result = [];
for(const actorId in this.actorHandler.actors)
{
const actor = this.actorHandler.actors[actorId];
if(this._checkActor(actor, searchValue, filter)) {
this.result.push(actor);
if(!isNaN(filter.limit) && this.result.length >= filter.limit) {
break;
}
}
}
return this.result;
}
_checkActor(actor, searchValue, filter)
{
const params = actor.params;
if(!["Static", "Dynamic"].includes(actor.type))
return false;
if(!filter.static && actor.type == "Static")
return false;
if(!filter.dynamic && actor.type == "Dynamic")
return false;
if(filter.id && actor.getHashId() == searchValue)
return true;
if(filter.srt && actor.getSRTHash() == searchValue)
return true;
let textToCompare = null;
if(filter.name)
textToCompare = actor.getName().toLowerCase();
if(filter.params)
textToCompare = JSON.stringify(params).toLowerCase();
if(textToCompare != null)
{
try{
return (filter.regex)
? new RegExp(searchValue).test(textToCompare)
: textToCompare.includes(searchValue);
}catch(e)
{}
}
return false;
}
selectIndex(actorIndex)
{
actorIndex = parseInt(actorIndex);
if(actorIndex >= 0 && actorIndex < this.result.length)
this.actorEditor.selectActor(this.result[actorIndex]);
}
deselectIndex(actorIndex)
{
actorIndex = parseInt(actorIndex);
if(actorIndex >= 0 && actorIndex < this.result.length)
this.actorEditor.deselectActor(this.result[actorIndex]);
}
selectAll()
{
for(let actor of this.result)
this.actorEditor.selectActor(actor);
}
};
\ No newline at end of file
/**
* @copyright 2018 - Max Bebök
* @author Max Bebök
* @license GNU-GPLv3 - see the "LICENSE" file in the root directory
*/
const HTML_Loader = require("./../../../html_loader");
const Draggable = require('draggable');
module.exports = class Actor_Search_GUI
{
constructor(uiNode, actorFinder)
{
this.uiNode = uiNode;
this.actorFinder = actorFinder;
this.windowNode = undefined;
this.actorSearchHtml = new HTML_Loader('html/actor_search.html');
this.isOpen = false;
this.filter = {};
this.searchValue = "";
this._initGlobalActions();
}
open()
{
if(!this.isOpen)
{
this._createView();
this._initFilters();
this._initActions();
this.isOpen = true;
}
}
close()
{
this.filter = {};
this.searchValue = "";
this.uiNode.innerHTML = "";
this.isOpen = false;
}
/**
* creates the HTML from the template and appends it
*/
_createView()
{
this.uiNode.appendChild(this.actorSearchHtml.create());
this.windowNode = this.uiNode.querySelector("#searchWindow-actor");
this.dragObj = new Draggable(this.windowNode, {
filterTarget: (target) => target.classList.contains("drag-bar"),
onDragStart: () => this.windowNode.classList.add("isMoving"),
onDragEnd: () => this.windowNode.classList.remove("isMoving"),
useGPU: true
});
this.dragObj.set(
(document.body.clientWidth - this.windowNode.clientWidth) * 0.5,
(document.body.clientHeight - this.windowNode.clientHeight) * 0.5,
);
this.resultNode = this.uiNode.querySelector(".data-resultTable");
this.resultCount = this.uiNode.querySelector(".data-resultCount");
}
/**
* triggers a search action
*/
_triggerSearch()
{
const result = this.actorFinder.search(this.searchValue, this.filter);
let i = 0;
this.resultNode.innerHTML = result.reduce((resultHtml, actor) => {
return resultHtml + `<tr data-actorIndex="${i++}" class="${actor.selected ? "active" : ""}">
<td>${actor.getName()}</td>
<td>${actor.getHashId()}</td>
<td>${actor.getSRTHash()}</td>
<td>${actor.type}</td>
</tr>`;
}, "");
this.resultCount.innerHTML = result.length || 0;
}
_triggerSelectAll()
{
for(let row of this.resultNode.children)
row.classList.add("active");
this.actorFinder.selectAll();
}
/**
* initializes filter callbacks and arrays
*/
_initFilters()
{
this.filter.limit = 100;
const filterNodes = this.windowNode.querySelectorAll("button[data-filter]");
for(let node of filterNodes)
{
const filterName = node.getAttribute("data-filter");
this.filter[filterName] = node.classList.contains("active");
node.onclick = () => {
if(this.filter[filterName] = !this.filter[filterName]) {
node.classList.add("active");
}else{
node.classList.remove("active");
}
this._triggerSearch();
};
}
const searchInput = this.windowNode.querySelector(".data-searchValue");
searchInput.onkeyup = ev => {
this.searchValue = ev.target.value;
this._triggerSearch();
};
searchInput.focus();
this.windowNode.querySelector(".data-limit").onchange = ev => {
this.filter.limit = parseInt(ev.target.value);
this._triggerSearch();
};
}
/**
* inits actions-buttons
*/
_initActions()
{
this.windowNode.querySelector(".data-tool-selectAll").onclick = () => this._triggerSelectAll();
this.windowNode.querySelector(".data-tool-close").onclick = () => this.close();
this.resultNode.onclick = ev => {
for(const node of ev.path) {
if(node instanceof HTMLTableRowElement) {
const actorIndex = node.getAttribute("data-actorIndex");
if(node.classList.contains("active")) {
this.actorFinder.deselectIndex(actorIndex);
node.classList.remove("active");
} else {
this.actorFinder.selectIndex(actorIndex);
node.classList.add("active");
}
}
}
};
}
_initGlobalActions()
{
const actorSearchBtn = document.querySelector(".data-tool-openActorSearch");
if(actorSearchBtn) {
actorSearchBtn.onclick = () => this.open();
}
document.addEventListener("keydown", (ev) => {
if(ev.ctrlKey && ev.key == "f") {
this.close();
this.open();
}
});
}
};
\ No newline at end of file
......@@ -89,10 +89,7 @@ module.exports = class Actor_Editor
{
if(!isMouseUp)
{
if(!this.selectedActors.includes(actor))
{
this._addToSelection(actor);
}
this.selectActor(actor);
}else if(!mouseMoved && !this.actorAdded && this.selectedActors.includes(actor))
{
this.deselectActor(actor);
......@@ -169,9 +166,13 @@ module.exports = class Actor_Editor
this.selectedActors = [];
}
_addToSelection(actor)
selectActor(actor)
{
if(this.selectedActors.includes(actor))
return;
this.selectedActors.push(actor);
actor.selected = true;
actor.setColor([1.0, 0.3, 0.3, 1.0]);
this.actorAdded = true;
......@@ -190,6 +191,7 @@ module.exports = class Actor_Editor
_removeActorSelection(actor)
{
actor.selected = false;
actor.setColor([1.0, 1.0, 1.0, 1.0]);
this.mubinRenderer.deselectActor(actor);
}
......
......@@ -7,6 +7,7 @@
const Mubin_Renderer = require("./renderer/renderer");
const Actor_Handler = require("./actor/handler/handler");
const Actor_Editor = require("./actor_editor/editor");
const Actor_Search_Finder = require("./actor/search/finder");
const Renderer_Settings = require("./renderer/settings");
const Actor_Loader = require("./actor/loader");
......@@ -39,9 +40,13 @@ module.exports = class Mubin_Editor
this.actorLoader = new Actor_Loader(this.actorHandler, (actorType) => this.generateMubinPath(actorType));
this.prodLoader = new PROD_Loader( this.actorHandler, (actorType) => this.generateProdPath(actorType));
this.actorFinder = new Actor_Search_Finder(this.actorHandler, this.actorEditor);
this.loadActorData = true;
this.loadProdData = false;
this.mubinRenderer.init(this.actorFinder);
this.mubinRenderer.renderer.addUpdateCallback(() => this.update());
this.actorHandler.setEditor(this.actorEditor);
......
......@@ -7,6 +7,7 @@
const Renderer = require("./../../3d_renderer/renderer");
const HTML_Loader = require("./../../html_loader");
const Actor_GUI = require("./../actor/gui");
const Actor_Search_GUI = require("./../actor/search/gui");
module.exports = class Mubin_Renderer
{
......@@ -21,14 +22,6 @@ module.exports = class Mubin_Renderer
this.uiNode = uiNode;
this.loader = loader;
this.clear();
}
clear()
{
if(this.renderer != null)
this.renderer.clear();
this.history = undefined;
this.renderer = new Renderer(this.canvasNode);