+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
cavas
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/main.py b/main.py
new file mode 100644
index 0000000..829a812
--- /dev/null
+++ b/main.py
@@ -0,0 +1,238 @@
+import random
+import string
+
+import cherrypy
+import os
+import json
+from jinja2 import Environment, FileSystemLoader
+env = Environment(loader=FileSystemLoader('./'))
+
+import os
+import sys
+import scene_reader
+from tools import check_labels as check
+
+
+# BASE_DIR = os.path.dirname(os.path.abspath(__file__))
+# sys.path.append(BASE_DIR)
+
+#sys.path.append(os.path.join(BASE_DIR, './algos'))
+#import algos.rotation as rotation
+from algos import pre_annotate
+
+
+#sys.path.append(os.path.join(BASE_DIR, '../tracking'))
+#import algos.trajectory as trajectory
+
+# extract_object_exe = "~/code/pcltest/build/extract_object"
+# registration_exe = "~/code/go_icp_pcl/build/test_go_icp"
+
+# sys.path.append(os.path.join(BASE_DIR, './tools'))
+# import tools.dataset_preprocess.crop_scene as crop_scene
+
+class Root(object):
+ @cherrypy.expose
+ def index(self, scene="", frame=""):
+ tmpl = env.get_template('index.html')
+ return tmpl.render()
+
+ @cherrypy.expose
+ def icon(self):
+ tmpl = env.get_template('test_icon.html')
+ return tmpl.render()
+
+ @cherrypy.expose
+ def ml(self):
+ tmpl = env.get_template('test_ml.html')
+ return tmpl.render()
+
+ @cherrypy.expose
+ def reg(self):
+ tmpl = env.get_template('registration_demo.html')
+ return tmpl.render()
+
+ @cherrypy.expose
+ def view(self, file):
+ tmpl = env.get_template('view.html')
+ return tmpl.render()
+
+ # @cherrypy.expose
+ # def saveworld(self, scene, frame):
+
+ # # cl = cherrypy.request.headers['Content-Length']
+ # rawbody = cherrypy.request.body.readline().decode('UTF-8')
+
+ # with open("./data/"+scene +"/label/"+frame+".json",'w') as f:
+ # f.write(rawbody)
+
+ # return "ok"
+
+ @cherrypy.expose
+ def saveworldlist(self):
+
+ # cl = cherrypy.request.headers['Content-Length']
+ rawbody = cherrypy.request.body.readline().decode('UTF-8')
+ data = json.loads(rawbody)
+
+ for d in data:
+ scene = d["scene"]
+ frame = d["frame"]
+ ann = d["annotation"]
+ with open("./data/"+scene +"/label/"+frame+".json",'w') as f:
+ json.dump(ann, f, indent=2, sort_keys=True)
+
+ return "ok"
+
+
+ @cherrypy.expose
+ @cherrypy.tools.json_out()
+ def cropscene(self):
+ rawbody = cherrypy.request.body.readline().decode('UTF-8')
+ data = json.loads(rawbody)
+
+ rawdata = data["rawSceneId"]
+
+ timestamp = rawdata.split("_")[0]
+
+ print("generate scene")
+ log_file = "temp/crop-scene-"+timestamp+".log"
+
+ cmd = "python ./tools/dataset_preprocess/crop_scene.py generate "+ \
+ rawdata[0:10]+"/"+timestamp + "_preprocessed/dataset_2hz " + \
+ "- " +\
+ data["startTime"] + " " +\
+ data["seconds"] + " " +\
+ "\""+ data["desc"] + "\"" +\
+ "> " + log_file + " 2>&1"
+ print(cmd)
+
+ code = os.system(cmd)
+
+ with open(log_file) as f:
+ log = list(map(lambda s: s.strip(), f.readlines()))
+
+ os.system("rm "+log_file)
+
+ return {"code": code,
+ "log": log
+ }
+
+
+ @cherrypy.expose
+ @cherrypy.tools.json_out()
+ def checkscene(self, scene):
+ ck = check.LabelChecker(os.path.join("./data", scene))
+ ck.check()
+ print(ck.messages)
+ return ck.messages
+
+
+ # data N*3 numpy array
+ @cherrypy.expose
+ @cherrypy.tools.json_out()
+ def predict_rotation(self):
+ cl = cherrypy.request.headers['Content-Length']
+ rawbody = cherrypy.request.body.readline().decode('UTF-8')
+
+ data = json.loads(rawbody)
+
+ return {"angle": pre_annotate.predict_yaw(data["points"])}
+ #return {}
+
+
+ @cherrypy.expose
+ @cherrypy.tools.json_out()
+ def auto_annotate(self, scene, frame):
+ print("auto annotate ", scene, frame)
+ return pre_annotate.annotate_file('./data/{}/lidar/{}.pcd'.format(scene,frame))
+
+
+
+ @cherrypy.expose
+ @cherrypy.tools.json_out()
+ def load_annotation(self, scene, frame):
+ return scene_reader.read_annotations(scene, frame)
+
+
+ @cherrypy.expose
+ @cherrypy.tools.json_out()
+ def load_ego_pose(self, scene, frame):
+ return scene_reader.read_ego_pose(scene, frame)
+
+
+ @cherrypy.expose
+ @cherrypy.tools.json_out()
+ def loadworldlist(self):
+ rawbody = cherrypy.request.body.readline().decode('UTF-8')
+ worldlist = json.loads(rawbody)
+
+ anns = list(map(lambda w:{
+ "scene": w["scene"],
+ "frame": w["frame"],
+ "annotation":scene_reader.read_annotations(w["scene"], w["frame"])},
+ worldlist))
+
+ return anns
+
+
+ @cherrypy.expose
+ @cherrypy.tools.json_out()
+ def datameta(self):
+ return scene_reader.get_all_scenes()
+
+
+ @cherrypy.expose
+ @cherrypy.tools.json_out()
+ def scenemeta(self, scene):
+ return scene_reader.get_one_scene(scene)
+
+ @cherrypy.expose
+ @cherrypy.tools.json_out()
+ def get_all_scene_desc(self):
+ return scene_reader.get_all_scene_desc()
+
+ @cherrypy.expose
+ @cherrypy.tools.json_out()
+ def objs_of_scene(self, scene):
+ return self.get_all_objs(os.path.join("./data",scene))
+
+ def get_all_objs(self, path):
+ label_folder = os.path.join(path, "label")
+ if not os.path.isdir(label_folder):
+ return []
+
+ files = os.listdir(label_folder)
+
+ files = filter(lambda x: x.split(".")[-1]=="json", files)
+
+
+ def file_2_objs(f):
+ with open(f) as fd:
+ boxes = json.load(fd)
+ objs = [x for x in map(lambda b: {"category":b["obj_type"], "id": b["obj_id"]}, boxes)]
+ return objs
+
+ boxes = map(lambda f: file_2_objs(os.path.join(path, "label", f)), files)
+
+ # the following map makes the category-id pairs unique in scene
+ all_objs={}
+ for x in boxes:
+ for o in x:
+
+ k = str(o["category"])+"-"+str(o["id"])
+
+ if all_objs.get(k):
+ all_objs[k]['count']= all_objs[k]['count']+1
+ else:
+ all_objs[k]= {
+ "category": o["category"],
+ "id": o["id"],
+ "count": 1
+ }
+
+ return [x for x in all_objs.values()]
+
+if __name__ == '__main__':
+ cherrypy.quickstart(Root(), '/', config="server.conf")
+else:
+ application = cherrypy.Application(Root(), '/', config="server.conf")
diff --git a/main2.py b/main2.py
new file mode 100644
index 0000000..a916eaf
--- /dev/null
+++ b/main2.py
@@ -0,0 +1,79 @@
+from fastapi import FastAPI, Request
+from fastapi.staticfiles import StaticFiles
+from fastapi.templating import Jinja2Templates
+
+from router import router
+
+# 1. 创建 FastAPI 应用实例
+app = FastAPI(
+ title="Annotation Tool API",
+ description="从 CherryPy 转换而来的标注工具 API",
+ version="2.0.0"
+)
+
+# 2. 设置 Jinja2 模板
+# CherryPy 的 FileSystemLoader('./') 对应 FastAPI 的 directory="."
+templates = Jinja2Templates(directory=".")
+
+# 3. 挂载静态文件 (对应 server.conf)
+# app.mount("路径", StaticFiles(directory="本地目录"), name="唯一名称")
+app.mount("/static", StaticFiles(directory="public"), name="public")
+app.mount("/data", StaticFiles(directory="data"), name="data")
+app.mount("/temp", StaticFiles(directory="temp"), name="temp")
+app.mount("/views", StaticFiles(directory="views"), name="views")
+app.mount("/assets", StaticFiles(directory="assets"), name="assets")
+
+
+# 4. 定义 Pydantic 模型用于请求体验证
+# 这比直接解析 JSON 更安全、更清晰
+
+# 5. 将 CherryPy 的类方法转换为 FastAPI 路由函数
+
+# --- HTML 页面路由 ---
+
+@app.get("/icon")
+def icon(request: Request):
+ """渲染测试图标页"""
+ return templates.TemplateResponse("test_icon.html", {"request": request})
+
+
+@app.get("/ml")
+def ml(request: Request):
+ """渲染测试 ML 页"""
+ return templates.TemplateResponse("test_ml.html", {"request": request})
+
+
+@app.get("/reg")
+def reg(request: Request):
+ """渲染注册演示页"""
+ return templates.TemplateResponse("registration_demo.html", {"request": request})
+
+
+@app.get("/view/{file_path:path}")
+def view(request: Request, file_path: str):
+ """渲染查看页,:path 允许路径中包含斜杠"""
+ # 原始代码没有使用 file 参数,这里保持一致
+ return templates.TemplateResponse("view.html", {"request": request})
+
+
+# --- API 接口路由 ---
+
+@app.get("/")
+def index(request: Request, scene: str = "", frame: str = ""):
+ """渲染主页"""
+ return templates.TemplateResponse("index.html", {"request": request})
+
+
+app.include_router(router)
+# 6. 启动服务器 (对应 if __name__ == '__main__')
+# 在命令行运行: uvicorn main:app --host 0.0.0.0 --port 8081 --reload
+if __name__ == "__main__":
+ import uvicorn
+
+ print("Starting FastAPI server...")
+ # server.conf 中的 host 和 port 在这里配置
+ uvicorn.run(
+ app,
+ host="0.0.0.0",
+ port=8081
+ )
diff --git a/notes.md b/notes.md
new file mode 100644
index 0000000..0f98932
--- /dev/null
+++ b/notes.md
@@ -0,0 +1,9 @@
+1. the views on the left are 3 orthographical cameras installed in the scene according to the selected box. keys 5/6/7 to show/hide the installed camera frames.
+
+2. we use rear-view, other than front-view, so that when we adjust the position of box, this view moves
+in the same direction as the top-down view, otherwise it would be very confusing (imagine the box in the
+first view moves right but in the second view moves left.)
+
+2. the main view is a perspective camera by default.
+
+
diff --git a/public/css/font-awesome.min.css b/public/css/font-awesome.min.css
new file mode 100644
index 0000000..540440c
--- /dev/null
+++ b/public/css/font-awesome.min.css
@@ -0,0 +1,4 @@
+/*!
+ * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome
+ * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
+ */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.7.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.fa-handshake-o:before{content:"\f2b5"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-o:before{content:"\f2b7"}.fa-linode:before{content:"\f2b8"}.fa-address-book:before{content:"\f2b9"}.fa-address-book-o:before{content:"\f2ba"}.fa-vcard:before,.fa-address-card:before{content:"\f2bb"}.fa-vcard-o:before,.fa-address-card-o:before{content:"\f2bc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-circle-o:before{content:"\f2be"}.fa-user-o:before{content:"\f2c0"}.fa-id-badge:before{content:"\f2c1"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\f2c3"}.fa-quora:before{content:"\f2c4"}.fa-free-code-camp:before{content:"\f2c5"}.fa-telegram:before{content:"\f2c6"}.fa-thermometer-4:before,.fa-thermometer:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-shower:before{content:"\f2cc"}.fa-bathtub:before,.fa-s15:before,.fa-bath:before{content:"\f2cd"}.fa-podcast:before{content:"\f2ce"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\f2d3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\f2d4"}.fa-bandcamp:before{content:"\f2d5"}.fa-grav:before{content:"\f2d6"}.fa-etsy:before{content:"\f2d7"}.fa-imdb:before{content:"\f2d8"}.fa-ravelry:before{content:"\f2d9"}.fa-eercast:before{content:"\f2da"}.fa-microchip:before{content:"\f2db"}.fa-snowflake-o:before{content:"\f2dc"}.fa-superpowers:before{content:"\f2dd"}.fa-wpexplorer:before{content:"\f2de"}.fa-meetup:before{content:"\f2e0"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}
diff --git a/public/css/icon.css b/public/css/icon.css
new file mode 100644
index 0000000..8102dcf
--- /dev/null
+++ b/public/css/icon.css
@@ -0,0 +1,61 @@
+.docIcon
+{
+ background:#eee;
+ background: linear-gradient(top, #ddd 0, #eee 15%, #fff 40%, #fff 70%, #eee 100%);
+ border:1px solid #ccc;
+ display:block;
+ width:40px;
+ height:56px;
+ position:relative;
+ margin:0px auto;
+ box-shadow:inset rgba(255,255,255,0.8) 0 1px 1px;
+
+ text-indent:-9999em;
+
+ border-radius:0px 15px 8px 3px;
+}
+.docIcon:before {
+ content: "";
+ display: block;
+ position: absolute;
+ top: 0;
+ right: 0;
+ width: 15px;
+ height: 15px;
+ background: #ccc;
+
+ background: -webkit-linear-gradient(45deg, #fff 0, #eee 50%, #ccc 100%);
+ background: -moz-linear-gradient(45deg, #fff 0, #eee 50%, #ccc 100%);
+ background: -o-linear-gradient(45deg, #fff 0, #eee 50%, #ccc 100%);
+ background: -ms-linear-gradient(45deg, #fff 0, #eee 50%, #ccc 100%);
+ background: linear-gradient(45deg, #fff 0, #eee 50%, #ccc 100%);
+
+ -webkit-box-shadow: rgba(0,0,0,0.05) -1px 1px 1px, inset white 0 0 1px;
+ -moz-box-shadow: rgba(0,0,0,0.05) -1px 1px 1px, inset white 0 0 1px;
+ box-shadow: rgba(0,0,0,0.05) -1px 1px 1px, inset white 0 0 1px;
+
+ border-bottom: 1px solid #ccc;
+ border-left: 1px solid #ccc;
+ -webkit-border-radius:3px 15px 3px 3px;
+ -moz-border-radius:3px 15px 3px 3px;
+ border-radius:3px 15px 3px 3px;
+}
+
+.docIcon:after
+{
+ content:"";
+ display:block;
+ position:absolute;
+ left:0;
+ top:0;
+ width:60%;
+ margin:22px 20% 0;
+ height:15px;
+
+ background:#ccc;
+ background: -webkit-linear-gradient(top, #ccc 0, #ccc 20%, #fff 20%, #fff 40%, #ccc 40%, #ccc 60%, #fff 60%, #fff 80%, #ccc 80%, #ccc 100%);
+ background: -moz-linear-gradient(top, #ccc 0, #ccc 20%, #fff 20%, #fff 40%, #ccc 40%, #ccc 60%, #fff 60%, #fff 80%, #ccc 80%, #ccc 100%);
+ background: -o-linear-gradient(top, #ccc 0, #ccc 20%, #fff 20%, #fff 40%, #ccc 40%, #ccc 60%, #fff 60%, #fff 80%, #ccc 80%, #ccc 100%);
+ background: -ms-linear-gradient(top, #ccc 0, #ccc 20%, #fff 20%, #fff 40%, #ccc 40%, #ccc 60%, #fff 60%, #fff 80%, #ccc 80%, #ccc 100%);
+ background:linear-gradient(top, #ccc 0, #ccc 20%, #fff 20%, #fff 40%, #ccc 40%, #ccc 60%, #fff 60%, #fff 80%, #ccc 80%, #ccc 100%);
+}
\ No newline at end of file
diff --git a/public/css/main.css b/public/css/main.css
new file mode 100644
index 0000000..719d2cc
--- /dev/null
+++ b/public/css/main.css
@@ -0,0 +1,1083 @@
+
+
+.theme-dark {
+ --font-color: #ffffff;
+ --background-color: #121212;
+ --widget-background-color: #333333;
+ --window-background-color: #222222;
+ --widget-color: gray;
+ --highlight-background-color: darkgray;
+ --highlight-color: yellow;
+ --highlight-color-editor: #ffff0020;
+
+ --highlight-background-transparent-color: #aaaaaa44;
+}
+
+.theme-light {
+ --font-color: #000000;
+ --background-color: #ffffff;
+ --widget-background-color: #cccccc;
+ --window-background-color: #dddddd;
+ --widget-color: darkgray;
+ --highlight-background-color: gray;
+ --highlight-color: blue;
+ --highlight-color-editor: #0000ff20;
+
+ --highlight-background-transparent-color: #22222244;
+}
+
+html,body {
+ margin: 0;
+ height: 100%;
+ width: 100%;
+ background-color: var(--background-color);
+ color: var(--font-color);
+ /*font-family: Monospace;*/
+ font-family: sans-serif, 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana;
+ font-size: calc(1em * 0.875);
+ line-height: 24px;
+ overflow-x: hidden;
+ overflow-y: hidden;
+}
+
+
+
+.red {
+ color: red;
+}
+
+
+a {
+ color: var(--font-color);
+ text-decoration: none;
+ /* pointer-events: auto; */
+}
+
+a:hover {
+ text-decoration: underline;
+}
+
+button {
+ cursor: pointer;
+ /*text-transform: uppercase;*/
+}
+
+canvas {
+ display: block;
+}
+
+
+
+#main-editor{
+ position: absolute;
+ top: 0%;
+ left: 0%;
+ width: 100%;
+ height: 100%;
+}
+
+#batch-editor{
+ display: none;
+ position: absolute;
+ top: 0%;
+ left: 0%;
+ width: 100%;
+ height: 100%;
+}
+
+#main-ui{
+ position: relative;
+ width: 100%;
+ height: 100%;
+ /*overflow: hidden;*/
+
+}
+
+#main-ui #content {
+ position:relative;
+ /* height: 90%; */
+}
+
+#container{
+ position: relative;
+ width: 100%;
+ /* height: 100%; */
+}
+
+#global-info {
+ /* position: absolute; */
+ top: 0px;
+ color: var(--font-color);
+ background-color: var(--widget-background-color);
+ width: auto;
+ font-size: inherit;
+ padding: 0px;
+ box-sizing: border-box;
+ text-align: left;
+ z-index: 3; /* TODO Solve this in HTML */
+ font-size: smaller;
+ display: flex;
+
+}
+
+
+#selectors {
+ background-color: inherit;
+ color: inherit;
+ border-width: 0;
+ display: inline-flex;
+}
+
+#object-category-selector,
+#object-track-id-editor, #attr-input, #scene-selector,
+#frame-selector, #object-selector,
+#camera-selector, #obj-ids-of-scene
+{
+ background-color: inherit;
+ color: inherit;
+ border-width: 0;
+ display: inline-block;
+}
+
+#camera-selector {
+ position: relative;
+ padding-right: 5px;
+ padding-left: 5px;
+}
+
+#camera-list {
+ position: absolute;
+ top: 100%;
+ left: 0%;
+ background-color: inherit;
+ width: 250px;
+ z-index: 1;
+}
+
+.camera-item {
+ -webkit-user-select: none; /* Safari */
+ -moz-user-select: none; /* Firefox */
+ -ms-user-select: none; /* IE10+/Edge */
+ user-select: none; /* Standard */
+
+ float: left;
+ padding-left: 5px;
+ padding-right: 5px;
+}
+
+.camera-selected {
+ color: var(--highlight-color);
+}
+
+.camera-item:hover {
+ background-color: var(--highlight-background-color);
+}
+
+
+#object-track-id-editor, #attr-input
+{
+ border-bottom-width: 1px;
+}
+
+#attr-input
+{
+ margin-right: 5px;
+ line-height: 24px;
+}
+#object-category-attribute, #obj-ids-of-scene{
+ display: none;
+}
+
+
+.alarm-mark:hover {
+ background-color: lightgray;
+}
+
+
+#static-buttons {
+ display: flex;
+}
+
+#changed-mark {
+ position: relative;
+}
+
+#log-button {
+ stroke-width: 5px;
+ display: none;
+}
+
+#log-wrapper #tabs{
+ display: flex;
+}
+
+#log-wrapper .tab-button{
+ color: var(--widget-color);
+ text-align: center;
+ width: 40px;
+ cursor:default;
+}
+
+#log-wrapper .tab-selected{
+ color: var(--font-color);
+ border-bottom-color: var(--font-color);
+ border-bottom-style: solid;
+ border-bottom-width: 1px;
+}
+
+#changed-world-list-wrapper {
+ position: absolute;
+ top: 100%;
+ right: 0%;
+ display: none;
+ width: 400px;
+ background-color: var(--background-color);
+ line-height: 1.5;
+ z-index: 1;
+}
+
+.modified-world-item {
+ padding: 0;
+ border: 0;
+ margin: 0;
+}
+
+#header #buttons
+{
+ display: flex;
+ position: absolute;
+ right: 0;
+ top: 0;
+}
+
+#config-button {
+ width: 20px;
+ height: 20px;
+}
+
+/* .header-button {
+ height: 20px;
+ width: 20px;
+ padding: 2px;
+} */
+
+
+
+#maincanvas-svg {
+ position: absolute;
+ top: 0%;
+ width: 100%;
+ height:100%;
+ border: 0px;
+ padding: 0px;
+}
+
+.image-wrapper #move-handle{
+ position: absolute;
+ top: 0%;
+ left: 0%;
+ width: 100%;
+ height: 90%;
+}
+
+.image-wrapper #header{
+ position: absolute;
+ top: 0%;
+ left: 0%;
+ color: red;
+ padding-left: 5px;
+}
+
+/* line {
+ stroke-width: 2;
+} */
+
+/* .box-svg :hover{
+ stroke-width: 5px;
+} */
+
+
+.box-svg{
+ stroke-width: 3px;
+}
+
+/*
+this css can be moved to dynamic css in main.js, after obj-type css being set
+for now when a box is selected, its objtype class in css is removed so the color is set by selected state.
+dynamic set obj-type css has higher priority than this one.
+*/
+.box-svg-selected{
+ stroke: #ff00ff88;
+ fill: #ff00ff22;
+ stroke-width: 2px;
+}
+
+.maincanvas-line{
+ stroke: #00ff0088;
+ stroke-width: 2px;
+ fill: #00ff0022;
+}
+
+.radar-points{
+ stroke: #ff0000aa;
+ stroke-width: 2px;
+ fill: #ff0000aa;
+}
+
+.radar-tracks{
+ stroke: #00ff00aa;
+ stroke-width: 2px;
+ fill: #00ff00aa;
+
+}
+
+.image-wrapper {
+ position: absolute;
+ resize:both;
+ overflow:hidden;
+ top: 0%;
+ left: 20%;
+ width: 30%;
+ height: 30%;
+ padding: 0px;
+ margin: 0px;
+}
+
+
+#resize-handle {
+ position: absolute;
+ width: 30px;
+ height: 30px;
+ right: 0px;
+ bottom: 0px;
+}
+#resize-handle:hover{
+ cursor: se-resize;
+}
+
+
+#maincanvas-c {
+ position: relative;
+ top: 0%;
+ left: 0%;
+ width: inherit;
+ height: 100%;
+
+ padding: 0px;
+ box-sizing: border-box;
+ text-align: left;
+ align-content: left;
+ z-index: 1; /* TODO Solve this in HTML */
+}
+
+
+#obj-editor{
+ position: absolute;
+ /*color: #ff00ff; */
+ background-color:var(--widget-background-color);
+ font-size: inherit;
+ padding: 0px;
+ box-sizing: border-box;
+ text-align: left;
+ display: none; /* defult hidden */
+}
+
+#obj-label {
+ display: none;
+}
+
+
+#attr-editor {
+ position: relative;
+}
+
+#attr-selector {
+ position: absolute;
+ top: 100%;
+ left: 0%;
+ background-color: var(--widget-background-color);
+ width: 250px;
+}
+
+.attr-item {
+ -webkit-user-select: none; /* Safari */
+ -moz-user-select: none; /* Firefox */
+ -ms-user-select: none; /* IE10+/Edge */
+ user-select: none; /* Standard */
+
+ float: left;
+ padding-left: 5px;
+ padding-right: 5px;
+}
+
+.attr-selected {
+ color: var(--highlight-color);
+}
+
+.attr-item:hover {
+ background-color: var(--highlight-background-color);
+}
+
+#camera {
+ width:100%;
+ height:100%;
+}
+
+#main-view-grid {
+ position: absolute;
+ top: 0%;
+ left: 0%;
+ height: 100%;
+ width: 100%;
+}
+
+#context-menu-wrapper {
+ position: absolute;
+ display: none;
+ top: 0%;
+ left: 0%;
+ height: 100%;
+ width: 100%;
+ z-index: 10;
+ line-height: 1.5;
+}
+
+#context-menu, #object-context-menu, #box-editor-context-menu, #box-editor-manager-context-menu, #config-menu {
+ position: absolute;
+ display: block;
+ background-color: var(--widget-background-color);
+}
+
+#new-submenu, #saveall-submenu, #play-submenu, #goto-submenu, #cm-fit-submenu, #cm-this-submenu {
+ display: none;
+ position: absolute;
+ /* left:100%;
+ top: 0%; */
+ padding-left: 3px;
+ min-width: 150px;
+ background-color: var(--widget-background-color);
+}
+
+#new-submenu {
+ width: 250px;
+}
+
+.cm-new-item {
+ float: left;
+}
+
+.menu-item-arrow {
+ display: inline;
+ position: absolute;
+ right: 0;
+}
+
+.menu-item, .menu-nonclickable-item{
+ padding-left: 10px;
+ padding-right: 10px;
+ position: relative;
+}
+
+#context-menu,#object-context-menu,#box-editor-context-menu, #box-editor-manager-context-menu{
+ width: 150px;
+}
+#config-menu{
+ width: 300px;
+}
+
+#cfg-experimental-submenu, #cfg-data-submenu {
+ display: none;
+ position: absolute;
+ right:100%;
+ top: 0%;
+ padding-left: 3px;
+ min-width: 400px;
+ background-color: var(--widget-background-color);
+}
+
+.cfg-widget-group {
+ display: inline-flex;
+ position: absolute;
+ right: 10px;
+}
+
+.cfg-widget {
+ background-color: var(--widget-background-color);
+ color: var(--font-color);
+}
+
+.menu-button {
+ display: inline;
+ padding-left: 5px;
+ padding-right: 5px;
+}
+
+.menu-button:hover {
+ background-color: var(--highlight-background-color);
+}
+
+.menu-item:hover {
+ background-color: var(--highlight-background-color);
+}
+
+.menu-item-icon {
+ display: inline;
+ height: 20px;
+ width: 20px;
+}
+
+
+.menu-item-text {
+ display: inline;
+}
+
+.menu-seperator {
+ padding-top: 1px;
+ background-color:var(--widget-color);
+}
+
+
+#config-wrapper{
+ display: none;
+ position: absolute;
+ top: 0%;
+ left: 0%;
+ height: 100%;
+ width: 100%;
+}
+
+.dg.ac {
+ -moz-user-select: none;
+ -webkit-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ z-index: 2 !important; /* TODO Solve this in HTML */
+}
+
+
+.float-label {
+ color: #0000ff;
+ position: absolute;
+ display:inline-flex;
+}
+
+
+.non-selectable {
+ -webkit-user-select: none; /* Safari */
+ -moz-user-select: none; /* Firefox */
+ -ms-user-select: none; /* IE10+/Edge */
+ user-select: none; /* Standard */
+}
+
+.label-obj-id-text, .label-obj-type-text, .label-obj-attr-text {
+ padding-left: 5px;
+}
+
+.label-out-view {
+ display: none;
+}
+
+
+
+
+
+
+#batch-editor-tools-wrapper{
+ width:auto;
+ display: flex;
+ justify-content: flex-end;
+}
+
+#batch-editor-tools{
+ background-color: var(--widget-background-color);
+ display: inherit;
+}
+
+.obj-editor-row {
+ display: flex;
+ color: inherit;
+ background: inherit;
+ padding: inherit;
+ margin: 2px;
+}
+
+#label-more {
+ position:relative;
+}
+#object-dropdown-menu {
+ position:absolute;
+ /* top: 100%;
+ left: 0%; */
+ background-color: var(--widget-background-color);
+ width: 250px;
+ line-height: 1.5;
+ z-index: 1;
+ display: none;
+}
+
+.ui-button {
+ background-color: var(--widget-background-color);
+ padding-left: 0;
+ padding-right: 0;
+ border-width: 1;
+ border-color: var(--widget-background-color);
+ color:var(--font-color);
+ height: 20px;
+ width: 20px;
+ padding: 2px;
+}
+
+.ui-button:hover {
+ background-color: var(--highlight-background-color);
+}
+
+.svg-menu-icon{
+ /* pointer-events: none; */
+ display: block;
+ width: 100%;
+ height: 100%;
+ fill:transparent;
+ stroke: var(--font-color);
+ stroke-width: 1px;
+}
+
+.svg-button{
+ /* pointer-events: none; */
+ display: block;
+ width: 100%;
+ height: 100%;
+ fill:transparent;
+ stroke: var(--widget-color);
+ stroke-width: 1px;
+}
+
+.alarm-mark .svg-button{
+ /* pointer-events: none; */
+ display: block;
+ width: 100%;
+ height: 100%;
+ fill:transparent;
+ stroke: red;
+ stroke-width: 1px;
+}
+
+#main-box-editor-wrapper {
+ position: absolute;
+ top: 0%;
+ left: 0%;
+ width: 0%;
+ height: 100%;
+}
+
+
+
+#main-box-editor-wrapper .v-buttons-wrapper {
+ position: absolute;
+ bottom: 0%;
+ right: 0%;
+ background-color: var(--widget-background-color);;
+ margin: 1px;
+ display: none;
+}
+
+
+#main-box-editor-wrapper #sub-views {
+ display: inline-block;
+ position: relative;
+ width: 320px;
+ height: 100%;
+ resize: both;
+ overflow: hidden;
+}
+
+#batch-box-editor{
+ position: absolute;
+ top: 0%;
+ left: 0%;
+ width: 100%;
+ height: 100%;
+ border: 0px;
+}
+
+#batch-box-editor #sub-views {
+ display: inline-block;
+ position: relative;
+ width: 130px;
+ height: 450px;
+ /* resize: none;
+ overflow: hidden; */
+}
+
+#batch-box-editor .selected {
+ background-color: var(--highlight-color-editor);
+ color:var(--highlight-color);
+}
+
+#batch-box-editor .v-buttons-wrapper {
+ display: none;
+ position: absolute;
+ bottom: 0%;
+ right: 0%;
+ background-color:var(--widget-background-color);
+ margin: 0;
+}
+
+#batch-box-editor .ui-button {
+ display: block;
+ background-color:var(--widget-background-color);
+ padding-left: 0;
+ padding-right: 0;
+ border-width: 1;
+ border-color: var(--widget-background-color);
+ color: yellow;
+ padding: 0;
+ border: 0;
+ margin: 0;
+ height: auto;
+}
+
+#batch-box-editor .ui-button:hover {
+ background-color: var(--highlight-background-color);;
+}
+
+
+#box-info{
+ position: absolute;
+ top: 0px;
+ /*color: #ffff00; */
+ /* background-color: var(--widget-background-color);; */
+ width: 100%;
+ padding: 0px;
+ box-sizing: border-box;
+ text-align: left;
+ /*z-index: 3; /* TODO Solve this in HTML */
+ font-size: x-small;
+}
+
+.selected #box-info{
+ background-color: var(--highlight-background-transparent-color);
+}
+
+.view-manipulator {
+ width: 100%;
+ /*background-color: #00000000;*/
+ border-width: 1px;
+ position: relative;
+ /* resize:both; */
+ overflow: hidden;
+}
+
+#z-view-manipulator {
+ height: 35%;
+}
+
+#y-view-manipulator {
+ height: 20%;
+}
+
+#x-view-manipulator {
+ height: 20%;
+}
+
+
+#focuscanvas {
+ color: #ffff00;
+ width: 100%;
+ height: 25%;
+ padding: 0px;
+ box-sizing: border-box;
+ text-align: left;
+ align-content: left;
+ z-index: 1; /* TODO Solve this in HTML */
+}
+
+#view-manipulator:hover{
+ /* border-color: yellow; */
+
+}
+
+
+.ew-handle:hover{
+ cursor: ew-resize;
+}
+
+.ns-handle:hover{
+ cursor: ns-resize;
+}
+
+
+.nw-handle:hover{
+ cursor: nw-resize;
+}
+.ne-handle:hover{
+ cursor: ne-resize;
+}
+
+.sw-handle:hover{
+ cursor: sw-resize;
+}
+
+.se-handle:hover{
+ cursor: se-resize;
+}
+
+.grab-handle:hover{
+ cursor: move;
+}
+
+.v-table-wrapper {
+ display: none;
+}
+
+
+td {
+ padding: 0px;
+}
+
+
+
+.svg-line{
+ stroke-dasharray: 3,3;
+ stroke: #00000000;
+ stroke-width: 1px;
+ stroke-opacity: 0.6;
+}
+
+
+.subview-svg, .subview-obj-size{
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ top: 0%;
+ left: 0%;
+}
+
+.subview-obj-size .obj-vertical-size {
+ position: absolute;
+ top: 50%;
+ left: 0%;
+}
+
+
+.origin-point-indicator{
+ position: relative;
+ stroke: #ff0000;
+ stroke-width: 1px;
+ stroke-opacity: 0.7;
+ fill-opacity: 0.0;
+}
+
+#select-box {
+ display: none;
+ position: absolute;
+ border: 1px solid #55aaff;
+ background-color: rgba(75, 160, 255, 0.3);
+
+}
+
+#main-view-svg{
+ position: absolute;
+ top: 0%;
+ left: 0%;
+ height: 100%;
+ width: 100%;
+}
+#grid-lines-wrapper{
+ position: relative;
+}
+.grid-line {
+
+ stroke-dasharray: 3,3;
+ stroke: gray;
+ stroke-width: 1px;
+ stroke-opacity: 0.2;
+}
+
+
+
+/* */
+
+.popup-window-wrapper #view {
+ position: relative;
+ left: 20%;
+ top: 20%;
+
+ height: 60%;
+ width: 60%;
+ background-color: var(--window-background-color);
+ border: 1px;
+ border-color: gray;
+ resize: both;
+ overflow: hidden;
+ z-index: 4;
+}
+
+
+.popup-window-wrapper #header {
+ background-color: var(--widget-background-color);
+ min-width: 400px; /*minwidth of dialog*/
+}
+
+.popup-window-wrapper #title {
+ padding-left: 5px;
+}
+
+
+.popup-window-wrapper #buttons {
+ display: inline-flex;
+ float: right;
+}
+
+.popup-window-wrapper #btn-restore {
+ display: none;
+}
+
+
+.popup-window-wrapper {
+ position: absolute;
+ left: 0%;
+ top: 0%;
+ width: 100%;
+ height: 100%;
+ display: none;
+
+}
+
+
+/* trajectory */
+#object-track-svg {
+ width: 100%;
+ height:100%;
+ border: 0px;
+ padding: 0px;
+}
+
+#svg-arrows {
+ stroke: red;
+ fill:none;
+}
+
+
+#svg-scaler {
+ stroke: gray;
+ fill:none;
+}
+
+
+#track-ego-car {
+ stroke: green;
+}
+
+.track-label {
+ width: 150px;
+ height: 28px;
+ /* font-size: 18px; */
+ display: none;
+}
+
+.scaler-label{
+ width: 150px;
+ height: 28px;
+}
+
+
+.object-track-current-frame {
+ stroke: purple;
+}
+
+.one-track:hover {
+ color: yellow;
+ stroke: yellow;
+ z-index: 10;
+}
+
+.one-track:hover .track-label{
+ display: inherit;
+}
+
+.track-wrapper {
+ stroke: #00000000;
+ fill: #00000000;
+}
+
+
+
+/* info */
+#info-wrapper {
+ background-color: #88888888;
+}
+
+#info-wrapper #view {
+ height: fit-content;
+ width: fit-content;
+ resize: none;
+}
+
+#info-content {
+ padding-left: 5px;
+ height: fit-content;
+ overflow-wrap: break-word;
+}
+
+#info-wrapper #view #info-bottom-buttons{
+ float: right;
+ padding: 5px;
+}
+
+
+/*crop scene */
+#crop-scene-wrapper {
+ width: 0%;
+ height: 0%;
+}
+
+#crop-scene-wrapper #view {
+ left: 200px;
+ top: 200px;
+
+ height: 400px; /*60%;*/
+ width: 600px; /*60%; */
+}
+
+
+#crop-scene-wrapper #content {
+ padding-left: 5px;
+}
+
+
+#log-wrapper {
+ width: 0%;
+ height: 0%;
+ display: inherit;
+}
+
+#log-wrapper #view {
+ left: 400px;
+ top: 400px;
+ height: 400px; /*60%;*/
+ width: 600px; /*60%; */
+}
+
+#log-wrapper #content-logs,#content-errors {
+ height: 100%;
+ width: 100%;
+ line-height: 1.5;
+ overflow-y: auto;
+}
+
+
+
+.log-object-frame-id{
+ cursor: pointer;
+}
+
+#move-handle-wrapper {
+ position: absolute;
+ display: none;
+ cursor: move;
+ top: 0%;
+ left: 0%;
+ height: 100%;
+ width: 100%;
+ z-index: 10;
+}
\ No newline at end of file
diff --git a/public/css/reg.css b/public/css/reg.css
new file mode 100644
index 0000000..2e8745f
--- /dev/null
+++ b/public/css/reg.css
@@ -0,0 +1,29 @@
+body {
+ margin: 0;
+ background-color: #000;
+ color: #fff;
+ font-family: Monospace;
+ font-size: 13px;
+ line-height: 24px;
+}
+
+
+
+#info {
+ position: absolute;
+ top: 0px;
+ color: #ffff00;
+ font-size: 10px;
+ text-align: left;
+ z-index: 1; /* TODO Solve this in HTML */
+}
+
+td {
+ text-align: right;
+}
+
+.selectBox {
+ border: 1px solid #55aaff;
+ background-color: rgba(75, 160, 255, 0.3);
+ position: fixed;
+}
\ No newline at end of file
diff --git a/public/icon.svg b/public/icon.svg
new file mode 100644
index 0000000..a7c32a0
--- /dev/null
+++ b/public/icon.svg
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/icon2.png b/public/icon2.png
new file mode 100644
index 0000000..cb23fb4
Binary files /dev/null and b/public/icon2.png differ
diff --git a/public/js/annotation.js b/public/js/annotation.js
new file mode 100644
index 0000000..3c8255d
--- /dev/null
+++ b/public/js/annotation.js
@@ -0,0 +1,591 @@
+
+
+import * as THREE from './lib/three.module.js';
+import {globalObjectCategory} from './obj_cfg.js';
+import {saveWorldList} from "./save.js"
+import { intersect } from './util.js';
+
+
+function Annotation(sceneMeta, world, frameInfo){
+ this.world = world;
+ this.data = this.world.data;
+ //this.coordinatesOffset = this.world.coordinatesOffset;
+ this.boxes_load_time = 0;
+ this.frameInfo = frameInfo;
+
+
+ this.modified = false;
+ this.setModified = function(){
+ this.modified=true;
+
+ if (pointsGlobalConfig.autoSave)
+ {
+ saveWorldList([this.world]);
+ }
+ };
+ this.resetModified = function(){this.modified=false;};
+
+
+ this.sort_boxes = function(){
+ this.boxes = this.boxes.sort(function(x,y){
+ return x.position.y - y.position.y;
+ });
+ };
+ this.findBoxByTrackId = function(id){
+ if (this.boxes){
+ let box = this.boxes.find(function(x){
+ return x.obj_track_id == id;
+ });
+ return box;
+ }
+
+ return null;
+ };
+
+ this.findIntersectedBoxes = function(box){
+ return this.boxes.filter(b=>b!=box).filter(b=>intersect(box, b));
+ };
+
+ this.preload = function(on_preload_finished){
+ this.on_preload_finished = on_preload_finished;
+ this.load_annotation((boxes)=>this.proc_annotation(boxes));
+ };
+
+
+
+ this.go_cmd_received = false;
+ this.webglScene = null;
+ this.on_go_finished = null;
+ this.go = function(webglScene, on_go_finished){
+ this.webglScene = webglScene;
+
+ if (this.preloaded){
+
+ //this.boxes.forEach(b=>this.webglScene.add(b));
+ if (this.data.cfg.color_obj != "no"){
+ this.color_boxes();
+ }
+
+ if (on_go_finished)
+ on_go_finished();
+ } else {
+ this.go_cmd_received = true;
+ this.on_go_finished = on_go_finished;
+ }
+ };
+
+
+ // internal funcs below
+ this._afterPreload = function(){
+ this.preloaded = true;
+ console.log("annotation preloaded");
+
+ if (this.on_preload_finished){
+ this.on_preload_finished();
+ }
+ if (this.go_cmd_received){
+ this.go(this.webglScene, this.on_go_finished);
+ }
+ };
+
+
+ this.unload = function(){
+ if (this.boxes){
+ this.boxes.forEach((b)=>{
+ //this.webglGroup.remove(b);
+
+ if (b.boxEditor)
+ b.boxEditor.detach();
+ });
+ }
+ };
+
+ this.deleteAll = function(){
+ this.remove_all_boxes();
+ };
+ this.boxToAnn = function(box){
+ let ann = {
+ psr: {
+ position:{
+ x: box.position.x,
+ y: box.position.y,
+ z: box.position.z,
+ },
+ scale:{
+ x: box.scale.x,
+ y: box.scale.y,
+ z: box.scale.z,
+ },
+ rotation:{
+ x:box.rotation.x,
+ y:box.rotation.y,
+ z:box.rotation.z,
+ },
+ },
+ obj_type: box.obj_type,
+ obj_id: String(box.obj_track_id),
+ obj_attr: box.obj_attr,
+ //vertices: vertices,
+ };
+ return ann;
+ };
+
+ this.toBoxAnnotations = function(){
+ let anns = this.boxes.map((b)=>{
+ //var vertices = psr_to_xyz(b.position, b.scale, b.rotation);
+ let ann = this.boxToAnn(b);
+
+ if (b.annotator)
+ ann.annotator = b.annotator;
+
+ if (b.follows)
+ ann.follows = b.follows;
+ return ann;
+ });
+
+ anns.sort((a,b)=>a.obj_id- b.obj_id);
+
+ return anns;
+ };
+
+
+
+ // to real-world position (no offset)
+ this.ann_to_vector_global = function(box) {
+ let posG = this.world.lidarPosToScene(box.position);
+ let rotG = this.world.lidarRotToScene(box.rotation);
+
+ return [
+ posG.x - this.world.coordinatesOffset[0], posG.y-this.world.coordinatesOffset[1], posG.z-this.world.coordinatesOffset[2],
+ rotG.x, rotG.y, rotG.z,
+ box.scale.x, box.scale.y, box.scale.z,
+ ];
+
+ };
+
+ // real-world position to ann
+ this.vector_global_to_ann = function(v)
+ {
+ let posG = new THREE.Vector3(v[0]+this.world.coordinatesOffset[0],
+ v[1]+this.world.coordinatesOffset[1],
+ v[2]+this.world.coordinatesOffset[2]);
+ let rotG = new THREE.Euler(v[3],v[4],v[5]);
+
+ let rotL = this.world.sceneRotToLidar(rotG);
+ let posL = this.world.scenePosToLidar(posG);
+
+ return {
+ position: {x: posL.x, y: posL.y, z: posL.z},
+ rotation: {x: rotL.x, y: rotL.y, z: rotL.z},
+ scale: {x: v[6], y: v[7], z: v[8]}
+ };
+
+ };
+
+
+ // this.vector_to_ann = function(v){
+ // return {
+ // position:{
+ // x:v[0],// + this.coordinatesOffset[0],
+ // y:v[1],// + this.coordinatesOffset[1],
+ // z:v[2],// + this.coordinatesOffset[2],
+ // },
+
+
+ // rotation:{
+ // x:v[3],
+ // y:v[4],
+ // z:v[5],
+ // },
+
+ // scale:{
+ // x:v[6],
+ // y:v[7],
+ // z:v[8],
+ // },
+
+ // };
+ // };
+
+ this.remove_all_boxes = function(){
+ if (this.boxes){
+ this.boxes.forEach((b)=>{
+ this.webglGroup.remove(b);
+ this.world.data.dbg.free();
+ b.geometry.dispose();
+ b.material.dispose();
+ b.world = null;
+ b.boxEditor = null;
+ });
+
+ this.boxes = [];
+ }
+ else{
+ console.error("destroy empty world!")
+ }
+ };
+
+ this.new_bbox_cube=function(color){
+
+ var h = 0.5;
+
+ var body = [
+ //top
+ -h,h,h, h,h,h,
+ h,h,h, h,-h,h,
+ h,-h,h, -h,-h,h,
+ -h,-h,h, -h, h, h,
+
+ //botom
+ -h,h,-h, h,h,-h,
+ h,h,-h, h,-h,-h,
+ h,-h,-h, -h,-h,-h,
+ -h,-h,-h, -h, h, -h,
+
+ // vertical lines
+ -h,h,h, -h,h,-h,
+ h,h,h, h,h,-h,
+ h,-h,h, h,-h,-h,
+ -h,-h,h, -h,-h,-h,
+
+ //direction
+ h, 0, h, 1.5*h, 0, h,
+ //h/2, -h, h+0.1, h, 0, h+0.1,
+ //h/2, h, h+0.1, h, 0, h+0.1,
+
+ //side direction
+ // h, h/2, h, h, h, 0,
+ // h, h/2, -h, h, h, 0,
+ // h, 0, 0, h, h, 0,
+
+ ];
+
+
+ this.world.data.dbg.alloc();
+
+ var bbox = new THREE.BufferGeometry();
+ bbox.setAttribute( 'position', new THREE.Float32BufferAttribute(body, 3 ) );
+
+ if (!color){
+ color = 0x00ff00;
+ }
+
+ /*
+ https://threejs.org/docs/index.html#api/en/materials/LineBasicMaterial
+ linewidth is 1, regardless of set value.
+ */
+
+
+ var material = new THREE.LineBasicMaterial( { color: color, linewidth: 1, opacity: this.data.cfg.box_opacity, transparent: true } );
+ var box = new THREE.LineSegments( bbox, material );
+
+ box.scale.x=1.8;
+ box.scale.y=4.5;
+ box.scale.z=1.5;
+ box.name="bbox";
+ box.obj_type="car";
+
+ //box.computeLineDistances();
+
+
+
+ return box;
+ };
+
+ this.createCuboid = function(pos, scale, rotation, obj_type, track_id, obj_attr){
+ let mesh = this.new_bbox_cube(parseInt("0x"+globalObjectCategory.get_obj_cfg_by_type(obj_type).color.slice(1)));
+ mesh.position.x = pos.x;
+ mesh.position.y = pos.y;
+ mesh.position.z = pos.z;
+
+ mesh.scale.x = scale.x;
+ mesh.scale.y = scale.y;
+ mesh.scale.z = scale.z;
+
+ mesh.rotation.x = rotation.x;
+ mesh.rotation.y = rotation.y;
+ mesh.rotation.z = rotation.z;
+
+ mesh.obj_track_id = track_id; //tracking id
+ mesh.obj_type = obj_type;
+ mesh.obj_attr = obj_attr;
+ mesh.obj_local_id = this.get_new_box_local_id();
+
+ mesh.world = this.world;
+
+ return mesh;
+ };
+ /*
+ pos: offset position, after transformed
+ */
+
+ this.add_box=function(pos, scale, rotation, obj_type, track_id, obj_attr){
+
+ let mesh = this.createCuboid(pos, scale, rotation, obj_type, track_id, obj_attr)
+
+ this.boxes.push(mesh);
+ this.sort_boxes();
+
+ this.webglGroup.add(mesh);
+
+ return mesh;
+ };
+
+ this.load_box = function(box){
+
+ this.webglGroup.add(box);
+ };
+
+ this.unload_box = function(box){
+
+ this.webglGroup.remove(box);
+ };
+
+ this.remove_box=function(box){
+ this.world.data.dbg.free();
+ box.geometry.dispose();
+ box.material.dispose();
+ //selected_box.dispose();
+ this.boxes = this.boxes.filter(function(x){return x !=box;});
+ };
+
+ this.set_box_opacity=function(box_opacity){
+ this.boxes.forEach(function(x){
+ x.material.opacity = box_opacity;
+ });
+ };
+
+ this.translate_box_position=function(pos, theta, axis, delta){
+ switch (axis){
+ case 'x':
+ pos.x += delta*Math.cos(theta);
+ pos.y += delta*Math.sin(theta);
+ break;
+ case 'y':
+ pos.x += delta*Math.cos(Math.PI/2 + theta);
+ pos.y += delta*Math.sin(Math.PI/2 + theta);
+ break;
+ case 'z':
+ pos.z += delta;
+ break;
+
+ }
+ };
+
+ this.find_boxes_inside_rect = function(x,y,w,h, camera){
+
+ let selected_boxes_by_rect = [];
+
+ if (!this.boxes)
+ return selected_boxes_by_rect;
+
+
+ var p = new THREE.Vector3();
+
+ for (var i=0; i< this.boxes.length; i++){
+ let box_center = this.boxes[i].position;
+
+ let pw = this.world.lidarPosToScene(box_center);
+ p.set(pw.x, pw.y, pw.z);
+ p.project(camera);
+ p.x = p.x/p.z;
+ p.y = p.y/p.z;
+ //console.log(p);
+ if ((p.x > x) && (p.x < x+w) && (p.y>y) && (p.y
this.webglGroup.add(b));
+
+ this.world.webglGroup.add(this.webglGroup);
+
+ this.boxes_load_time = new Date().getTime();
+ console.log(this.boxes_load_time, this.frameInfo.scene, this.frameInfo.frame, "loaded boxes ", this.boxes_load_time - this.create_time, "ms");
+
+ this.sort_boxes();
+
+ this._afterPreload();
+ };
+
+ this.load_annotation=function(on_load){
+ if (this.data.cfg.disableLabels){
+ on_load([]);
+ }else {
+ var xhr = new XMLHttpRequest();
+ // we defined the xhr
+ var _self = this;
+ xhr.onreadystatechange = function () {
+ if (this.readyState != 4) return;
+
+ if (this.status == 200) {
+ let ann = _self.frameInfo.anno_to_boxes(this.responseText);
+ on_load(ann);
+ }
+
+ // end of state change: it can be after some time (async)
+ };
+
+ xhr.open('GET', "/load_annotation"+"?scene="+this.frameInfo.scene+"&frame="+this.frameInfo.frame, true);
+ xhr.send();
+ }
+ };
+
+ this.reloadAnnotation=function(done){
+ this.load_annotation(ann=>{
+ this.reapplyAnnotation(ann, done);
+ });
+ };
+
+
+ this.reapplyAnnotation = function(boxes, done){
+ // these boxes haven't attached a world
+ //boxes = this.transformBoxesByOffset(boxes);
+
+ // mark all old boxes
+ this.boxes.forEach(b=>{b.delete=true;});
+
+ let pendingBoxList=[];
+
+ boxes.forEach(nb=>{ // nb is annotation format, not a true box
+ let old_box = this.boxes.find(function(x){
+ return x.obj_track_id == nb.obj_id && x.obj_track_id != "" && nb.obj_id != "" && x.obj_type == nb.obj_type;;
+ });
+
+ if (old_box){
+ // found
+ // update psr
+ delete old_box.delete; // unmark delete flag
+ old_box.position.set(nb.psr.position.x, nb.psr.position.y, nb.psr.position.z);
+ old_box.scale.set(nb.psr.scale.x, nb.psr.scale.y, nb.psr.scale.z);
+ old_box.rotation.set(nb.psr.rotation.x, nb.psr.rotation.y, nb.psr.rotation.z);
+ old_box.obj_attr = nb.obj_attr;
+ old_box.annotator = nb.annotator;
+ old_box.changed=false; // clear changed flag.
+
+ }else{
+ // not found
+ let box=this.createOneBoxByAnn(nb);
+ pendingBoxList.push(box);
+ }
+ });
+
+ // delete removed
+ let toBeDelBoxes = this.boxes.filter(b=>b.delete);
+ toBeDelBoxes.forEach(b=>{
+ if (b.boxEditor){
+
+ b.boxEditor.detach("donthide");
+ }
+
+ this.webglGroup.remove(b);
+
+ this.remove_box(b);
+ })
+
+ pendingBoxList.forEach(b=>{
+ this.boxes.push(b);
+ })
+
+
+ //todo, restore point color
+ //todo, update imagecontext, selected box, ...
+ //refer to normal delete operation
+ // re-color again
+ this.world.lidar.recolor_all_points();
+
+ this.color_boxes();
+
+ // add new boxes
+ pendingBoxList.forEach(b=>{
+ this.webglGroup.add(b);
+ })
+
+
+ this.resetModified();
+
+ if (done)
+ done();
+
+ }
+
+ this.createOneBoxByAnn = function(annotation){
+ let b = annotation;
+
+ let mesh = this.createCuboid(b.psr.position,
+ b.psr.scale,
+ b.psr.rotation,
+ b.obj_type,
+ b.obj_id,
+ b.obj_attr);
+
+ if (b.annotator){
+ mesh.annotator = b.annotator;
+ }
+
+ if (b.follows)
+ mesh.follows = b.follows;
+
+ return mesh;
+ };
+
+ this.createBoxes = function(annotations){
+ return annotations.map((b)=>{
+ return this.createOneBoxByAnn(b);
+ });
+ };
+
+
+ this.box_local_id = 0;
+ this.get_new_box_local_id=function(){
+ var ret = this.box_local_id;
+ this.box_local_id+=1;
+ return ret;
+ };
+
+
+ this.color_box = function(box)
+ {
+ if (this.data.cfg.color_obj == "category" || this.data.cfg.color_obj == "no")
+ {
+ let color = globalObjectCategory.get_color_by_category(box.obj_type);
+ box.material.color.r=color.x;
+ box.material.color.g=color.y;
+ box.material.color.b=color.z;
+ }
+ else
+ {
+
+ let color = globalObjectCategory.get_color_by_id(box.obj_track_id);
+ box.material.color.r=color.x;
+ box.material.color.g=color.y;
+ box.material.color.b=color.z;
+ }
+ }
+
+ this.color_boxes = function()
+ {
+ this.boxes.forEach(box=>{
+ this.color_box(box);
+ })
+ }
+}
+
+
+export{Annotation}
\ No newline at end of file
diff --git a/public/js/auto-adjust.js b/public/js/auto-adjust.js
new file mode 100644
index 0000000..62483c6
--- /dev/null
+++ b/public/js/auto-adjust.js
@@ -0,0 +1,463 @@
+
+import {transpose, matmul2, euler_angle_to_rotate_matrix_3by3,normalizeAngle } from "./util.js";
+import { logger } from "./log.js";
+
+// todo: this module needs a proper name
+
+function AutoAdjust(boxOp, mouse, header){
+ this.boxOp = boxOp,
+ this.mouse = mouse;
+ this.header = header;
+ var marked_object = null;
+
+ // mark bbox, which will be used as reference-bbox of an object.
+ this.mark_bbox=function(box){
+ if (box){
+ this.marked_object = {
+ frame: box.world.frameInfo.frame,
+ scene: box.world.frameInfo.scene,
+ ann: box.world.annotation.boxToAnn(box),
+ }
+
+ logger.log(`selected reference objcet ${this.marked_object}`);
+
+ this.header.set_ref_obj(this.marked_object);
+ }
+ };
+
+ this.followStaticObjects = function(box) {
+ let world = box.world;
+ let staticObjects = world.annotation.boxes.
+ filter(b=>b!=box && b.obj_attr && b.obj_attr.search('static')>=0).
+ map(refObj=>{
+ let coord = euler_angle_to_rotate_matrix_3by3(refObj.rotation);
+ let trans = transpose(coord, 3);
+ let p = [box.position.x - refObj.position.x,
+ box.position.y - refObj.position.y,
+ box.position.z - refObj.position.z];
+ let relativePos = matmul2(trans, p, 3);
+ let relativeRot = {
+ x: normalizeAngle(box.rotation.x - refObj.rotation.x),
+ y: normalizeAngle(box.rotation.y - refObj.rotation.y),
+ z: normalizeAngle(box.rotation.z - refObj.rotation.z),
+ };
+
+
+ let distance = Math.sqrt(relativePos[0]*relativePos[0] + relativePos[1]*relativePos[1] + relativePos[2]*relativePos[2]);
+ return {
+ obj_track_id: refObj.obj_track_id,
+ relativePos,
+ relativeRot,
+ distance
+
+ }
+ });
+
+ let worldList = box.world.data.worldList;
+ //let saveList = [];
+ worldList.forEach(w=>{
+ if (w === box.world){
+ //current frame
+ return;
+ }
+
+ let existedBox = w.annotation.boxes.find(b=>b.obj_track_id == box.obj_track_id);
+ if (existedBox && !existedBox.annotator)
+ {
+ // have same objects annotated.
+ // if its generated by machine, lets overwrite it
+ return;
+ }
+
+ let candPoseSets = staticObjects.map(refObj=>{
+
+ let refObjInW = w.annotation.boxes.find(b=>b.obj_track_id == refObj.obj_track_id);
+ if (!refObjInW){
+ // not found refobj in this world, give up
+ return null;
+ }
+
+ let relativePos = refObj.relativePos;
+ let relativeRot = refObj.relativeRot;
+
+ let coord = euler_angle_to_rotate_matrix_3by3(refObjInW.rotation);
+
+ let rp = matmul2(coord, relativePos, 3);
+ let newObjPos = {
+ x: refObjInW.position.x + rp[0],
+ y: refObjInW.position.y + rp[1],
+ z: refObjInW.position.z + rp[2],
+ };
+
+ let newObjRot = {
+ x: normalizeAngle(refObjInW.rotation.x + relativeRot.x),
+ y: normalizeAngle(refObjInW.rotation.y + relativeRot.y),
+ z: normalizeAngle(refObjInW.rotation.z + relativeRot.z)
+ };
+
+
+
+ return {
+ distance: refObj.distance,
+ weight: Math.exp(-refObj.distance * (refObjInW.annotator?1:0.1)),
+ position: newObjPos,
+ rotation: newObjRot,
+ };
+ });
+
+ candPoseSets = candPoseSets.filter(p=>!!p);
+
+
+ if (candPoseSets.length == 0) {
+ return;
+ }
+
+ // calculate mean pos/rot
+ let denorm = candPoseSets.reduce((a,b)=>a+b.weight, 0);
+
+ let newObjPos = {x:0, y:0, z:0};
+ let newObjRot = {x:0, y:0, z:0, cosZ: 0, sinZ:0};
+ candPoseSets.forEach(p=>{
+ newObjPos.x += p.position.x * p.weight;
+ newObjPos.y += p.position.y * p.weight;
+ newObjPos.z += p.position.z * p.weight;
+
+ newObjRot.x += p.rotation.x * p.weight;
+ newObjRot.y += p.rotation.y * p.weight;
+ //newObjRot.z += p.rotation.z * p.weight;
+ newObjRot.cosZ += Math.cos(p.rotation.z) * p.weight;
+ newObjRot.sinZ += Math.sin(p.rotation.z) * p.weight;
+ });
+
+ newObjPos.x /= denorm;
+ newObjPos.y /= denorm;
+ newObjPos.z /= denorm;
+ newObjRot.x /= denorm;
+ newObjRot.y /= denorm;
+ newObjRot.cosZ /= denorm;
+ newObjRot.sinZ /= denorm;
+ newObjRot.z = Math.atan2(newObjRot.sinZ, newObjRot.cosZ);
+
+
+ // ignor distant objects
+
+ if (pointsGlobalConfig.ignoreDistantObject){
+ let objDistance = Math.sqrt(newObjPos.x * newObjPos.x + newObjPos.y * newObjPos.y + newObjPos.z * newObjPos.z);
+
+ if ((box.scale.z < 2 && objDistance > 100) || objDistance > 150)
+ {
+ return;
+ }
+ }
+
+ // apply
+ if (existedBox){
+ existedBox.position.x = newObjPos.x;
+ existedBox.position.y = newObjPos.y;
+ existedBox.position.z = newObjPos.z;
+
+ existedBox.rotation.x = newObjRot.x;
+ existedBox.rotation.y = newObjRot.y;
+ existedBox.rotation.z = newObjRot.z;
+
+ existedBox.scale.x = box.scale.x;
+ existedBox.scale.y = box.scale.y;
+ existedBox.scale.z = box.scale.z;
+
+ existedBox.annotator="S";
+
+
+ logger.log(`modified box in ${w}`);
+ } else{
+ let newBox = w.annotation.add_box(newObjPos,
+ box.scale,
+ newObjRot,
+ box.obj_type,
+ box.obj_track_id,
+ box.obj_attr);
+ newBox.annotator="S";
+
+ w.annotation.load_box(newBox);
+ logger.log(`inserted box in ${w}`);
+ }
+
+ console.log("added box in ", w.frameInfo.frame);
+ //saveList.push(w);
+ w.annotation.setModified();
+
+
+ });
+
+ };
+
+ this.followsRef = function(box){
+ //find ref object in current frame
+ let world = box.world;
+ let refObj = world.annotation.boxes.find(b=>b.obj_track_id == this.marked_object.ann.obj_id);
+ if (refObj){
+ console.log("found ref obj in current frame");
+ world.annotation.setModified()
+
+ //compute relative position
+ // represent obj in coordinate system of refobj
+
+ let coord = euler_angle_to_rotate_matrix_3by3(refObj.rotation);
+ let trans = transpose(coord, 3);
+ let p = [box.position.x - refObj.position.x,
+ box.position.y - refObj.position.y,
+ box.position.z - refObj.position.z];
+ const relativePos = matmul2(trans, p, 3);
+ const relativeRot = {
+ x: box.rotation.x - refObj.rotation.x,
+ y: box.rotation.y - refObj.rotation.y,
+ z: box.rotation.z - refObj.rotation.z,
+ };
+
+ let worldList = box.world.data.worldList;
+ //let saveList = [];
+ worldList.forEach(w=>{
+ if (w === box.world){
+ //current frame
+ return;
+ }
+
+ let existedBox = w.annotation.boxes.find(b=>b.obj_track_id == box.obj_track_id);
+
+ if (existedBox && !existedBox.annotator)
+ {
+ // have same objects annotated.
+ // if its generated by machine, lets overwrite it
+ return;
+ }
+
+ let refObjInW = w.annotation.boxes.find(b=>b.obj_track_id == refObj.obj_track_id);
+ if (!refObjInW){
+ // not found refobj in this world, give up
+ return;
+ }
+
+ let coord = euler_angle_to_rotate_matrix_3by3(refObjInW.rotation);
+
+ let rp = matmul2(coord, relativePos, 3);
+ let newObjPos = {
+ x: refObjInW.position.x + rp[0],
+ y: refObjInW.position.y + rp[1],
+ z: refObjInW.position.z + rp[2],
+ };
+
+ let newObjRot = {
+ x: refObjInW.rotation.x + relativeRot.x,
+ y: refObjInW.rotation.y + relativeRot.y,
+ z: refObjInW.rotation.z + relativeRot.z
+ };
+
+ if (existedBox){
+ existedBox.position.x = newObjPos.x;
+ existedBox.position.y = newObjPos.y;
+ existedBox.position.z = newObjPos.z;
+
+ existedBox.rotation.x = newObjRot.x;
+ existedBox.rotation.y = newObjRot.y;
+ existedBox.rotation.z = newObjRot.z;
+
+ existedBox.scale.x = box.scale.x;
+ existedBox.scale.y = box.scale.y;
+ existedBox.scale.z = box.scale.z;
+
+ existedBox.annotator="F";
+ existedBox.follows = {
+ obj_track_id: refObj.obj_track_id,
+ relative_position: {
+ x: relativePos[0],
+ y: relativePos[1],
+ z: relativePos[2],
+ },
+ relative_rotation: relativeRot,
+ };
+
+ logger.log(`modified box in ${w}`);
+ } else{
+ let newBox = w.annotation.add_box(newObjPos,
+ box.scale,
+ newObjRot,
+ box.obj_type,
+ box.obj_track_id,
+ box.obj_attr);
+ newBox.annotator="F";
+ newBox.follows = {
+ obj_track_id: refObj.obj_track_id,
+ relative_position: {
+ x: relativePos[0],
+ y: relativePos[1],
+ z: relativePos[2],
+ },
+ relative_rotation: relativeRot,
+ };
+
+ w.annotation.load_box(newBox);
+ logger.log(`inserted box in ${w}`);
+ }
+
+ console.log("added box in ", w.frameInfo.frame);
+ //saveList.push(w);
+ w.annotation.setModified();
+ });
+
+ //saveWorldList(saveList);
+ }
+ };
+
+ this.syncFollowers = function(box){
+ let world = box.world;
+ let allFollowers = world.annotation.boxes.filter(b=>b.follows && b.follows.obj_track_id === box.obj_track_id);
+
+ if (allFollowers.length == 0){
+ console.log("no followers");
+ return;
+ }
+
+ let refObj = box;
+ let coord = euler_angle_to_rotate_matrix_3by3(refObj.rotation);
+
+
+ allFollowers.forEach(fb=>{
+ let relpos = [fb.follows.relative_position.x,
+ fb.follows.relative_position.y,
+ fb.follows.relative_position.z,
+ ];
+
+ let rp = matmul2(coord, relpos, 3);
+
+ fb.position.x = refObj.position.x + rp[0];
+ fb.position.y = refObj.position.y + rp[1];
+ fb.position.z = refObj.position.z + rp[2];
+
+ fb.rotation.x = refObj.rotation.x + fb.follows.relative_rotation.x;
+ fb.rotation.y = refObj.rotation.y + fb.follows.relative_rotation.y;
+ fb.rotation.z = refObj.rotation.z + fb.follows.relative_rotation.z;
+ });
+ };
+
+ this.paste_bbox=function(pos, add_box){
+
+ if (!pos)
+ pos = this.marked_object.ann.psr.position;
+ else
+ pos.z = this.marked_object.ann.psr.position.z;
+
+ return add_box(pos, this.marked_object.ann.psr.scale, this.marked_object.ann.psr.rotation,
+ this.marked_object.ann.obj_type, this.marked_object.ann.obj_id, this.marked_object.ann.obj_attr);
+ };
+
+
+ // this.auto_adjust_bbox=function(box, done, on_box_changed){
+
+ // saveWorld(function(){
+ // do_adjust(box, on_box_changed);
+ // });
+ // let _self =this;
+ // function do_adjust(box, on_box_changed){
+ // console.log("auto adjust highlighted bbox");
+
+ // var xhr = new XMLHttpRequest();
+ // // we defined the xhr
+
+ // xhr.onreadystatechange = function () {
+ // if (this.readyState != 4) return;
+
+ // if (this.status == 200) {
+ // console.log(this.responseText)
+ // console.log(box.position);
+ // console.log(box.rotation);
+
+
+ // var trans_mat = JSON.parse(this.responseText);
+
+ // var rotation = Math.atan2(trans_mat[4], trans_mat[0]) + box.rotation.z;
+ // var transform = {
+ // x: -trans_mat[3],
+ // y: -trans_mat[7],
+ // z: -trans_mat[11],
+ // }
+
+
+
+ // /*
+ // cos sin x
+ // -sin cos y
+ // */
+ // var new_pos = {
+ // x: Math.cos(-rotation) * transform.x + Math.sin(-rotation) * transform.y,
+ // y: -Math.sin(-rotation) * transform.x + Math.cos(-rotation) * transform.y,
+ // z: transform.z,
+ // };
+
+
+ // box.position.x += new_pos.x;
+ // box.position.y += new_pos.y;
+ // box.position.z += new_pos.z;
+
+
+
+ // box.scale.x = marked_object.scale.x;
+ // box.scale.y = marked_object.scale.y;
+ // box.scale.z = marked_object.scale.z;
+
+ // box.rotation.z -= Math.atan2(trans_mat[4], trans_mat[0]);
+
+ // console.log(box.position);
+ // console.log(box.rotation);
+
+ // on_box_changed(box);
+
+ // _self.header.mark_changed_flag();
+
+ // if (done){
+ // done();
+ // }
+ // }
+
+ // // end of state change: it can be after some time (async)
+ // };
+
+ // xhr.open('GET',
+ // "/auto_adjust"+"?scene="+marked_object.scene + "&"+
+ // "ref_frame=" + marked_object.frame + "&" +
+ // "object_id=" + marked_object.obj_track_id + "&" +
+ // "adj_frame=" + data.world.frameInfo.frame,
+ // true);
+ // xhr.send();
+ // }
+ // };
+
+ this.smart_paste=function(selected_box, add_box, on_box_changed){
+ var box = selected_box;
+ if (!box){
+ let sceneP = this.mouse.get_mouse_location_in_world()
+ // trans pos to world local pos
+ //let pos = this.data.world.scenePosToLidar(sceneP);
+ box = this.paste_bbox(pos, add_box);
+ }
+ else if (this.marked_object){
+ box.scale.x = this.marked_object.ann.psr.scale.x;
+ box.scale.y = this.marked_object.ann.psr.scale.y;
+ box.scale.z = this.marked_object.ann.psr.scale.z;
+ }
+
+ // this.auto_adjust_bbox(box,
+ // function(){saveWorld();},
+ // on_box_changed);
+
+ // this.header.mark_changed_flag();
+
+
+
+ this.boxOp.auto_rotate_xyz(box, null, null,
+ on_box_changed,
+ "noscaling");
+ };
+
+}
+
+
+export {AutoAdjust}
\ No newline at end of file
diff --git a/public/js/auto_annotate.js b/public/js/auto_annotate.js
new file mode 100644
index 0000000..8f51f82
--- /dev/null
+++ b/public/js/auto_annotate.js
@@ -0,0 +1,30 @@
+import { globalObjectCategory } from "./obj_cfg.js";
+
+
+
+function autoAnnotate(world, done, alg){
+ var xhr = new XMLHttpRequest();
+ // we defined the xhr
+ xhr.onreadystatechange = function () {
+ if (this.readyState != 4) return;
+
+ if (this.status == 200) {
+ let anns = JSON.parse(this.responseText);
+
+ anns.map(a=>a.obj_type = globalObjectCategory.guess_obj_type_by_dimension(a.psr.scale));
+
+ // load annotations
+ world.annotation.reapplyAnnotation(anns);
+
+ if (done)
+ done();
+ }
+ };
+
+ xhr.open('GET', "/auto_annotate?"+"scene="+world.frameInfo.scene+"&frame="+world.frameInfo.frame, true);
+
+ xhr.send();
+}
+
+
+export {autoAnnotate}
\ No newline at end of file
diff --git a/public/js/aux_lidar.js b/public/js/aux_lidar.js
new file mode 100644
index 0000000..570a362
--- /dev/null
+++ b/public/js/aux_lidar.js
@@ -0,0 +1,403 @@
+import * as THREE from './lib/three.module.js';
+import { PCDLoader } from './lib/PCDLoader.js';
+import { matmul, euler_angle_to_rotate_matrix_3by3} from "./util.js"
+
+
+//todo: clean arrows
+
+function AuxLidar(sceneMeta, world, frameInfo, auxLidarName){
+ this.world = world;
+ this.frameInfo = frameInfo;
+ this.name = auxLidarName;
+ this.sceneMeta = sceneMeta;
+ this.coordinatesOffset = world.coordinatesOffset;
+
+ this.showPointsOnly = true;
+ this.showCalibBox = false;
+ //this.cssStyleSelector = this.sceneMeta.calib.aux_lidar[this.name].cssstyleselector;
+ this.color = this.sceneMeta.calib.aux_lidar[this.name].color;
+
+
+ if (!this.color)
+ {
+ this.color = [
+ this.world.data.cfg.point_brightness,
+ this.world.data.cfg.point_brightness,
+ this.world.data.cfg.point_brightness,
+ ];
+ }
+
+ this.lidar_points = null; // read from file, centered at 0
+ this.elements = null; // geometry points
+
+ this.preloaded = false;
+ this.loaded = false;
+
+
+ this.go_cmd_received = false;
+ this.webglScene = null;
+ this.on_go_finished = null;
+ this.go = function(webglScene, on_go_finished){
+ this.webglScene = webglScene;
+
+ if (this.preloaded){
+ if (this.elements){
+ this.webglScene.add(this.elements.points);
+
+
+
+ if (this.showCalibBox)
+ this.webglScene.add(this.calib_box);
+ }
+
+ this.loaded = true;
+ if (on_go_finished)
+ on_go_finished();
+ }
+
+ //anyway we save go cmd
+ {
+ this.go_cmd_received = true;
+ this.on_go_finished = on_go_finished;
+ }
+ };
+
+ this.showCalibBox = function(){
+ this.showCalibBox = true;
+ this.webglScene.add(this.calib_box);
+ };
+
+ this.hideCalibBox = function(){
+ this.showCalibBox = false;
+ this.webglScene.remove(this.calib_box);
+ };
+
+ this.get_unoffset_lidar_points = function(){
+ if (this.elements){
+ let pts = this.elements.points.geometry.getAttribute("position").array;
+ return pts.map((p,i)=>p-this.world.coordinatesOffset[i %3]);
+ }
+ else{
+ return [];
+ }
+ };
+
+ // todo: what if it's not preloaded yet
+ this.unload = function(keep_box){
+ if (this.elements){
+ this.webglScene.remove(this.elements.points);
+ if (!this.showPointsOnly)
+ this.elements.arrows.forEach(a=>this.webglScene.remove(a));
+
+ if (!keep_box)
+ this.webglScene.remove(this.calib_box);
+ }
+ this.loaded = false;
+ };
+
+ // todo: its possible to remove points before preloading,
+ this.deleteAll = function(keep_box){
+ if (this.loaded){
+ this.unload();
+ }
+
+ if (this.elements){
+ //this.scene.remove(this.points);
+ this.world.data.dbg.free();
+
+ if (this.elements.points)
+ {
+ this.elements.points.geometry.dispose();
+ this.elements.points.material.dispose();
+ }
+
+ if (this.elements.arrows)
+ {
+ this.elements.arrows.forEach(a=>{
+ this.world.data.dbg.free();
+ a.geometry.dispose();
+ a.material.dispose();
+ })
+ }
+
+ this.elements = null;
+ }
+
+ if (!keep_box && this.calib_box){
+ this.world.data.dbg.free();
+ this.calib_box.geometry.dispose();
+ this.calib_box.material.dispose();
+ this.calib_box = null;
+ }
+ };
+
+ this.filterPoints = function(position){
+ let filtered_position = [];
+
+ if (pointsGlobalConfig.enableFilterPoints)
+ {
+ for(let i = 0; i <= position.length; i+=3)
+ {
+ if (position[i+2] <= pointsGlobalConfig.filterPointsZ)
+ {
+ filtered_position.push(position[i]);
+ filtered_position.push(position[i+1]);
+ filtered_position.push(position[i+2]);
+
+ }
+ }
+ }
+
+ return filtered_position;
+ };
+
+ this.preload = function(on_preload_finished){
+
+ this.on_preload_finished = on_preload_finished;
+
+ var loader = new PCDLoader();
+
+ var _self = this;
+ loader.load( this.frameInfo.get_aux_lidar_path(this.name),
+ //ok
+ function ( pcd ) {
+ var position = pcd.position;
+
+
+ //_self.points_parse_time = new Date().getTime();
+ //console.log(_self.points_load_time, _self.frameInfo.scene, _self.frameInfo.frame, "parse pionts ", _self.points_parse_time - _self.create_time, "ms");
+ _self.lidar_points = position;
+
+ // add one box to calibrate lidar with lidar
+ _self.calib_box = _self.createCalibBox();
+
+ // install callback for box changing
+ _self.calib_box.on_box_changed = ()=>{
+ _self.move_lidar(_self.calib_box);
+ };
+
+ //position = _self.transformPointsByOffset(position);
+ position = _self.move_points(_self.calib_box);
+
+
+
+ let elements = _self.buildGeometry(position);
+
+ _self.elements = elements;
+ //_self.points_backup = mesh;
+
+ _self._afterPreload();
+
+ },
+
+ // on progress,
+ function(){},
+
+ // on error
+ function(){
+ //error
+ console.log("load lidar failed.");
+ _self._afterPreload();
+ },
+
+ // on file loaded
+ function(){
+ //_self.points_readfile_time = new Date().getTime();
+ //console.log(_self.points_load_time, _self.frameInfo.scene, _self.frameInfo.frame, "read file ", _self.points_readfile_time - _self.create_time, "ms");
+ }
+ );
+ };
+
+ // internal funcs below
+ this._afterPreload = function(){
+ this.preloaded = true;
+ console.log(`lidar ${this.auxLidarName} preloaded`);
+ if (this.on_preload_finished){
+ this.on_preload_finished();
+ }
+ if (this.go_cmd_received){
+ this.go(this.webglScene, this.on_go_finished);
+ }
+ };
+
+ this.createCalibBox = function(){
+ if (this.sceneMeta.calib.aux_lidar && this.sceneMeta.calib.aux_lidar[this.name]){
+ return this.world.annotation.createCuboid(
+ {
+ x: this.sceneMeta.calib.aux_lidar[this.name].translation[0] + this.coordinatesOffset[0],
+ y: this.sceneMeta.calib.aux_lidar[this.name].translation[1] + this.coordinatesOffset[1],
+ z: this.sceneMeta.calib.aux_lidar[this.name].translation[2] + this.coordinatesOffset[2],
+ },
+ {x:0.5, y:0.5, z:0.5},
+ {
+ x: this.sceneMeta.calib.aux_lidar[this.name].rotation[0],
+ y: this.sceneMeta.calib.aux_lidar[this.name].rotation[1],
+ z: this.sceneMeta.calib.aux_lidar[this.name].rotation[2],
+ },
+ "lidar",
+ this.name);
+
+ }else {
+ return this.world.annotation.createCuboid(
+ {x: this.coordinatesOffset[0],
+ y: this.coordinatesOffset[1],
+ z: this.coordinatesOffset[2]},
+ {x:0.5, y:0.5, z:0.5},
+ {x:0,y:0,z:0},
+ "lidar",
+ this.name);
+ }
+ };
+
+ this.buildPoints = function(position){
+ // build geometry
+ this.world.data.dbg.alloc();
+ let geometry = new THREE.BufferGeometry();
+ if ( position.length > 0 )
+ geometry.addAttribute( 'position', new THREE.Float32BufferAttribute( position, 3 ) );
+
+
+ let pointColor = this.color;
+ let color=[];
+ for (var i =0; i< position.length; i+=3){
+
+ color.push(pointColor[0]);
+ color.push(pointColor[1]);
+ color.push(pointColor[2]);
+ }
+
+ geometry.addAttribute( 'color', new THREE.Float32BufferAttribute(color, 3 ) );
+
+ geometry.computeBoundingSphere();
+
+ // build material
+ let pointSize = this.sceneMeta.calib.aux_lidar[this.name].point_size;
+ if (!pointSize)
+ pointSize = 1;
+
+ let material = new THREE.PointsMaterial( { size: pointSize, vertexColors: THREE.VertexColors } );
+ //material.size = 2;
+ material.sizeAttenuation = false;
+
+ // build mesh
+ let mesh = new THREE.Points( geometry, material );
+ mesh.name = "lidar";
+
+ return mesh;
+ };
+
+
+ this.buildGeometry = function(position){
+ let points = this.buildPoints(position);
+
+ return {
+ points: points,
+ };
+ };
+
+ this.move_points = function(box){
+ let points = this.lidar_points;
+ let trans = euler_angle_to_rotate_matrix_3by3(box.rotation);
+ let rotated_points = matmul(trans, points, 3);
+ let translation=[box.position.x, box.position.y, box.position.z];
+ let translated_points = rotated_points.map((p,i)=>{
+ return p + translation[i % 3];
+ });
+
+ let filtered_position = this.filterPoints(translated_points);
+ return filtered_position;
+ };
+
+
+
+ this.move_lidar= function(box){
+
+ let translated_points = this.move_points(box);
+
+ let elements = this.buildGeometry(translated_points);
+
+ // remove old points
+ this.unload(true);
+ this.deleteAll(true);
+
+ this.elements = elements;
+ //_self.points_backup = mesh;
+ if (this.go_cmd_received) // this should be always true
+ {
+ this.webglScene.add(this.elements.points);
+ if (!this.showPointsOnly)
+ this.elements.arrows.forEach(a=>this.webglScene.add(a));
+ }
+ };
+}
+
+function AuxLidarManager(sceneMeta, world, frameInfo){
+ this.lidarList = [];
+
+ if (world.data.cfg.enableAuxLidar && sceneMeta.aux_lidar){
+ let lidars = [];
+
+ for (let r in sceneMeta.calib.aux_lidar){
+ if (!sceneMeta.calib.aux_lidar[r].disable)
+ lidars.push(r);
+ }
+
+ this.lidarList = lidars.map(name=>{
+ return new AuxLidar(sceneMeta, world, frameInfo, name);
+ });
+ }
+
+ this.getAllBoxes = function()
+ {
+ if (this.showCalibBox)
+ {
+ return this.lidarList.map(r=>r.calib_box);
+ }
+ else
+ {
+ return [];
+ }
+ };
+
+ this.preloaded = function(){
+ for (let r in this.lidarList){
+ if (!this.lidarList[r].preloaded)
+ return false;
+ }
+ return true;
+ };
+
+ this.go = function(webglScene, on_go_finished){
+ this.lidarList.forEach(r=>r.go(webglScene, on_go_finished));
+ };
+
+ this.preload = function(on_preload_finished){
+ this.lidarList.forEach(r=>r.preload(on_preload_finished));
+ };
+
+ this.unload = function(){
+ this.lidarList.forEach(r=>r.unload());
+ };
+
+ this.deleteAll = function(){
+ this.lidarList.forEach(r=>r.deleteAll());
+ };
+
+ this.getOperableObjects = function(){
+ return this.lidarList.flatMap(r=>r.getOperableObjects());
+ };
+
+ this.showCalibBox = false;
+ this.showCalibBox = function(){
+ this.showCalibBox = true;
+ this.lidarList.forEach(r=>r.showCalibBox());
+ };
+
+ this.hideCalibBox = function(){
+ this.showCalibBox = false;
+ this.lidarList.forEach(r=>r.hideCalibBox());
+ }
+};
+
+
+export {AuxLidarManager}
\ No newline at end of file
diff --git a/public/js/box_editor.js b/public/js/box_editor.js
new file mode 100644
index 0000000..76cb159
--- /dev/null
+++ b/public/js/box_editor.js
@@ -0,0 +1,1839 @@
+
+import {ProjectiveViewOps} from "./side_view_op.js"
+import {BoxImageContext} from "./image.js";
+import {saveWorldList, reloadWorldList} from "./save.js"
+import {objIdManager} from "./obj_id_list.js"
+import { globalKeyDownManager } from "./keydown_manager.js";
+import{ml} from "./ml.js";
+import { BooleanKeyframeTrack } from "./lib/three.module.js";
+import { checkScene } from "./error_check.js";
+import { logger } from "./log.js";
+
+
+/*
+2 ways to attach and edit a box
+1) attach/detach
+2) setTarget, tryAttach, resetTarget, this is only for batch-editor-manager
+*/
+function BoxEditor(parentUi, boxEditorManager, viewManager, cfg, boxOp,
+ func_on_box_changed, func_on_box_remove, name){
+
+ this.boxEditorManager = boxEditorManager;
+ this.parentUi = parentUi;
+ this.name=name;
+ let uiTmpl = document.getElementById("box-editor-ui-template");
+ let tmpui = uiTmpl.content.cloneNode(true); //sub-views
+
+ parentUi.appendChild(tmpui);
+ this.ui = parentUi.lastElementChild;
+ this.boxInfoUi = this.ui.querySelector("#box-info");
+
+ this.viewManager = viewManager;
+ this.boxOp = boxOp;
+ this.boxView = this.viewManager.addBoxView(this.ui); //this.editorUi.querySelector("#sub-views")
+ this.projectiveViewOps = new ProjectiveViewOps(
+ this.ui, //this.editorUi.querySelector("#sub-views"),
+ cfg,
+ this,
+ this.boxView.views,
+ this.boxOp,
+ func_on_box_changed,
+ func_on_box_remove);
+
+ this.focusImageContext = new BoxImageContext(this.ui.querySelector("#focuscanvas"));
+
+ this.pseudoBox = {
+ position: {x: 0, y: 0, z: 0},
+ rotation: {x: 0, y: 0, z: 0},
+ scale: {x: 1, y: 1, z: 1},
+ };
+
+ this.copyPseudoBox = function(b)
+ {
+ this.pseudoBox.position.x = b.position.x;
+ this.pseudoBox.position.y = b.position.y;
+ this.pseudoBox.position.z = b.position.z;
+
+ this.pseudoBox.rotation.x = b.rotation.x;
+ this.pseudoBox.rotation.y = b.rotation.y;
+ this.pseudoBox.rotation.z = b.rotation.z;
+
+ this.pseudoBox.scale.x = b.scale.x;
+ this.pseudoBox.scale.y = b.scale.y;
+ this.pseudoBox.scale.z = b.scale.z;
+ };
+
+ this.isInBatchMode = function(){
+ return !!this.boxEditorManager;
+ }
+
+ this.target = {};
+
+ this.setTarget = function(world, objTrackId, objType){
+ this.target = {
+ world: world,
+ objTrackId: objTrackId,
+ objType: objType,
+ }
+
+ if (this.isInBatchMode()){
+
+ this.pseudoBox.world = world;
+ this.boxView.attachBox(this.pseudoBox);
+ }
+
+ this.tryAttach();
+ this.ui.style.display="inline-block";
+ this.updateInfo();
+ };
+
+ this.setIndex = function(index){
+ this.index = index; // index as of in all editors.
+ };
+
+ this.setSelected = function(selected, eventId){
+ if (selected)
+ {
+ this.ui.className = "selected";
+ this.selected = true;
+ this.selectEventId = eventId;
+ }
+ else
+ {
+ if (!eventId || (this.selectEventId == eventId))
+ {
+ // cancel only you selected.
+ this.ui.className = "";
+ this.selected = false;
+ this.selectEventId = null;
+ }
+ }
+
+
+ };
+
+ // this.onContextMenu = (event)=>{
+ // if (this.boxEditorManager) // there is no manager for box editor in main ui
+ // this.boxEditorManager.onContextMenu(event, this);
+ // };
+
+ // this.ui.oncontextmenu = this.onContextMenu;
+
+ this.resetTarget = function(){
+ if (this.target.world){
+ //unload if it's not the main world
+
+
+ // if (this.target.world !== this.target.world.data.world)
+ // this.target.world.unload();
+ }
+
+ this.detach();
+ this.target = {};
+ //this.ui.style.display="none";
+ };
+
+ this.tryAttach = function(){
+ // find target box, attach to me
+ if (this.target && this.target.world){
+
+ let box = this.target.world.annotation.findBoxByTrackId(this.target.objTrackId);
+ if (box){
+ this.attachBox(box);
+ }
+ }
+ };
+
+
+ /*
+ the projectiveView tiggers zoomratio changing event.
+ editormanager broaccasts it to all editors
+ */
+ this._setViewZoomRatio = function(viewIndex, ratio){
+ this.boxView.views[viewIndex].zoom_ratio = ratio;
+ };
+
+ this.updateViewZoomRatio = function(viewIndex, ratio){
+ //this.upate();
+ if (this.boxEditorManager)
+ this.boxEditorManager.updateViewZoomRatio(viewIndex, ratio);
+ else{
+ this._setViewZoomRatio(viewIndex, ratio);
+ this.update();
+ //this.viewManager.render();
+ }
+ };
+
+ this.onOpCmd = function(cmd){
+ if (this.boxEditorManager)
+ this.boxEditorManager.onOpCmd(cmd, this);
+ else{
+ this.executeOpCmd(cmd);
+ }
+ };
+
+ this.executeOpCmd = function(cmd){
+
+ if (!this.box)
+ {
+ return;
+ }
+
+ if (cmd.op == "translate")
+ {
+ for (let axis in cmd.params.delta)
+ {
+ this.boxOp.translate_box(this.box, axis, cmd.params.delta[axis]);
+ //this.boxOp.translate_box(this.box, "y", delta.y);
+ }
+
+ func_on_box_changed(this.box);
+ }
+ }
+
+ this.box = null;
+ this.attachBox = function(box){
+ if (this.box && this.box !== box){
+ this.box.boxEditor=null;
+ console.log("detach box editor");
+ //todo de-highlight box
+ }
+
+ this.box = null;
+ this.show();
+
+ if (box){
+ box.boxEditor = this;
+ this.box=box;
+ //this.boxOp.highlightBox(box);
+ this.boxView.attachBox(box);
+ this.projectiveViewOps.attachBox(box);
+ this.focusImageContext.updateFocusedImageContext(box);
+
+ //this.update();
+ this.updateInfo();
+ // this.boxView.render();
+
+ if (this.isInBatchMode()){
+ this.boxEditorManager.onBoxChanged(this);
+ }
+ }
+ };
+
+ this.detach = function(dontHide){
+ if (this.box){
+ if (this.box.boxEditor === this){
+ this.box.boxEditor = null;
+ }
+ //this.boxOp.unhighlightBox(this.box);
+ //todo de-highlight box
+ this.projectiveViewOps.detach();
+ this.boxView.detach();
+
+ if (this.isInBatchMode()){
+ this.copyPseudoBox(this.box);
+ this.boxView.attachBox(this.pseudoBox);
+
+ }
+
+ this.focusImageContext.clear_canvas();
+ this.box = null;
+ }
+
+ if (!dontHide)
+ this.hide();
+ };
+
+
+
+
+ this.hide = function(){
+ this.ui.style.display="none";
+
+
+ // this is a hack, if we don't have manager, this is the main editor
+ // hide parent ui
+ // todo, add a pseudo manager, hide itself when child hide
+ if (!this.boxEditorManager){
+ this.parentUi.style.display="none";
+ }
+ }
+ this.show = function(){
+ this.ui.style.display="";//"inline-block";
+ if (!this.boxEditorManager){
+ this.parentUi.style.display="";
+ }
+ }
+
+ this.onBoxChanged=function(){
+
+ this.projectiveViewOps.update_view_handle();
+ this.focusImageContext.updateFocusedImageContext(this.box);
+ this.boxView.onBoxChanged();
+
+ // mark
+ delete this.box.annotator; // human annotator doesn't need a name
+ delete this.box.follows;
+ this.box.changed = true;
+
+ // don't mark world's change flag, for it's hard to clear it.
+
+ // inform boxEditorMgr to transfer annotation to other frames.
+ if (this.boxEditorManager)
+ this.boxEditorManager.onBoxChanged(this);
+
+ this.updateInfo();
+
+ //this.boxView.render();
+ };
+
+ this.onDelBox = function(){
+ let box = this.box;
+ this.detach("donthide");
+ };
+
+ // windowresize...
+ this.update = function(dontRender=false){
+
+ if (this.boxView){
+ this.boxView.onBoxChanged(dontRender);
+
+ // this.boxView.updateCameraRange(this.box);
+ // this.boxView.updateCameraPose(this.box);
+
+ // if (!dontRender)
+ // this.boxView.render();
+ }
+
+ // boxview should be updated for pseudobox.
+
+ if (this.box === null)
+ return;
+
+ this.projectiveViewOps.update_view_handle();
+
+
+
+ // this is not needed somtime
+ this.focusImageContext.updateFocusedImageContext(this.box);
+
+ // should we update info?
+ this.updateInfo();
+ };
+
+ this.updateInfo = function(){
+ let info = ""
+ if (this.target.world){
+ info += String(this.target.world.frameInfo.frame);
+
+ if (this.box && this.box.annotator)
+ info += ","+this.box.annotator;
+
+ // if (this.box && this.box.changed)
+ // info += " *";
+ }
+
+ this.boxInfoUi.innerHTML = info;
+ };
+
+ this.updateBoxDimension = function(){
+
+ };
+
+
+ this.resize = function(width, height)
+ {
+ // if (height + "px" == this.ui.style.height && width + "px" == this.ui.style.width)
+ // {
+ // return;
+ // }
+
+ this.ui.style.width = width + "px";
+ this.ui.style.height = height + "px";
+ this.boxView.render();
+ };
+
+ this.setResize = function(option){
+ this.ui.style.resize=option;
+ this.ui.style["z-index"] = "0";
+
+ if (option == 'both')
+ {
+
+ this.lastSize= {
+ width: 0,
+ height: 0,
+ };
+
+ this.resizeObserver = new ResizeObserver(elements=>{
+
+ let rect = elements[0].contentRect;
+ console.log("sub-views resized.", rect);
+
+ if (rect.height == 0 || rect.width == 0)
+ {
+ return;
+ }
+
+ if (rect.height != this.lastSize.height || rect.width != this.lastSize.width)
+ {
+ // viewManager will clear backgound
+ // so this render is effectiveless.
+ //this.boxView.render();
+
+ // save
+
+ if (this.boxEditorManager) // there is no manager for box editor in main ui
+ {
+ pointsGlobalConfig.setItem("batchModeSubviewSize", {width: rect.width, height: rect.height});
+ this.boxEditorManager.onSubViewsResize(rect.width, rect.height);
+ }
+ else{
+ this.boxView.render();
+ }
+
+ //save
+ this.lastSize.width = rect.width;
+ this.lastSize.height = rect.height;
+ }
+
+
+
+ });
+
+ this.resizeObserver.observe(this.ui);
+ }
+ }
+
+
+
+}
+
+
+//parentUi #batch-box-editor
+function BoxEditorManager(parentUi, viewManager, objectTrackView,
+ cfg, boxOp, globalHeader, contextMenu, configMenu,
+ func_on_box_changed, func_on_box_remove, func_on_annotation_reloaded){
+ this.viewManager = viewManager;
+ this.objectTrackView = objectTrackView;
+ this.boxOp = boxOp;
+ this.activeIndex = 0;
+ this.editorList = [];
+ this.cfg = cfg;
+ this.globalHeader = globalHeader;
+ this.contextMenu = contextMenu;
+ this.parentUi = parentUi; //#batch-box-editor
+ this.boxEditorGroupUi = parentUi.querySelector("#batch-box-editor-group");
+ this.boxEditorHeaderUi = parentUi.querySelector("#batch-box-editor-header");
+ this.batchSize = cfg.batchModeInstNumber;
+ //this.configMenu = configMenu;
+
+ this.activeEditorList = function(){
+ return this.editorList.slice(0, this.activeIndex);
+ };
+
+ this.editingTarget = {
+ data: null,
+ sceneMeta: "",
+ objTrackId: "",
+ frame:"",
+ frameIndex: NaN,
+ };
+
+ this.onExit = null;
+ // frame specifies the center frame to edit
+
+
+ // this.parentUi.addEventListener("contextmenu", event=>{
+ // this.contextMenu.show("boxEditorManager", event.clientX, event.clientY, this);
+ // event.stopPropagation();
+ // event.preventDefault();
+ // })
+
+ this.onSubViewsResize = function(width, height)
+ {
+ this.viewManager.mainView.clearView();
+ this.editorList.forEach(e=>{
+ e.resize(width, height);
+ });
+
+ //this.viewManager.render();
+ };
+
+ this.calculateBestSubviewSize=function(batchSize)
+ {
+ let parentRect = this.parentUi.getBoundingClientRect();
+ let headerRect = this.boxEditorHeaderUi.getBoundingClientRect();
+ let editorsGroupRect = this.boxEditorGroupUi.getBoundingClientRect();
+
+ let availableHeight = parentRect.height - headerRect.height;
+ let availableWidth = parentRect.width;
+
+ if (availableHeight ==0 || availableWidth ==0)
+ {
+ this.batchSizeUpdated=true;
+ return;
+ }
+
+
+ let defaultBoxWidth=130;
+ let defaultBoxHeight=450;
+
+ let rows = 1;
+ let w = availableWidth/Math.ceil(batchSize/rows);
+ let h = availableHeight/rows;
+ let cost = Math.abs((w/h) - (defaultBoxWidth/defaultBoxHeight));
+ let minCost = cost;
+ let bestRows = rows;
+ while(true)
+ {
+ rows +=1;
+
+ let w = Math.floor(availableWidth/Math.ceil(batchSize/rows));
+ let h = Math.floor(availableHeight/rows);
+ let cost = Math.abs((w/h) - (defaultBoxWidth/defaultBoxHeight));
+
+ if (cost < minCost)
+ {
+ minCost = cost;
+ bestRows = rows;
+ }
+ else{
+ break;
+ }
+ }
+
+ //bestRows
+ pointsGlobalConfig.batchModeSubviewSize = {
+ width: Math.floor(availableWidth/Math.ceil(batchSize/bestRows)),
+ height: Math.floor(availableHeight/bestRows),
+ }
+ }
+
+ this.setBatchSize = function(batchSize)
+ {
+ this.calculateBestSubviewSize(batchSize);
+
+ this.batchSize = batchSize;
+ if (this.parentUi.style.display != "none")
+ {
+
+
+ this.edit( this.editingTarget.data,
+ this.editingTarget.sceneMeta,
+ this.editingTarget.frame,
+ this.editingTarget.objTrackId,
+ this.editingTarget.objType
+ );
+ }
+ };
+
+ this.onWindowResize = function()
+ {
+ this.setBatchSize(this.batchSize);
+ };
+
+ this.edit = function(data, sceneMeta, frame, objTrackId, objType, onExit){
+
+ this.show();
+ this.reset();
+
+ if (this.batchSizeUpdated)
+ {
+ this.batchSizeUpdated=false;
+ this.calculateBestSubviewSize(this.batchSize);
+ }
+
+
+ if (onExit){
+ // next/prev call will not update onExit
+ this.onExit = onExit;
+ }
+ let sceneName = sceneMeta.scene;
+
+ this.editingTarget.data = data;
+ this.editingTarget.sceneMeta = sceneMeta;
+ this.editingTarget.objTrackId = objTrackId;
+
+
+ this.editingTarget.objType = objType;
+
+ this.editingTarget.frame = frame;
+
+ // this.parentUi.querySelector("#object-track-id-editor").value=objTrackId;
+ // this.parentUi.querySelector("#object-category-selector").value=objType;
+
+
+ let centerIndex = sceneMeta.frames.findIndex(f=>f==frame);
+ this.editingTarget.frameIndex = centerIndex;
+
+ if (centerIndex < 0){
+ centerIndex = 0;
+ }
+
+
+ let startIndex = Math.max(0, centerIndex - this.batchSize/2);
+
+ if(startIndex > 0)
+ {
+ if (startIndex + this.batchSize > sceneMeta.frames.length)
+ {
+ startIndex -= startIndex + this.batchSize - sceneMeta.frames.length;
+
+ if (startIndex < 0)
+ {
+ startIndex = 0;
+ }
+ }
+ }
+
+
+
+ let frames = sceneMeta.frames.slice(startIndex, startIndex+this.batchSize);
+
+
+ //this.viewManager.mainView.clearView();
+
+ frames.forEach(async (frame, editorIndex)=>{
+ let world = await data.getWorld(sceneName, frame);
+ let editor = this.addEditor();
+ //editor.setTarget(world, objTrackId, objType);
+ editor.setIndex(editorIndex);
+ editor.resize(pointsGlobalConfig.batchModeSubviewSize.width, pointsGlobalConfig.batchModeSubviewSize.height);
+
+ if (this.editingTarget.frame == frame){
+ editor.setSelected(true);
+ }
+
+ data.activate_world(world,
+ ()=>{
+ //editor.tryAttach();
+
+ editor.setTarget(world, objTrackId, objType);
+
+ //
+ //this.viewManager.render();
+ },
+ true);
+ });
+
+
+ // set obj selector
+ this.globalHeader.setObject(objTrackId);
+ };
+
+ this.onContextMenu = function(event, boxEditor)
+ {
+ this.firingBoxEditor = boxEditor;
+
+ if (boxEditor.selected)
+ {
+ // ok
+ }
+ else
+ {
+ this.getSelectedEditors().forEach(e=>e.setSelected(false));
+ boxEditor.setSelected(true);
+ }
+
+ this.contextMenu.show("boxEditor", event.clientX, event.clientY, this);
+ event.stopPropagation();
+ event.preventDefault();
+ };
+
+ this.parentUi.oncontextmenu = (event)=>{
+ let ed = this.getEditorByMousePosition(event.clientX, event.clientY);
+
+ this.onContextMenu(event, ed);
+ };
+
+
+ this.handleContextMenuKeydownEvent = function(event, menuPos)
+ {
+ switch(event.key){
+ case 's':
+ this.activeEditorList().forEach(e=>e.setSelected(true));
+ return true;
+ break;
+ case 'a':
+ this.autoAnnotateSelectedFrames();
+ break;
+ case 'f':
+ this.finalizeSelectedBoxes();
+ break;
+ case 'd':
+ this.deleteSelectedBoxes(menuPos);
+ break;
+ case 'e':
+ this.interpolateSelectedFrames();
+ break;
+ case 'g':
+ this.gotoThisFrame();
+ break;
+ case 't':
+ this.showTrajectory();
+ break;
+ default:
+ return true;
+ }
+
+ return false;
+ };
+
+ this.delayUpdateAutoGeneratedBoxesTimer = null;
+
+ this.updateAutoGeneratedBoxes = function(){
+
+ if (this.delayUpdateAutoGeneratedBoxesTimer)
+ {
+ clearTimeout(this.delayUpdateAutoGeneratedBoxesTimer)
+ }
+
+ this.delayUpdateAutoGeneratedBoxesTimer = setTimeout(async ()=>{
+
+ if (this.cfg.autoUpdateInterpolatedBoxes){
+ await this.updateInterpolatedBoxes();
+ }
+
+ await this.updatePseudoBoxes();
+ },
+ 500);
+ };
+
+ this.updateInterpolatedBoxes = async function(){
+
+ let editorList = this.activeEditorList();
+ let applyIndList = editorList.map(e=>e.box&&e.box.annotator=="i");
+
+ let boxList = editorList.map(e=>e.box);
+ let worldList = editorList.map(e=>e.target.world);
+ await this.boxOp.interpolateAsync(worldList, boxList, applyIndList);
+ //this.activeEditorList().forEach(e=>e.tryAttach());
+
+ this.globalHeader.updateModifiedStatus();
+ //this.viewManager.render();
+ editorList.forEach(e=>{
+ if (e.box&&e.box.annotator=="i"){
+ e.boxView.onBoxChanged();
+ }
+ });
+
+ };
+
+
+ this.updatePseudoBoxes = async function(){
+ let editorList = this.activeEditorList();
+ let boxList = editorList.map(e=>e.box);
+ let anns = boxList.map(b=> b?b.world.annotation.ann_to_vector_global(b):null);
+
+ let ret = await ml.interpolate_annotation(anns);
+
+ editorList.forEach((e,i)=>{
+ if (!e.box){
+ let ann = e.target.world.annotation.vector_global_to_ann(ret[i]);
+ e.copyPseudoBox(ann);
+ e.boxView.onBoxChanged();
+ }
+ });
+ };
+
+
+
+ // manager
+ this.onBoxChanged = function(e){
+ this.updateAutoGeneratedBoxes();
+ //
+ };
+
+
+ let onBoxChangedInBatchMode = function(box)
+ {
+ if (box.boxEditor) // if in batch mode with less than 40 windows, some box don't have editor attached.
+ box.boxEditor.update(); //render.
+
+ box.world.annotation.setModified();
+ };
+
+
+ this.finalizeSelectedBoxes = function()
+ {
+ this.getSelectedEditors().forEach(e=>{
+
+ if (e.box){
+ if (e.box.annotator)
+ {
+ delete e.box.annotator;
+ func_on_box_changed(e.box);
+ //e.box.world.annotation.setModified();
+ e.updateInfo();
+ }
+ }
+ });
+
+ this.globalHeader.updateModifiedStatus();;
+ };
+
+ this.interpolateSelectedFrames = function(){
+ let applyIndList = this.activeEditorList().map(e=>false); //all shoud be applied.
+ let selectedEditors = this.getSelectedEditors();
+
+ // if interpolate only one box, remove it if exist.
+ // no matter who is the annotator.
+ if (selectedEditors.length == 1)
+ {
+ if (selectedEditors[0].box)
+ {
+ func_on_box_remove(selectedEditors[0].box, true);
+ }
+ }
+
+ selectedEditors.forEach(e=>applyIndList[e.index] = true);
+ this.interpolate(applyIndList);
+
+
+ this.updateAutoGeneratedBoxes();
+ };
+
+ this.deleteEmptyBoxes = function()
+ {
+ let editors = this.activeEditorList();
+ editors.forEach(e=>{
+ if (e.box)
+ {
+ if (e.box.world.lidar.get_box_points_number(e.box) == 0)
+ {
+ func_on_box_remove(e.box, true);
+ }
+ }
+ });
+
+ this.updateAutoGeneratedBoxes();
+ };
+
+ this.deleteIntersectedBoxes = function(){
+
+ let editors = this.getSelectedEditors();
+ editors.forEach(e=>{
+ if (e.box)
+ {
+ let boxes = e.box.world.annotation.findIntersectedBoxes(e.box);
+
+ boxes.forEach(b=>{
+ func_on_box_remove(b, true);
+ });
+
+ onBoxChangedInBatchMode(e.box);
+ }
+ });
+ };
+
+ this.deleteSelectedBoxes = function(infoBoxPos)
+ {
+ let selectedEditors = this.getSelectedEditors();
+
+ if (selectedEditors.length >= 2)
+ {
+ window.editor.infoBox.show(
+ "Confirm",
+ `Delete ${selectedEditors.length} selected boxes?`,
+ ["yes", "no"],
+ (btn)=>{
+ if (btn == "yes")
+ {
+
+ selectedEditors.forEach(e=>{
+ if (e.box)
+ func_on_box_remove(e.box, true);
+ });
+
+ this.updateAutoGeneratedBoxes();
+ }
+ },
+ infoBoxPos
+ );
+ }
+ else{
+ selectedEditors.forEach(e=>{
+ if (e.box)
+ func_on_box_remove(e.box, true)
+ });
+
+ this.updateAutoGeneratedBoxes();
+ }
+ };
+
+ this.autoAnnotateSelectedFrames = function()
+ {
+ let applyIndList = this.activeEditorList().map(e=>false); //all shoud be applied.
+ this.getSelectedEditors().forEach(e=>applyIndList[e.index] = true);
+ this.autoAnnotate(applyIndList);
+ };
+
+ this.onOpCmd = function(cmd, firingEditor){
+
+ firingEditor.executeOpCmd(cmd);
+
+ if (this.cfg.linkEditorsInBatchMode)
+ {
+ let editors = this.getSelectedEditors();
+
+ if (editors.includes(firingEditor))
+ {
+ editors.filter(x=>x!=firingEditor).forEach(e=>{
+ if (e.box && !e.box.annotator)
+ {
+ e.executeOpCmd(cmd);
+ }
+ });
+ }
+ }
+ };
+
+
+ this.handleContextMenuEvent = function(event)
+ {
+ console.log(event.currentTarget.id, event.type);
+ switch(event.currentTarget.id)
+ {
+
+ // manager
+ case 'cm-increase-box-editor':
+ this.batchSize += 1;
+ this.edit(
+ this.editingTarget.data,
+ this.editingTarget.sceneMeta,
+ this.editingTarget.sceneMeta.frame,
+ this.editingTarget.objTrackId,
+ this.editingTarget.objType
+ );
+ break;
+
+ case 'cm-decrease-box-editor':
+ this.batchSize -= 1;
+ this.edit(
+ this.editingTarget.data,
+ this.editingTarget.sceneMeta,
+ this.editingTarget.sceneMeta.frame,
+ this.editingTarget.objTrackId,
+ this.editingTarget.objType
+ );
+ break;
+
+ /////////////////////// obj instance //
+
+ case 'cm-select-all':
+ this.activeEditorList().forEach(e=>e.setSelected(true));
+ return false;//don't hide context menu
+ break;
+ case 'cm-select-all-previous':
+ this.activeEditorList().forEach(e=> e.setSelected(e.index <= this.firingBoxEditor.index));
+ return false;//don't hide context menu
+ break;
+ case 'cm-select-all-next':
+ this.activeEditorList().forEach(e=> e.setSelected(e.index >= this.firingBoxEditor.index));
+ return false;//don't hide context menu
+ break
+
+ case 'cm-delete':
+ this.deleteSelectedBoxes( {x: event.clientX, y: event.clientY});
+ break;
+ case 'cm-delete-empty-boxes':
+ this.deleteEmptyBoxes();
+ break;
+ case 'cm-delete-intersected-boxes':
+ this.deleteIntersectedBoxes();
+ break;
+ case 'cm-interpolate':
+ this.interpolateSelectedFrames();
+ break;
+
+ case 'cm-auto-annotate':
+ this.autoAnnotateSelectedFrames();
+ break;
+
+ case 'cm-auto-annotate-wo-rotation':
+ {
+ let applyIndList = this.activeEditorList().map(e=>false); //all shoud be applied.
+ this.getSelectedEditors().forEach(e=>applyIndList[e.index] = true);
+ this.autoAnnotate(applyIndList, "dontrotate");
+ }
+ break;
+
+ case 'cm-fit-moving-direction':
+ this.getSelectedEditors().forEach(e=>{
+ if (!e.box)
+ return;
+
+ let currentBox = e.box;
+ let estimatedRot = boxOp.estimate_rotation_by_moving_direciton(currentBox);
+
+ if (estimatedRot){
+ currentBox.rotation.z = estimatedRot.z;
+ func_on_box_changed(currentBox);
+ }
+ });
+
+ this.updateAutoGeneratedBoxes();
+
+ break;
+ case 'cm-fit-size':
+ this.getSelectedEditors().forEach(e=>{
+ if (!e.box)
+ return;
+
+ boxOp.fit_size(e.box, ['x','y']);
+ func_on_box_changed(e.box);
+ });
+
+ this.updateAutoGeneratedBoxes();
+ break;
+ case 'cm-fit-position':
+ this.getSelectedEditors().forEach(e=>{
+ if (!e.box)
+ return;
+ boxOp.auto_rotate_xyz(e.box, null,
+ null,//{x:false, y:false, z:true},
+ func_on_box_changed, //onBoxChangedInBatchMode,
+ "noscaling", "dontrotate");
+ });
+
+ this.updateAutoGeneratedBoxes();
+ break;
+ case 'cm-fit-rotation':
+ this.getSelectedEditors().forEach(e=>{
+ if (!e.box)
+ return;
+ boxOp.auto_rotate_xyz(e.box, null,
+ null,
+ func_on_box_changed,//onBoxChangedInBatchMode, //
+ "noscaling");
+
+ });
+
+ this.updateAutoGeneratedBoxes();
+ break;
+ case 'cm-fit-bottom':
+ this.getSelectedEditors().forEach(e=>{
+ if (!e.box)
+ return;
+ boxOp.fit_bottom(e.box);
+
+ func_on_box_changed(e.box);
+ });
+
+ this.updateAutoGeneratedBoxes();
+ break;
+ case 'cm-fit-top':
+ this.getSelectedEditors().forEach(e=>{
+ if (!e.box)
+ return;
+ boxOp.fit_top(e.box);
+
+ func_on_box_changed(e.box);
+ });
+
+ this.updateAutoGeneratedBoxes();
+ break;
+ case 'cm-fit-left':
+ this.getSelectedEditors().forEach(e=>{
+ if (!e.box)
+ return;
+ boxOp.fit_left(e.box);
+
+ func_on_box_changed(e.box);
+ });
+
+ this.updateAutoGeneratedBoxes();
+ break;
+ case 'cm-fit-right':
+ this.getSelectedEditors().forEach(e=>{
+ if (!e.box)
+ return;
+ boxOp.fit_right(e.box);
+
+ func_on_box_changed(e.box);
+ });
+
+ this.updateAutoGeneratedBoxes();
+ break;
+ case 'cm-fit-rear':
+ this.getSelectedEditors().forEach(e=>{
+ if (!e.box)
+ return;
+ boxOp.fit_rear(e.box);
+
+ func_on_box_changed(e.box);
+ });
+
+ this.updateAutoGeneratedBoxes();
+ break;
+ case 'cm-fit-front':
+ this.getSelectedEditors().forEach(e=>{
+ if (!e.box)
+ return;
+ boxOp.fit_front(e.box);
+
+ func_on_box_changed(e.box);
+ });
+
+ this.updateAutoGeneratedBoxes();
+ break;
+ case 'cm-reverse-direction':
+ this.getSelectedEditors().forEach(e=>{
+ if (!e.box)
+ return;
+ if (e.box.rotation.z > 0){
+ e.box.rotation.z -= Math.PI;
+ }else{
+ e.box.rotation.z += Math.PI;
+ }
+
+ onBoxChangedInBatchMode(e.box);
+ });
+
+ //this.viewManager.render();
+
+ this.updateAutoGeneratedBoxes();
+
+ break;
+ case 'cm-reset-roll-pitch':
+ this.getSelectedEditors().forEach(e=>{
+ if (!e.box)
+ return;
+ e.box.rotation.x =0;
+ e.box.rotation.y =0;
+ e.update('dontrender');
+ e.box.world.annotation.setModified();
+
+ onBoxChangedInBatchMode(e.box);
+ });
+
+ //this.viewManager.render();
+ this.updateAutoGeneratedBoxes();
+
+ break;
+ case 'cm-show-trajectory':
+ this.showTrajectory();
+ break;
+
+ case 'cm-check':
+ {
+ let scene = this.editingTarget.sceneMeta.scene;
+ checkScene(scene);
+ logger.show();
+ logger.errorBtn.onclick();
+ }
+ break;
+
+ case 'cm-finalize':
+ this.finalizeSelectedBoxes();
+ break;
+
+ case 'cm-sync-size':
+ editor.data.worldList.forEach(w=>{
+ let box = w.annotation.boxes.find(b=>b.obj_track_id == this.firingBoxEditor.target.objTrackId);
+ if (box && box !== this.firingBoxEditor.box){
+ box.scale.x = this.firingBoxEditor.box.scale.x;
+ box.scale.y = this.firingBoxEditor.box.scale.y;
+ box.scale.z = this.firingBoxEditor.box.scale.z;
+ //saveList.push(w);
+ w.annotation.setModified();
+
+ onBoxChangedInBatchMode(box);
+ }
+ });
+
+ //this.activeEditorList().forEach(e=>e.update('dontrender'));
+ //this.viewManager.render();
+ this.updateAutoGeneratedBoxes();
+
+ break;
+ case 'cm-reload':
+
+ {
+ let selectedEditors = this.getSelectedEditors();
+ this.reloadAnnotation(selectedEditors);
+
+ this.updateAutoGeneratedBoxes();
+
+ }
+ break;
+
+ case 'cm-goto-this-frame':
+ {
+ this.gotoThisFrame();
+ }
+ break;
+ case 'cm-follow-static-objects':
+ {
+ let b = this.firingBoxEditor.box;
+ editor.autoAdjust.followStaticObjects(b);
+ this.globalHeader.updateModifiedStatus();
+
+ this.activeEditorList().forEach(e=>{
+ e.tryAttach();
+ });
+
+ //this.viewManager.render();
+ this.updateAutoGeneratedBoxes();
+
+ }
+ break;
+ };
+
+
+
+ return true;
+ };
+
+ this.reset = function(){
+ this.activeEditorList().forEach(e=>{
+ e.setSelected(false);
+ e.resetTarget();
+ });
+
+ this.viewManager.mainView.clearView();
+
+ this.activeIndex = 0;
+ };
+
+
+ this.keydownHandler = (event)=>{
+
+ switch(event.key){
+ case 'a':
+ if (event.ctrlKey){
+ this.activeEditorList().forEach(e=>e.setSelected(true));
+ }
+ break;
+
+ case 's':
+ if (event.ctrlKey){
+ this._save();
+ console.log("saved for batch editor");
+ }
+ break;
+ case '+':
+ case '=':
+ this.editingTarget.data.scale_point_size(1.2);
+ this.viewManager.render();
+ break;
+ case '-':
+ this.editingTarget.data.scale_point_size(0.8);
+ this.viewManager.render();
+ break;
+ case 'v':
+ case 'Escape':
+ {
+ // let selected = this.getSelectedEditors();
+ // if (selected.length >= 2){
+ // selected.forEach(e=>e.setSelected(false));
+ // }
+ // else
+ {
+ this.hide();
+ this.reset();
+ if (this.onExit)
+ this.onExit();
+ }
+ }
+ break;
+ case 'PageUp':
+ case '3':
+ this.prevBatch();
+ break;
+ case 'PageDown':
+ case '4':
+ this.nextBatch();
+ break;
+ case 't':
+ this.showTrajectory();
+ break;
+ default:
+ console.log(`key ${event.key} igonored`);
+ break;
+ }
+
+ return false;
+ };
+
+
+ let keydownHandler = (event)=>this.keydownHandler(event);
+
+
+ this.hide =function(){
+ if (this.parentUi.style.display != "none")
+ {
+ this.parentUi.style.display = "none";
+ this.toolbox.style.display = "none";
+ //document.removeEventListener("keydown", keydownHandler);
+ globalKeyDownManager.deregister('batch-editor');
+ }
+
+ };
+ this.show = function(){
+ if (this.parentUi.style.display == "none")
+ {
+ this.parentUi.style.display = "";
+ //document.addEventListener("keydown", keydownHandler);
+ globalKeyDownManager.register(keydownHandler, 'batch-editor');
+ this.toolbox.style.display = "";
+ }
+ };
+
+ this.render =function()
+ {
+ if (this.parentUi.style.display != "none")
+ {
+ this.viewManager.render();
+ }
+ };
+
+
+ this._addToolBox = function(){
+ let template = document.getElementById("batch-editor-tools-template");
+ let tool = template.content.cloneNode(true);
+ // this.boxEditorHeaderUi.appendChild(tool);
+ // return this.boxEditorHeaderUi.lastElementChild;
+
+ document.getElementById("dynamic-buttons-placeholder").appendChild(tool);
+ return document.getElementById("dynamic-buttons-placeholder").lastElementChild;
+ };
+
+ this.toolbox = this._addToolBox();
+
+ this.reloadAnnotation = function(editorList){
+
+ let done = (anns)=>{
+ // update editor
+ editorList.forEach(e=>{
+ e.tryAttach();
+ e.update("dontrender");
+ });
+
+ // reload main view
+ if (func_on_annotation_reloaded)
+ func_on_annotation_reloaded();
+ // render all, at last
+
+ this.viewManager.render();
+
+ this.globalHeader.updateModifiedStatus();
+ };
+
+
+ let worldList = editorList.map(e=>e.target.world);
+
+ let modifiedFrames =worldList.filter(w=>w && w.annotation.modified);
+
+ if (modifiedFrames.length > 0)
+ {
+ window.editor.infoBox.show(
+ "Confirm",
+ `Discard changes to ${modifiedFrames.length} frames, continue to reload?`,
+ ["yes","no"],
+ (choice)=>{
+ if (choice=="yes")
+ {
+ reloadWorldList(worldList, done);
+ }
+ }
+ );
+ }
+ else{
+ reloadWorldList(worldList, done);
+ }
+
+
+
+ }
+
+ this.interpolate = async function(applyIndList){
+ let boxList = this.activeEditorList().map(e=>e.box);
+ let worldList = this.activeEditorList().map(e=>e.target.world);
+ await this.boxOp.interpolateAsync(worldList, boxList, applyIndList);
+ this.activeEditorList().forEach(e=>e.tryAttach());
+
+ this.globalHeader.updateModifiedStatus();
+
+ this.viewManager.render();
+ };
+
+ this.gotoThisFrame = function(){
+ let targetFrame = this.firingBoxEditor.target.world.frameInfo.frame;
+ let targetTrackId = this.firingBoxEditor.target.objTrackId;
+ this.hide();
+
+ this.reset();
+ if (this.onExit)
+ this.onExit(targetFrame, targetTrackId);
+ };
+
+ this.autoAnnotate = async function(applyIndList, dontRotate){
+ let editors = this.activeEditorList();
+ let boxList = editors.map(e=>e.box);
+ let worldList = editors.map(e=>e.target.world);
+
+ let onFinishOneBox = (i)=>{
+ editors[i].tryAttach();
+ editors[i].box.world.annotation.setModified();
+ this.viewManager.render();
+
+ this.updateAutoGeneratedBoxes();
+ }
+
+ await this.boxOp.interpolateAndAutoAdjustAsync(worldList, boxList, onFinishOneBox, applyIndList, dontRotate);
+
+ this.globalHeader.updateModifiedStatus();
+ }
+
+ // this.parentUi.querySelector("#object-track-id-editor").addEventListener("keydown", function(e){
+ // e.stopPropagation();});
+
+ // this.parentUi.querySelector("#object-track-id-editor").addEventListener("keyup", function(e){
+ // e.stopPropagation();
+ // });
+
+ // this.parentUi.querySelector("#object-track-id-editor").onchange = (ev)=>this.object_track_id_changed(ev);
+ // this.parentUi.querySelector("#object-category-selector").onchange = (ev)=>this.object_category_changed(ev);
+
+
+ // this should follow addToolBox
+
+ // this.parentUi.querySelector("#instance-number").value = this.batchSize;
+ // this.parentUi.querySelector("#instance-number").onchange = (ev)=>{
+ // this.batchSize = parseInt(ev.currentTarget.value);
+ // this.edit(
+ // this.editingTarget.data,
+ // this.editingTarget.sceneMeta,
+ // this.editingTarget.frame,
+ // this.editingTarget.objTrackId,
+ // this.editingTarget.objType
+ // );
+ // }
+
+ this.showTrajectory = () =>{
+ let tracks = this.editingTarget.data.worldList.map(w=>{
+ let box = w.annotation.findBoxByTrackId(this.editingTarget.objTrackId);
+ let ann = null;
+ if (box){
+ ann = w.annotation.boxToAnn(box);
+ ann.psr.position = w.lidarPosToUtm(ann.psr.position);
+ ann.psr.rotation = w.lidarRotToUtm(ann.psr.rotation);
+ }
+ return [w.frameInfo.frame, ann, false];
+ });
+
+ tracks.sort((a,b)=> (a[0] > b[0])? 1 : -1);
+
+ this.objectTrackView.setObject(
+ this.editingTarget.objType,
+ this.editingTarget.objTrackId,
+ tracks,
+ (targetFrame)=>{ //onExit
+ this.getSelectedEditors().forEach(e=>e.setSelected(false));
+ this.activeEditorList().find(e=>e.target.world.frameInfo.frame == targetFrame).setSelected(true);
+ }
+ );
+ };
+
+ this.toolbox.querySelector("#trajectory").onclick = (e)=>{
+ this.showTrajectory();
+ };
+
+ this.toolbox.querySelector("#reload").onclick = (e)=>{
+
+ let selectedEditors = this.activeEditorList();
+ this.reloadAnnotation(selectedEditors);
+ };
+
+ this.toolbox.querySelector("#interpolate").onclick = async ()=>{
+ //this.boxOp.interpolate_selected_object(this.editingTarget.scene, this.editingTarget.objTrackId, "");
+
+ let applyIndList = this.activeEditorList().map(e=>true); //all shoud be applied.
+ this.interpolate(applyIndList);
+
+ };
+
+ this.toolbox.querySelector("#auto-annotate").onclick = async ()=>{
+ let applyIndList = this.activeEditorList().map(e=>true); //all shoud be applied.
+ this.autoAnnotate(applyIndList);
+ };
+
+ this.toolbox.querySelector("#auto-annotate-translate-only").onclick = async ()=>{
+ let applyIndList = this.activeEditorList().map(e=>true); //all shoud be applied.
+ this.autoAnnotate(applyIndList, "dontrotate");
+ };
+
+ this.toolbox.querySelector("#exit").onclick = ()=>{
+ this.hide();
+
+ this.reset();
+
+ if (this.onExit)
+ this.onExit();
+ };
+
+ this.toolbox.querySelector("#next").onclick = ()=>{
+ this.nextBatch();
+ };
+
+ this.toolbox.querySelector("#prev").onclick = ()=>{
+ this.prevBatch();
+ };
+
+ this.nextBatch = function()
+ {
+ let maxFrameIndex = this.editingTarget.sceneMeta.frames.length-1;
+
+ let editors = this.activeEditorList()
+ let lastEditor = editors[editors.length-1];
+ if (lastEditor.target.world.frameInfo.frame_index == maxFrameIndex)
+ {
+ if (this.batchSize >= this.editingTarget.sceneMeta.frames.length)
+ {
+ this.nextObj();
+ }
+ else
+ {
+ window.editor.infoBox.show("Info", "This is the last batch of frames.");
+ }
+
+ }
+ else
+ {
+ this.edit(
+ this.editingTarget.data,
+ this.editingTarget.sceneMeta,
+ this.editingTarget.sceneMeta.frames[Math.min(this.editingTarget.frameIndex + this.batchSize/2, maxFrameIndex)],
+ this.editingTarget.objTrackId,
+ this.editingTarget.objType
+ );
+ }
+ };
+
+ this.prevBatch = function()
+ {
+ let firstEditor = this.activeEditorList()[0];
+ if (firstEditor.target.world.frameInfo.frame_index == 0)
+ {
+
+ if (this.batchSize >= this.editingTarget.sceneMeta.frames.length)
+ {
+ this.prevObj();
+ }
+ else
+ {
+ window.editor.infoBox.show("Info", "This is the first batch of frames");
+ }
+
+ }
+ else
+ {
+ this.edit(
+ this.editingTarget.data,
+ this.editingTarget.sceneMeta,
+ this.editingTarget.sceneMeta.frames[Math.max(this.editingTarget.frameIndex - this.batchSize/2, 0)],
+ this.editingTarget.objTrackId,
+ this.editingTarget.objType
+ );
+ }
+
+ };
+
+ this.prevObj = function(){
+ let idx = objIdManager.objectList.findIndex(x=>x.id==this.editingTarget.objTrackId);
+
+ let objNum = objIdManager.objectList.length;
+
+ idx = (idx + objNum - 1) % objNum;
+
+ let obj = objIdManager.objectList[idx];
+
+
+
+ this.edit(
+ this.editingTarget.data,
+ this.editingTarget.sceneMeta,
+ this.editingTarget.sceneMeta.frames[this.editingTarget.frameIndex],
+ obj.id,
+ obj.category,
+ );
+ };
+
+
+ this.gotoFrame = function(frameID){
+ this.getSelectedEditors().forEach(e=>e.setSelected(false));
+ this.activeEditorList().find(e=>e.target.world.frameInfo.frame == frameID).setSelected(true);
+ }
+
+ this.gotoObjectFrame = function(frameId, objId){
+ if (objId != this.editingTarget.objTrackId)
+ {
+ let obj = objIdManager.getObjById(objId);
+
+ this.edit(
+ this.editingTarget.data,
+ this.editingTarget.sceneMeta,
+ frameId,
+ objId,
+ obj.category,
+ );
+ }
+
+ this.getSelectedEditors().forEach(e=>e.setSelected(false));
+ this.activeEditorList().find(e=>e.target.world.frameInfo.frame == frameId).setSelected(true);
+ }
+
+ this.nextObj = function(){
+ let idx = objIdManager.objectList.findIndex(x=>x.id==this.editingTarget.objTrackId && x.category == this.editingTarget.objType);
+
+ let objNum = objIdManager.objectList.length;
+
+ idx = (idx + 1) % objNum;
+
+ let obj = objIdManager.objectList[idx];
+
+ this.edit(
+ this.editingTarget.data,
+ this.editingTarget.sceneMeta,
+ this.editingTarget.sceneMeta.frames[this.editingTarget.frameIndex],
+ obj.id,
+ obj.category,
+ );
+ };
+
+ // this.toolbox.querySelector("#save").onclick = ()=>{
+ // this._save();
+ // };
+
+ this.toolbox.querySelector("#finalize").onclick = ()=>{
+ this.finalize();
+ };
+
+ this.finalize = function(){
+ this.activeEditorList().forEach(e=>{
+ if (e.box){
+
+ if (e.box.annotator){
+ delete e.box.annotator;
+ func_on_box_changed(e.box);
+ }
+ e.box.world.annotation.setModified();
+ e.updateInfo();
+ }
+ });
+
+ this.globalHeader.updateModifiedStatus();
+ };
+
+ this.object_track_id_changed = function(event){
+ var id = event.currentTarget.value;
+
+ if (id == "new"){
+ id = objIdManager.generateNewUniqueId();
+ this.parentUi.querySelector("#object-track-id-editor").value=id;
+ }
+
+ this.activeEditorList().forEach(e=>{
+ if (e.box){
+ e.box.obj_track_id = id;
+ }
+ });
+
+ };
+
+ this.object_category_changed = function(event){
+ let obj_type = event.currentTarget.value;
+ this.activeEditorList().forEach(e=>{
+ if (e.box){
+ e.box.obj_type = obj_type;
+ }
+ });
+ };
+
+
+ this._save = function(){
+ let worldList = []
+ let editorList = []
+ this.activeEditorList().forEach(e=>{
+ worldList.push(e.target.world);
+ editorList.push(e);
+ });
+
+
+ saveWorldList(worldList);
+ }
+
+
+ this.updateViewZoomRatio = function(viewIndex, ratio){
+ const dontRender=true;
+ this.activeEditorList().forEach(e=>{
+ e._setViewZoomRatio(viewIndex, ratio);
+ e.update(dontRender);
+ })
+
+ // render all
+ this.viewManager.render();
+ }
+
+ this.addEditor = function(){
+ let editor = this.allocateEditor();
+ this.activeIndex += 1;
+ return editor;
+ };
+
+ this.allocateEditor = function(){
+ if (this.activeIndex >= this.editorList.length){
+ let editor = new BoxEditor(this.boxEditorGroupUi, this, this.viewManager, cfg, this.boxOp, func_on_box_changed, func_on_box_remove, String(this.activeIndex));
+
+ // resizable for the first editor
+
+ if (this.editorList.length == 0)
+ {
+ editor.setResize("both");
+ }
+
+ this.editorList.push(editor);
+
+ return editor;
+ }else{
+ return this.editorList[this.activeIndex];
+ }
+ };
+
+
+ this.getEditorByMousePosition = function(x,y){
+
+ return this.editorList.find(e=>{
+ let rect = e.ui.getBoundingClientRect();
+
+ return x > rect.left && x < rect.right && y > rect.top && y < rect.bottom;
+ })
+ };
+
+
+ this.parentUi.onmousedown= (event)=>{
+
+ if (event.which!=1)
+ return;
+
+ let eventId = Date.now();
+
+ let select_start_pos={
+ x: event.clientX,
+ y: event.clientY,
+ }
+
+ console.log("box editor manager, on mouse down.", select_start_pos);
+
+ let select_end_pos={
+ x: event.clientX,
+ y: event.clientY,
+ }
+
+ let leftMouseDown = true;
+
+ // a1 a2) [a1,a2]=[a2,a1];
+ if (b1 > b2) [b1,b2]=[b2,b1];
+
+ return (a1 > b1 && a1 < b2) || (a2 > b1 && a2 < b2) || (b1 > a1 && b1 < a2) || (b2 > a1 && b2 < a2)
+ }
+
+ // a,b: left, right, right, bottom
+ function intersect(domRect, mouseA, mouseB){
+ return (lineIntersect(select_end_pos.x, select_start_pos.x, domRect.left, domRect.right) &&
+ lineIntersect(select_end_pos.y, select_start_pos.y, domRect.top, domRect.bottom))
+ }
+
+
+
+ this.parentUi.onmousemove = (event)=>{
+ select_end_pos.x = event.clientX;
+ select_end_pos.y = event.clientY;
+
+ this.editorList.forEach(e=>{
+ let rect = e.ui.getBoundingClientRect();
+ let intersected = intersect(rect, select_start_pos, select_end_pos);
+
+ e.setSelected(intersected, event.ctrlKey?eventId:null);
+
+ })
+ }
+
+ this.parentUi.onmouseup = (event) =>{
+ if (event.which!=1)
+ return;
+
+ leftMouseDown = false;
+ this.parentUi.onmousemove = null;
+ this.parentUi.onmouseup = null;
+
+
+ if (event.clientX == select_start_pos.x && event.clientY == select_start_pos.y)
+ { // click
+
+ let ed = this.getEditorByMousePosition(event.clientX, event.clientY);
+
+
+ if (event.shiftKey)
+ {
+ let selectedEditors = this.getSelectedEditors();
+ if (selectedEditors.length == 0)
+ {
+
+ }
+ else if (ed.index < selectedEditors[0].index)
+ {
+ this.activeEditorList().forEach(e=>{
+ if (e.index >= ed.index && e.index < selectedEditors[0].index){
+ e.setSelected(true);
+ }
+ });
+ }
+ else if (ed.index > selectedEditors[selectedEditors.length-1].index)
+ {
+ this.activeEditorList().forEach(e=>{
+ if (e.index <= ed.index && e.index > selectedEditors[selectedEditors.length-1].index){
+ e.setSelected(true);
+ }
+ });
+ }
+ }
+ else if (event.ctrlKey)
+ {
+ ed.setSelected(!ed.selected);
+ }
+ else
+ {
+ let selectedEditors = this.getSelectedEditors();
+
+
+ if (ed){
+ if (ed.selected && selectedEditors.length == 1)
+ {
+ ed.setSelected(false);
+ }
+ else
+ {
+ selectedEditors.forEach(e=>e.setSelected(false));
+ ed.setSelected(true);
+ }
+ }
+ else{
+ selectedEditors.forEach(e=>e.setSelected(false));
+ }
+ }
+ }
+ }
+ }
+
+ this.getSelectedEditors = function(){
+ return this.editorList.filter(e=>e.selected);
+ }
+
+}
+export {BoxEditorManager, BoxEditor};
\ No newline at end of file
diff --git a/public/js/box_op.js b/public/js/box_op.js
new file mode 100644
index 0000000..8f41f46
--- /dev/null
+++ b/public/js/box_op.js
@@ -0,0 +1,844 @@
+import * as THREE from './lib/three.module.js';
+
+import {logger} from "./log.js"
+import {
+ Quaternion,
+ Vector3
+} from "./lib/three.module.js";
+
+import{ml} from "./ml.js";
+import {dotproduct, transpose, matmul, euler_angle_to_rotate_matrix_3by3} from "./util.js"
+
+
+function BoxOp(){
+ console.log("BoxOp called");
+ this.grow_box_distance_threshold = 0.3;
+ this.init_scale_ratio = {x:2, y:2, z:3};
+
+ this.fit_bottom = function(box)
+ {
+ let bottom = box.world.lidar.findBottom(box, {x:2, y:2, z:3});
+ this.translate_box(box, 'z', bottom + box.scale.z/2);
+ }
+
+ this.fit_top = function(box)
+ {
+ let top = box.world.lidar.findTop(box, {x:1.2, y:1.2, z:2});
+ this.translate_box(box, 'z', top - box.scale.z/2);
+ }
+
+ this.fit_left = function(box)
+ {
+ var extreme = box.world.lidar.grow_box(box, this.grow_box_distance_threshold, this.init_scale_ratio);
+
+ if (extreme){
+ this.translate_box(box, 'y', extreme.max.y - box.scale.y/2);
+ }
+ }
+
+ this.fit_right = function(box)
+ {
+ var extreme = box.world.lidar.grow_box(box, this.grow_box_distance_threshold, this.init_scale_ratio);
+
+ if (extreme){
+ this.translate_box(box, 'y', extreme.min.y + box.scale.y/2);
+ }
+ }
+
+ this.fit_front = function(box)
+ {
+ var extreme = box.world.lidar.grow_box(box, this.grow_box_distance_threshold, this.init_scale_ratio);
+
+ if (extreme){
+ this.translate_box(box, 'x', extreme.max.x - box.scale.x/2);
+ }
+ }
+
+ this.fit_rear = function(box)
+ {
+ var extreme = box.world.lidar.grow_box(box, this.grow_box_distance_threshold, this.init_scale_ratio);
+
+ if (extreme){
+ this.translate_box(box, 'x', extreme.min.x + box.scale.x/2);
+ }
+ }
+
+
+
+
+ this.fit_size = function(box,axies)
+ {
+ this.grow_box(box, this.grow_box_distance_threshold, {x:2, y:2, z:3}, axies);
+ }
+
+
+
+ this.justifyAutoAdjResult = function(orgBox, box)
+ {
+ let distance = Math.sqrt((box.position.x-orgBox.position.x)*(box.position.x-orgBox.position.x) +
+ (box.position.y-orgBox.position.y)*(box.position.y-orgBox.position.y) +
+ (box.position.z-orgBox.position.z)*(box.position.z-orgBox.position.z));
+
+ if (distance > Math.sqrt(box.scale.x*box.scale.x + box.scale.y*box.scale.y + box.scale.z*box.scale.z))
+ {
+ return false;
+ }
+
+ // if (Math.abs(box.rotation.z - orgBox.rotation.z) > Math.PI/4)
+ // {
+ // return false;
+ // }
+
+ if (box.scale.x > orgBox.scale.x*3 ||
+ box.scale.y > orgBox.scale.y*3 ||
+ box.scale.z > orgBox.scale.z*3)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+
+ this.auto_rotate_xyz= async function(box, callback, apply_mask, on_box_changed, noscaling, rotate_method){
+
+ let orgBox = box;
+ box = {
+ position: {x: box.position.x, y: box.position.y, z: box.position.z},
+ rotation: {x: box.rotation.x, y: box.rotation.y, z: box.rotation.z},
+ scale: {x: box.scale.x, y: box.scale.y, z: box.scale.z},
+ world: box.world,
+ };
+
+ // auto grow
+ // save scale
+ let grow = (box)=>{
+ let org_scale = {
+ x: box.scale.x,
+ y: box.scale.y,
+ z: box.scale.z,
+ };
+ this.grow_box(box, this.grow_box_distance_threshold, {x:2, y:2, z:3});
+ this.auto_shrink_box(box);
+ // now box has been centered.
+
+ let points_indices = box.world.lidar.get_points_of_box(box,1.0).index;
+ let extreme = box.world.lidar.get_dimension_of_points(points_indices, box);
+ // restore scale
+ if (noscaling){
+ box.scale.x = org_scale.x;
+ box.scale.y = org_scale.y;
+ box.scale.z = org_scale.z;
+ }
+ //
+ return extreme;
+ };
+
+ //points is N*3 shape
+
+ let applyRotation = (ret, extreme_after_grow)=>{
+
+ let angle = ret.angle;
+ if (!angle){
+ console.log("prediction not implemented?");
+ return;
+ }
+
+
+ //var points_indices = box.world.get_points_indices_of_box(box);
+ let points_indices = box.world.lidar.get_points_of_box(box,1.0).index;
+
+ var euler_delta = {
+ x: angle[0],
+ y: angle[1],
+ z: angle[2]
+ };
+
+ if (euler_delta.z > Math.PI){
+ euler_delta.z -= Math.PI*2;
+ };
+
+ /*
+ var composite_angel = linalg_std.euler_angle_composite(box.rotation, euler_delta);
+
+ console.log("orig ", box.rotation.x, box.rotation.y, box.rotation.z);
+ console.log("delt ", euler_delta.x, euler_delta.y, euler_delta.z);
+ console.log("comp ", composite_angel.x, composite_angel.y, composite_angel.z);
+
+ box.rotation.x = composite_angel.x;
+ box.rotation.y = composite_angel.y;
+ box.rotation.z = composite_angel.z;
+ */
+
+ if (apply_mask){
+ if (apply_mask.x)
+ box.rotation.x = euler_delta.x;
+ if (apply_mask.y)
+ box.rotation.y = euler_delta.y;
+ if (apply_mask.z)
+ box.rotation.z = euler_delta.z;
+ }
+ else{
+ box.rotation.x = euler_delta.x;
+ box.rotation.y = euler_delta.y;
+ box.rotation.z = euler_delta.z;
+ }
+
+
+ // rotation set, now rescaling the box
+ // important: should use original points before rotation set
+ var extreme = box.world.lidar.get_dimension_of_points(points_indices, box);
+
+ let auto_adj_dimension = [];
+
+ if (apply_mask){
+ if (apply_mask.x || apply_mask.y)
+ auto_adj_dimension.push('z');
+
+ if (apply_mask.x || apply_mask.z)
+ auto_adj_dimension.push('y');
+
+ if (apply_mask.y || apply_mask.z)
+ auto_adj_dimension.push('x');
+ }
+ else{
+ auto_adj_dimension = ['x','y','z'];
+ }
+
+ if (!noscaling){
+ auto_adj_dimension.forEach((axis)=>{
+ this.translate_box(box, axis, (extreme.max[axis] + extreme.min[axis])/2);
+ box.scale[axis] = extreme.max[axis] - extreme.min[axis];
+ })
+ }else {
+ //anyway, we move the box in a way
+ let trans = euler_angle_to_rotate_matrix_3by3(box.rotation);
+ trans = transpose(trans, 3);
+
+ // compute the relative position of the origin point,that is, the lidar's position
+ // note the origin point is offseted, we need to restore first.
+ let boxpos = box.position;
+ let orgPoint = [
+ - boxpos.x,
+ - boxpos.y,
+ - boxpos.z,
+ ];
+ let orgPointInBoxCoord = matmul(trans, orgPoint, 3);
+ let relativePosition = {
+ x: orgPointInBoxCoord[0],
+ y: orgPointInBoxCoord[1],
+ z: 1, //orgPointInBoxCoord[2],
+ }
+
+ if (extreme_after_grow)
+ extreme = extreme_after_grow;
+
+ auto_adj_dimension.forEach((axis)=>{
+ if (relativePosition[axis]>0){
+ //stick to max
+ this.translate_box(box, axis, extreme.max[axis] - box.scale[axis]/2);
+ }else{
+ //stick to min
+ this.translate_box(box, axis, extreme.min[axis] + box.scale[axis]/2);
+ }
+
+
+
+ })
+
+ }
+
+ return box;
+ };
+
+
+
+ let postProc = (box)=>{
+
+ if (this.justifyAutoAdjResult(orgBox, box))
+ {
+ // copy back
+ orgBox.position.x = box.position.x;
+ orgBox.position.y = box.position.y;
+ orgBox.position.z = box.position.z;
+
+ orgBox.rotation.x = box.rotation.x;
+ orgBox.rotation.y = box.rotation.y;
+ orgBox.rotation.z = box.rotation.z;
+
+ orgBox.scale.x = box.scale.x;
+ orgBox.scale.y = box.scale.y;
+ orgBox.scale.z = box.scale.z;
+ }
+
+ if (on_box_changed)
+ on_box_changed(orgBox);
+
+ if (callback){
+ callback();
+ }
+ return orgBox;
+ };
+
+ let extreme_after_grow = grow(box);
+
+ if (!rotate_method){
+ let points = box.world.lidar.get_points_relative_coordinates_of_box_wo_rotation(box, 1);
+ //let points = box.world.get_points_relative_coordinates_of_box(box, 1.0);
+
+ points = points.filter(function(p){
+ return p[2] > - box.scale.z/2 + 0.3;
+ })
+
+ let retBox = await ml.predict_rotation(points)
+ .then(applyRotation)
+ .then(postProc);
+
+ return retBox;
+ }
+ if (rotate_method == "moving-direction")
+ {
+ let estimatedRot = this.estimate_rotation_by_moving_direciton(box);
+
+ applyRotation({
+ angle:[
+ box.rotation.x, // use original rotation
+ box.rotation.y, // use original rotation
+ estimatedRot? estimatedRot.z : box.rotation.z, // use original rotation
+ ]
+ },
+ extreme_after_grow);
+
+ postProc(box);
+ return box;
+ }
+ else{ //dont rotate, or null
+ applyRotation({
+ angle:[
+ box.rotation.x, // use original rotation
+ box.rotation.y, // use original rotation
+ box.rotation.z, // use original rotation
+ ]
+ },
+ extreme_after_grow);
+
+ postProc(box);
+ return box;
+ }
+
+
+ }
+
+ this.auto_shrink_box= function(box){
+ var extreme = box.world.lidar.get_points_dimmension_of_box(box);
+
+ ['x', 'y','z'].forEach((axis)=>{
+
+ this.translate_box(box, axis, (extreme.max[axis] + extreme.min[axis])/2);
+ box.scale[axis] = extreme.max[axis]-extreme.min[axis];
+ })
+
+ };
+
+
+ this.estimate_rotation_by_moving_direciton = function(box)
+ {
+ let prevWorld = box.world.data.findWorld(box.world.frameInfo.scene,
+ box.world.frameInfo.frame_index-1);
+
+ let nextWorld = box.world.data.findWorld(box.world.frameInfo.scene,
+ box.world.frameInfo.frame_index+1);
+
+ let prevBox = prevWorld?prevWorld.annotation.findBoxByTrackId(box.obj_track_id): null;
+ let nextBox = nextWorld?nextWorld.annotation.findBoxByTrackId(box.obj_track_id): null;
+
+ if (prevBox && nextBox)
+ {
+ if ((prevBox.annotator && nextBox.annotator) || (!prevBox.annotator && !nextBox.annotator))
+ {
+ // all annotated by machine or man, it's ok
+ }
+ else
+ {
+ // only one is manually annotated, use this one.
+ if (prevBox.annotator)
+ prevBox = null;
+
+ if (nextBox.annotator)
+ nextBox = null;
+ }
+ }
+
+
+ if (!nextBox && !prevBox){
+ logger.logcolor("red", "Cannot estimate direction: neither previous nor next frame/box loaded/annotated.")
+ return null;
+ }
+
+ let currentP = box.world.lidarPosToUtm(box.position);
+ let nextP = nextBox?nextBox.world.lidarPosToUtm(nextBox.position) : null;
+ let prevP = prevBox?prevBox.world.lidarPosToUtm(prevBox.position) : null;
+
+ if (!prevP)
+ prevP = currentP;
+
+ if (!nextP)
+ nextP = currentP;
+
+ let azimuth = Math.atan2(nextP.y-prevP.y, nextP.x-prevP.x)
+
+ let estimatedRot = box.world.utmRotToLidar(new THREE.Euler(0,0,azimuth, "XYZ"));
+
+ return estimatedRot;
+ };
+
+ this.grow_box= function(box, min_distance, init_scale_ratio, axies){
+
+ if (!axies)
+ {
+ axies = ['x','y','z'];
+ }
+
+
+ var extreme = box.world.lidar.grow_box(box, min_distance, init_scale_ratio);
+
+ if (extreme){
+
+ axies.forEach((axis)=>{
+ this.translate_box(box, axis, (extreme.max[axis] + extreme.min[axis])/2);
+ box.scale[axis] = extreme.max[axis] - extreme.min[axis];
+ })
+ }
+
+ };
+
+ this.change_rotation_y = function(box, theta, sticky, on_box_changed){
+ //box.rotation.x += theta;
+ //on_box_changed(box);
+
+ var points_indices = box.world.lidar.get_points_indices_of_box(box);
+
+ var _tempQuaternion = new Quaternion();
+ var rotationAxis = new Vector3(0, 1, 0);
+
+ // NOTE: the front/end subview is different from top/side view, that we look at the reverse direction of y-axis
+ // it's end view acturally.
+ // we could project front-view, but the translation (left, right) will be in reverse direction of top view.
+ /// that would be frustrating.
+ box.quaternion.multiply( _tempQuaternion.setFromAxisAngle( rotationAxis, -theta ) ).normalize();
+
+ if (sticky){
+ var extreme = box.world.lidar.get_dimension_of_points(points_indices, box);
+
+ ['x','z'].forEach((axis)=>{
+
+ this.translate_box(box, axis, (extreme.max[axis] + extreme.min[axis])/2);
+ box.scale[axis] = extreme.max[axis] - extreme.min[axis];
+
+ })
+ }
+
+ if (on_box_changed)
+ on_box_changed(box);
+ }
+
+
+ this.auto_rotate_y=function(box, on_box_changed){
+ let points = box.world.lidar.get_points_of_box(box, 2.0);
+
+ // 1. find surounding points
+ var side_indices = []
+ var side_points = []
+ points.position.forEach(function(p, i){
+ if ((p[0] > box.scale.x/2 || p[0] < -box.scale.x/2) && (p[1] < box.scale.y/2 && p[1] > -box.scale.y/2)){
+ side_indices.push(points.index[i]);
+ side_points.push(points.position[i]);
+ }
+ })
+
+
+ var end_indices = []
+ var end_points = []
+ points.position.forEach(function(p, i){
+ if ((p[0] < box.scale.x/2 && p[0] > -box.scale.x/2) && (p[1] > box.scale.y/2 || p[1] < -box.scale.y/2)){
+ end_indices.push(points.index[i]);
+ end_points.push(points.position[i]);
+ }
+ })
+
+
+ // 2. grid by 0.3 by 0.3
+
+ // compute slope (derivative)
+ // for side part (pitch/tilt), use y,z axis
+ // for end part (row), use x, z axis
+
+
+
+ // box.world.lidar.set_spec_points_color(side_indices, {x:1,y:0,z:0});
+ // box.world.lidar.set_spec_points_color(end_indices, {x:0,y:0,z:1});
+ // box.world.lidar.update_points_color();
+
+ var x = end_points.map(function(x){return x[0]});
+ //var y = side_points.map(function(x){return x[1]});
+ var z = end_points.map(function(x){return x[2]});
+ var z_mean = z.reduce(function(x,y){return x+y;}, 0)/z.length;
+ var z = z.map(function(x){return x-z_mean;});
+ var theta = Math.atan2(dotproduct(x,z), dotproduct(x,x));
+ console.log(theta);
+
+ this.change_rotation_y(box, theta, false, on_box_changed);
+ }
+
+
+
+ this.change_rotation_x=function(box, theta, sticky, on_box_changed){
+ var points_indices = box.world.lidar.get_points_indices_of_box(box);
+
+ //box.rotation.x += theta;
+ //on_box_changed(box);
+ var _tempQuaternion = new Quaternion();
+ var rotationAxis = new Vector3(1,0,0);
+ box.quaternion.multiply( _tempQuaternion.setFromAxisAngle( rotationAxis, theta ) ).normalize();
+
+ if (sticky){
+ var extreme = box.world.lidar.get_dimension_of_points(points_indices, box);
+
+ ['y','z'].forEach((axis)=>{
+
+ this.translate_box(box, axis, (extreme.max[axis] + extreme.min[axis])/2);
+ box.scale[axis] = extreme.max[axis] - extreme.min[axis];
+
+ })
+ }
+
+ if (on_box_changed)
+ on_box_changed(box);
+
+ };
+
+
+ this.auto_rotate_x=function(box, on_box_changed){
+ console.log("x auto ratote");
+
+ let points = box.world.lidar.get_points_of_box(box, 2.0);
+
+ // 1. find surounding points
+ var side_indices = []
+ var side_points = []
+ points.position.forEach(function(p, i){
+ if ((p[0] > box.scale.x/2 || p[0] < -box.scale.x/2) && (p[1] < box.scale.y/2 && p[1] > -box.scale.y/2)){
+ side_indices.push(points.index[i]);
+ side_points.push(points.position[i]);
+ }
+ })
+
+
+ var end_indices = []
+ var end_points = []
+ points.position.forEach(function(p, i){
+ if ((p[0] < box.scale.x/2 && p[0] > -box.scale.x/2) && (p[1] > box.scale.y/2 || p[1] < -box.scale.y/2)){
+ end_indices.push(points.index[i]);
+ end_points.push(points.position[i]);
+ }
+ })
+
+
+ // 2. grid by 0.3 by 0.3
+
+ // compute slope (derivative)
+ // for side part (pitch/tilt), use y,z axis
+ // for end part (row), use x, z axis
+
+
+
+ // box.world.lidar.set_spec_points_color(side_indices, {x:1,y:0,z:0});
+ // box.world.lidar.set_spec_points_color(end_indices, {x:0,y:0,z:1});
+ // box.world.lidar.update_points_color();
+ //render();
+
+ var x = side_points.map(function(x){return x[0]});
+ var y = side_points.map(function(x){return x[1]});
+ var z = side_points.map(function(x){return x[2]});
+ var z_mean = z.reduce(function(x,y){return x+y;}, 0)/z.length;
+ var z = z.map(function(x){return x-z_mean;});
+ var theta = Math.atan2(dotproduct(y,z), dotproduct(y,y));
+ console.log(theta);
+
+ this.change_rotation_x(box, theta, false, on_box_changed);
+ };
+
+
+ this.translate_box=function(box, axis, delta){
+ let t = {x:0, y:0, z:0};
+
+ t[axis] = delta;
+
+ // switch (axis){
+ // case 'x':
+
+ // box.position.x += delta*Math.cos(box.rotation.z);
+ // box.position.y += delta*Math.sin(box.rotation.z);
+ // break;
+ // case 'y':
+ // box.position.x += delta*Math.cos(Math.PI/2 + box.rotation.z);
+ // box.position.y += delta*Math.sin(Math.PI/2 + box.rotation.z);
+ // break;
+ // case 'z':
+ // box.position.z += delta;
+ // break;
+
+ // }
+
+ let trans = this.translateBoxInBoxCoord(box.rotation, t);
+ box.position.x += trans.x;
+ box.position.y += trans.y;
+ box.position.z += trans.z;
+
+ };
+
+ this.translateBoxInBoxCoord = function(rotation, t)
+ {
+ // euler
+ let euler = new THREE.Euler(rotation.x, rotation.y, rotation.z, "XYZ")
+
+ let trans = new THREE.Vector3(t.x, t.y, t.z).applyEuler(euler);
+
+ return trans;
+ };
+
+ this.rotate_z=function(box, theta, sticky){
+ // points indices shall be obtained before rotation.
+ var points_indices = box.world.lidar.get_points_indices_of_box(box);
+
+
+ var _tempQuaternion = new Quaternion();
+ var rotationAxis = new Vector3(0,0,1);
+ box.quaternion.multiply( _tempQuaternion.setFromAxisAngle( rotationAxis, theta ) ).normalize();
+
+ if (sticky){
+
+ var extreme = box.world.lidar.get_dimension_of_points(points_indices, box);
+
+ ['x','y'].forEach((axis)=>{
+
+ this.translate_box(box, axis, (extreme.max[axis] + extreme.min[axis])/2);
+ box.scale[axis] = extreme.max[axis] - extreme.min[axis];
+
+ })
+ }
+ },
+
+
+
+ this.interpolate_selected_object= function(sceneName, objTrackId, currentFrame, done){
+
+ // var xhr = new XMLHttpRequest();
+ // // we defined the xhr
+
+ // xhr.onreadystatechange = function () {
+ // if (this.readyState != 4)
+ // return;
+
+ // if (this.status == 200) {
+ // var ret = JSON.parse(this.responseText);
+ // console.log(ret);
+
+ // if (done)
+ // done(sceneName, ret);
+ // }
+
+ // };
+
+ // xhr.open('GET', "/interpolate?scene="+sceneName+"&frame="+currentFrame+"&obj_id="+objTrackId, true);
+ // xhr.send();
+ };
+
+ this.highlightBox = function(box){
+ if (box){
+ box.material.color.r=1;
+ box.material.color.g=0;
+ box.material.color.b=1;
+ box.material.opacity=1;
+ }
+ };
+
+ this.unhighlightBox = function(box){
+ if (box){
+ // box.material.color = new THREE.Color(parseInt("0x"+get_obj_cfg_by_type(box.obj_type).color.slice(1)));
+
+ // box.material.opacity = box.world.data.cfg.box_opacity;
+
+ box.world.annotation.color_box(box);
+ }
+ }
+
+ this.interpolateAsync = async function(worldList, boxList, applyIndList){
+
+ // if annotator is not null, it's annotated by us algorithms
+ let anns = boxList.map(b=> (!b || b.annotator)? null : b.world.annotation.ann_to_vector_global(b));
+ console.log(anns);
+ let ret = await ml.interpolate_annotation(anns);
+ console.log(ret);
+
+ let refObj = boxList.find(b=>!!b);
+ let obj_type = refObj.obj_type;
+ let obj_track_id = refObj.obj_track_id;
+ let obj_attr = refObj.obj_attr;
+
+ for (let i = 0; i< boxList.length; i++){
+ if (!applyIndList[i])
+ {
+ continue;
+ }
+
+ //
+ let world = worldList[i];
+ let ann = world.annotation.vector_global_to_ann(ret[i]);
+
+ // don't roate x/y
+ if (!pointsGlobalConfig.enableAutoRotateXY)
+ {
+ ann.rotation.x = 0;
+ ann.rotation.y = 0;
+ }
+
+
+ // if (world.lidar.get_box_points_number(ann) == 0)
+ // {
+ // continue;
+ // }
+
+
+ if (!boxList[i]){
+ // create new box
+ let newBox = world.annotation.add_box(ann.position,
+ ann.scale,
+ ann.rotation,
+ obj_type,
+ obj_track_id,
+ obj_attr);
+ newBox.annotator="i";
+ world.annotation.load_box(newBox);
+ world.annotation.setModified();
+
+ } else if (boxList[i].annotator) {
+ // modify box attributes
+ let b = ann;
+
+ boxList[i].position.x = b.position.x;
+ boxList[i].position.y = b.position.y;
+ boxList[i].position.z = b.position.z;
+
+ boxList[i].scale.x = b.scale.x;
+ boxList[i].scale.y = b.scale.y;
+ boxList[i].scale.z = b.scale.z;
+
+ boxList[i].rotation.x = b.rotation.x;
+ boxList[i].rotation.y = b.rotation.y;
+ boxList[i].rotation.z = b.rotation.z;
+
+ boxList[i].annotator = "i";
+
+ boxList[i].world.annotation.setModified();
+ }
+ }
+ };
+
+ this.interpolateAndAutoAdjustAsync = async function(worldList, boxList, onFinishOneBoxCB, applyIndList, dontRotate){
+
+
+ // if annotator is not null, it's annotated by us algorithms
+ let anns = boxList.map((b,i)=> {
+
+ if (!b)
+ return null;
+
+ if (b.annotator)
+ return null;
+
+ return b.world.annotation.ann_to_vector_global(b);
+ });
+
+ console.log("anns to interpolate", anns);
+
+ let autoAdjAsync = async (index, newAnn)=>{
+ //let box = boxList[index];
+ let world = worldList[index];
+
+ let tempBox = world.annotation.vector_global_to_ann(newAnn);
+ tempBox.world = world;
+
+ // autoadj is timecomsuming
+ // jump this step
+ let rotateThis = dontRotate;
+ if (!applyIndList[index]){
+ rotateThis = "dontrotate";
+ }
+
+ let adjustedBox = await this.auto_rotate_xyz(tempBox, null, null, null, true, rotateThis);
+ return world.annotation.ann_to_vector_global(adjustedBox);
+ };
+
+
+ let refObj = boxList.find(b=>!!b);
+ let obj_type = refObj.obj_type;
+ let obj_track_id = refObj.obj_track_id;
+ let obj_attr = refObj.obj_attr;
+
+ let onFinishOneBox= (index)=>{
+ console.log(`auto insert ${index} ${worldList[index].frameInfo.frame}done`);
+ let i = index;
+
+ if (!applyIndList[i]){
+ return;
+ }
+
+ if (!boxList[i]){
+ // create new box
+ let world = worldList[i];
+ let ann = world.annotation.vector_global_to_ann(anns[i]);
+
+ let newBox = world.annotation.add_box(ann.position,
+ ann.scale,
+ ann.rotation,
+ obj_type,
+ obj_track_id,
+ obj_attr);
+ newBox.annotator="a";
+ world.annotation.load_box(newBox);
+
+ } else if (boxList[i].annotator) {
+ // modify box attributes
+ let b = boxList[i].world.annotation.vector_global_to_ann(anns[i]);
+ boxList[i].position.x = b.position.x;
+ boxList[i].position.y = b.position.y;
+ boxList[i].position.z = b.position.z;
+
+ boxList[i].scale.x = b.scale.x;
+ boxList[i].scale.y = b.scale.y;
+ boxList[i].scale.z = b.scale.z;
+
+ boxList[i].rotation.x = b.rotation.x;
+ boxList[i].rotation.y = b.rotation.y;
+ boxList[i].rotation.z = b.rotation.z;
+
+ boxList[i].annotator="a";
+ }
+
+ if (onFinishOneBoxCB)
+ onFinishOneBoxCB(i);
+ };
+
+ let ret = await ml.interpolate_annotation(anns, autoAdjAsync, onFinishOneBox);
+ console.log(ret);
+
+ // for (let i = 0; i< boxList.length; i++){
+ // onFinishOneBox(i);
+ // }
+ };
+
+
+}
+
+export {BoxOp}
\ No newline at end of file
diff --git a/public/js/box_op_desc.js b/public/js/box_op_desc.js
new file mode 100644
index 0000000..e69de29
diff --git a/public/js/calib.js b/public/js/calib.js
new file mode 100644
index 0000000..ab1ef38
--- /dev/null
+++ b/public/js/calib.js
@@ -0,0 +1,196 @@
+
+import {rotation_matrix_to_euler_angle,euler_angle_to_rotate_matrix, matmul, transpose} from "./util.js"
+//import {render_2d_image, update_image_box_projection} from "./image.js"
+
+function Calib(data, editor){
+ this.data = data;
+ this.editor = editor;
+
+ var euler_angle={x:0, y:0, y:0};
+ var translate = {x:0, y:0, z:0};
+
+ this.save_calibration = function(){
+
+
+ var scene_meta = data.meta[data.world.frameInfo.scene];
+
+
+ var active_camera_name = data.world.cameras.active_name;
+ var calib = scene_meta.calib.camera[active_camera_name]
+
+ var extrinsic = calib.extrinsic.map(function(x){return x*1.0;});
+
+ euler_angle = rotation_matrix_to_euler_angle(extrinsic);
+ translate = {
+ x: extrinsic[3]*1.0,
+ y: extrinsic[7]*1.0,
+ z: extrinsic[11]*1.0,
+ };
+
+
+ console.log(extrinsic, euler_angle, translate);
+
+ let matrix = euler_angle_to_rotate_matrix(euler_angle, translate)
+ console.log("restoreed matrix",matrix);
+
+
+ this.editor.infoBox.show("calib", JSON.stringify(matrix));
+ }
+
+ this.reset_calibration = function(){
+ // to be done
+ this.editor.imageContextManager.render_2d_image();
+ }
+
+ this.calib_box = null;
+
+ this.show_camera_pos = function(){
+ this.editor.viewManager.mainView.dumpPose();
+ };
+
+
+ // show a manipulating box
+ this.start_calibration = function(){
+ var scene_meta = this.data.meta[data.world.frameInfo.scene];
+
+ var active_camera_name = this.data.world.cameras.active_name;
+ var calib = scene_meta.calib.camera[active_camera_name]
+ var extrinsic = calib.extrinsic.map(function(x){return x*1.0;});
+ let viewMatrix = [0, -1, 0, 0, //row vector
+ 0, 0, -1, 0,
+ 1, 0, 0, 0,
+ 0, 0, 0, 1];
+ function transpose_transmatrix(m){
+ //m=4*4
+ return [
+ m[0],m[4],m[8],m[3],
+ m[1],m[5],m[9],m[7],
+ m[2],m[6],m[10],m[11],
+ m[12],m[13],m[14],m[15],
+
+ ];
+ }
+
+ var op_matrix = matmul (transpose_transmatrix(viewMatrix),
+ transpose_transmatrix(extrinsic), 4);
+
+ var euler_angle = rotation_matrix_to_euler_angle(op_matrix);
+ var translate = {
+ x: extrinsic[3]*1.0,
+ y: extrinsic[7]*1.0,
+ z: extrinsic[11]*1.0,
+ };
+
+ console.log(euler_angle, translate);
+ this.show_camera_pos();
+
+
+ if (!this.calib_box)
+ {
+ this.calib_box = this.data.world.annotation.createCuboid(
+ {
+ x: translate.x,// + this.data.world.coordinatesOffset[0],
+ y: translate.y,// + this.data.world.coordinatesOffset[1],
+ z: translate.z, // + this.data.world.coordinatesOffset[2]
+ },
+ {x:1,y:1, z:1},
+ {
+ x: euler_angle.x,
+ y: euler_angle.y,
+ z: euler_angle.z
+ },
+ "camera",
+ "camera"
+ );
+
+ this.data.world.scene.add(this.calib_box);
+
+ }
+ else{
+ console.log("calib box exists.");
+ this.calib_box.position.x = translate.x;// + this.data.world.coordinatesOffset[0];
+ this.calib_box.position.y = translate.y;// + this.data.world.coordinatesOffset[1];
+ this.calib_box.position.z = translate.z;// + this.data.world.coordinatesOffset[2];
+
+ this.calib_box.rotation.x = euler_angle.x;
+ this.calib_box.rotation.y = euler_angle.y;
+ this.calib_box.rotation.z = euler_angle.z;
+ }
+
+ console.log(this.calib_box);
+ this.editor.render();
+
+
+ this.calib_box.on_box_changed = ()=>{
+ console.log("calib box changed.");
+
+ let real_pos = {
+ x: this.calib_box.position.x,// - this.data.world.coordinatesOffset[0],
+ y: this.calib_box.position.y,// - this.data.world.coordinatesOffset[1],
+ z: this.calib_box.position.z,// - this.data.world.coordinatesOffset[2],
+ };
+
+ let extrinsic = euler_angle_to_rotate_matrix(this.calib_box.rotation, real_pos);
+ calib.extrinsic = transpose_transmatrix(matmul (viewMatrix, extrinsic, 4));
+ console.log("extrinsic", calib.extrinsic)
+ console.log("euler", euler_angle, "translate", translate);
+
+ this.editor.imageContextManager.render_2d_image();
+ }
+
+
+
+ };
+
+ function stop_calibration()
+ {
+ //tbd
+ };
+
+ /*
+ function calibrate(ax, value){
+ var scene_meta = data.meta[data.world.frameInfo.scene];
+
+ var active_camera_name = data.world.cameras.active_name;
+ var calib = scene_meta.calib.camera[active_camera_name]
+ var extrinsic = calib.extrinsic.map(function(x){return x*1.0;});
+
+ var euler_angle = rotation_matrix_to_euler_angle(extrinsic);
+ var translate = {
+ x: extrinsic[3]*1.0,
+ y: extrinsic[7]*1.0,
+ z: extrinsic[11]*1.0,
+ };
+
+ if (ax == 'z'){
+ euler_angle.z += value;
+ }else if (ax == 'x'){
+ euler_angle.x += value;
+ }
+ else if (ax == 'y'){
+ euler_angle.y += value;
+ }else if (ax == 'tz'){
+ translate.z += value;
+ }else if (ax == 'tx'){
+ translate.x += value;
+ }
+ else if (ax == 'ty'){
+ translate.y += value;
+ }
+
+ calib.extrinsic = euler_angle_to_rotate_matrix(euler_angle, translate);
+
+ console.log("extrinsic", calib.extrinsic)
+ console.log("euler", euler_angle, "translate", translate);
+
+ render_2d_image();
+
+ if (selected_box)
+ update_image_box_projection(selected_box);
+ }
+ */
+
+};
+
+
+export {Calib}
\ No newline at end of file
diff --git a/public/js/config.js b/public/js/config.js
new file mode 100644
index 0000000..66418ad
--- /dev/null
+++ b/public/js/config.js
@@ -0,0 +1,121 @@
+
+class Config{
+
+ //dataCfg = {
+
+ //disableLabels: true,
+ enablePreload = true;
+ color_points = "mono";
+ enableRadar = false;
+ enableAuxLidar = false;
+ enableDynamicGroundLevel = true;
+
+ coordinateSystem = 'utm';
+
+ point_size = 1;
+ point_brightness = 0.6;
+ box_opacity = 1;
+ show_background = true;
+ color_obj = "category";
+ theme = "dark";
+
+ enableFilterPoints = false;
+ filterPointsZ = 2.0;
+
+ batchModeInstNumber = 20;
+ batchModeSubviewSize = {width: 130, height: 450};
+
+
+ // edit on one box, apply to all selected boxes.
+ linkEditorsInBatchMode = false;
+
+ // only rotate z in 'auto/interpolate' algs
+ enableAutoRotateXY = false;
+ autoSave = true;
+
+ autoUpdateInterpolatedBoxes = true;
+
+ hideId = false;
+ hideCategory = false;
+
+ moveStep = 0.01; // ratio, percentage
+ rotateStep = Math.PI/360;
+
+ ignoreDistantObject = true;
+
+ ///editorCfg
+
+ //disableSceneSelector = true;
+ //disableFrameSelector = true;
+ //disableCameraSelector = true;
+ //disableFastToolbox= true;
+ //disableMainView= true;
+ //disableMainImageContext = true;
+ //disableGrid = true;
+ //disableRangeCircle = true;
+ //disableAxis = true;
+ //disableMainViewKeyDown = true;
+ //projectRadarToImage = true;
+ //projectLidarToImage = true;
+
+ constructor()
+ {
+
+ }
+
+ readItem(name, defaultValue, castFunc){
+ let ret = window.localStorage.getItem(name);
+
+ if (ret)
+ {
+ if (castFunc)
+ return castFunc(ret);
+ else
+ return ret;
+ }
+ else
+ {
+ return defaultValue;
+ }
+ }
+
+ setItem(name, value)
+ {
+ this[name] = value;
+ if (typeof value == 'object')
+ value = JSON.stringify(value);
+ window.localStorage.setItem(name, value);
+ }
+
+ toBool(v)
+ {
+ return v==="true";
+ }
+
+ saveItems = [
+ ["theme", null],
+ ["enableRadar", this.toBool],
+ ["enablePreload", this.toBool],
+ ["enableAuxLidar", this.toBool],
+ ["enableFilterPoints", this.toBool],
+ ["filterPointsZ", parseFloat],
+ ["color_points", null],
+ ["coordinateSystem", null],
+ ["batchModeInstNumber", parseInt],
+ ["batchModeSubviewSize", JSON.parse],
+ ["enableAutoRotateXY", this.toBool],
+ ["autoUpdateInterpolatedBoxes", this.toBool],
+ ];
+
+ load()
+ {
+ this.saveItems.forEach(item=>{
+ let key = item[0];
+ let castFunc = item[1];
+
+ this[key] = this.readItem(key, this[key], castFunc);
+ })
+ }
+};
+
+export {Config};
\ No newline at end of file
diff --git a/public/js/config_ui.js b/public/js/config_ui.js
new file mode 100644
index 0000000..2bae001
--- /dev/null
+++ b/public/js/config_ui.js
@@ -0,0 +1,347 @@
+import { globalKeyDownManager } from "./keydown_manager.js";
+import {logger} from "./log.js";
+
+class ConfigUi{
+
+ clickableItems = {
+ "#cfg-increase-size": (event)=>{
+ this.editor.data.scale_point_size(1.2);
+ this.editor.render();
+ this.editor.boxEditorManager.render();
+ return false;
+ },
+
+ "#cfg-decrease-size": (event)=>{
+ this.editor.data.scale_point_size(0.8);
+ this.editor.render();
+ this.editor.boxEditorManager.render();
+ return false;
+ },
+
+ "#cfg-increase-brightness": (event)=>{
+ this.editor.data.scale_point_brightness(1.2);
+ this.editor.render();
+ this.editor.boxEditorManager.render();
+ return false;
+ },
+
+ "#cfg-decrease-brightness": (event)=>{
+ this.editor.data.scale_point_brightness(0.8);
+ this.editor.render();
+ this.editor.boxEditorManager.render();
+ return false;
+ },
+
+ "#cfg-take-screenshot": (event)=>{
+ this.editor.downloadWebglScreenShot();
+ return true;
+ },
+
+ "#cfg-show-log": (event)=>{
+ logger.show();
+ return true;
+ },
+
+ "#cfg-start-calib":(event)=>{
+ this.editor.calib.start_calibration();
+ return true;
+ },
+
+ "#cfg-show-calib":(event)=>{
+ this.editor.calib.save_calibration();
+ return true;
+ },
+
+ // "#cfg-reset-calib":(event)=>{
+ // this.editor.calib.reset_calibration();
+ // return true;
+ // }
+
+ "#cfg-crop-scene": (event)=>{
+ this.editor.cropScene.show();
+
+ return true;
+ },
+
+ };
+
+ changeableItems = {
+
+ "#cfg-theme-select":(event)=>{
+ let theme = event.currentTarget.value;
+
+ //let scheme = document.documentElement.className;
+
+
+ document.documentElement.className = "theme-"+theme;
+
+ pointsGlobalConfig.setItem("theme", theme);
+
+ this.editor.viewManager.setColorScheme();
+ this.editor.render();
+ this.editor.boxEditorManager.render();
+
+ return false;
+ },
+
+ "#cfg-hide-box-checkbox":(event)=>{
+ let checked = event.currentTarget.checked;
+
+ //let scheme = document.documentElement.className;
+
+ if (checked)
+ this.editor.data.set_box_opacity(0);
+ else
+ this.editor.data.set_box_opacity(1);
+
+ this.editor.render();
+ this.editor.boxEditorManager.render();
+
+
+ return false;
+ },
+
+
+ "#cfg-hide-id-checkbox":(event)=>{
+ let checked = event.currentTarget.checked;
+ this.editor.floatLabelManager.show_id(!checked);
+ return false;
+ },
+
+
+
+ "#cfg-hide-category-checkbox":(event)=>{
+ let checked = event.currentTarget.checked;
+ this.editor.floatLabelManager.show_category(!checked);
+ return false;
+ },
+
+ "#cfg-hide-circle-ruler-checkbox": (event)=>{
+ let checked = event.currentTarget.checked;
+ this.editor.showRangeCircle(!checked);
+ return false;
+ },
+
+ "#cfg-auto-rotate-xy-checkbox": (event)=>{
+ let checked = event.currentTarget.checked;
+ pointsGlobalConfig.setItem("enableAutoRotateXY", checked);
+ return false;
+ },
+
+ '#cfg-auto-update-interpolated-boxes-checkbox': (event)=>{
+ let checked = event.currentTarget.checked;
+ pointsGlobalConfig.setItem("autoUpdateInterpolatedBoxes", checked);
+ return false;
+ },
+
+ "#cfg-color-points-select": (event)=>{
+ let value = event.currentTarget.value;
+ pointsGlobalConfig.setItem("color_points", value);
+
+ this.editor.data.worldList.forEach(w=>{
+ w.lidar.color_points();
+ w.lidar.update_points_color();
+ });
+ this.editor.render();
+ return false;
+ },
+
+ "#cfg-color-object-scheme":(event)=>{
+ let value = event.currentTarget.value;
+ this.editor.data.set_obj_color_scheme(value);
+ this.editor.render();
+ this.editor.imageContextManager.render_2d_image();
+
+ this.editor.floatLabelManager.set_color_scheme(value);
+ this.editor.render2dLabels(this.editor.data.world);
+ this.editor.boxEditorManager.render();
+
+ return false;
+ },
+
+ "#cfg-batch-mode-inst-number":(event)=>{
+ let batchSize = parseInt(event.currentTarget.value);
+
+ pointsGlobalConfig.setItem("batchModeInstNumber", batchSize);
+
+ this.editor.boxEditorManager.setBatchSize(batchSize);
+ return false;
+ },
+
+ "#cfg-coordinate-system-select": (event)=>{
+ let coord = event.currentTarget.value;
+ pointsGlobalConfig.setItem("coordinateSystem", coord);
+
+ this.editor.data.worldList.forEach(w=>{
+ w.calcTransformMatrix();
+ });
+ this.editor.render();
+ },
+
+ "#cfg-data-aux-lidar-checkbox": (event)=>{
+ let checked = event.currentTarget.checked;
+
+ pointsGlobalConfig.setItem("enableAuxLidar", checked);
+ return false;
+ },
+
+ "#cfg-data-radar-checkbox": (event)=>{
+ let checked = event.currentTarget.checked;
+
+ pointsGlobalConfig.setItem("enableRadar", checked);
+ return false;
+ },
+
+ "#cfg-data-filter-points-checkbox": (event)=>{
+ let checked = event.currentTarget.checked;
+
+ pointsGlobalConfig.setItem("enableFilterPoints", checked);
+ return false;
+ },
+
+ "#cfg-data-filter-points-z": (event)=>{
+ let z = event.currentTarget.value;
+
+ pointsGlobalConfig.setItem("filterPointsZ", z);
+ return false;
+ },
+
+
+ "#cfg-data-preload-checkbox": (event)=>{
+ let checked = event.currentTarget.checked;
+ pointsGlobalConfig.setItem("enablePreload", checked);
+ return false;
+ }
+
+ };
+
+ ignoreItems = [
+ "#cfg-point-size",
+ "#cfg-point-brightness",
+ "#cfg-theme",
+ "#cfg-color-object",
+ "#cfg-menu-batch-mode-inst-number",
+ "#cfg-hide-box",
+ "#cfg-calib-camera-LiDAR",
+ "#cfg-experimental",
+ "#cfg-data",
+ ];
+
+ subMenus = [
+ "#cfg-experimental",
+ "#cfg-data",
+ ];
+
+ constructor(button, wrapper, editor)
+ {
+ this.button = button;
+ this.wrapper = wrapper;
+ this.editor = editor;
+ this.editorCfg = editor.editorCfg;
+ this.dataCfg = editor.data.cfg;
+ this.menu = this.wrapper.querySelector("#config-menu");
+
+ this.wrapper.onclick = ()=>{
+ this.hide();
+ }
+
+ this.button.onclick = (event)=>{
+ this.show(event.currentTarget);
+ }
+
+ for (let item in this.clickableItems)
+ {
+ this.menu.querySelector(item).onclick = (event)=>{
+ let ret = this.clickableItems[item](event);
+ if (ret)
+ {
+ this.hide();
+ }
+
+ event.stopPropagation();
+ }
+ }
+
+ for (let item in this.changeableItems)
+ {
+ this.menu.querySelector(item).onchange = (event)=>{
+ let ret = this.changeableItems[item](event);
+ if (ret)
+ {
+ this.hide();
+ }
+
+ event.stopPropagation();
+ }
+ }
+
+ this.ignoreItems.forEach(item=>{
+ this.menu.querySelector(item).onclick = (event)=>{
+ {
+ event.stopPropagation();
+ }
+ }
+ });
+
+ this.subMenus.forEach(item=>{
+ this.menu.querySelector(item).onmouseenter = function(event){
+ if (this.timerId)
+ {
+ clearTimeout(this.timerId);
+ this.timerId = null;
+ }
+
+ event.currentTarget.querySelector(item +"-submenu").style.display="inherit";
+ }
+
+ this.menu.querySelector(item).onmouseleave = function(event){
+ let ui = event.currentTarget.querySelector(item +"-submenu");
+ this.timerId = setTimeout(()=>{
+ ui.style.display="none";
+ this.timerId = null;
+ },
+ 200);
+ }
+ });
+
+ this.menu.onclick = (event)=>{
+ event.stopPropagation();
+ };
+
+
+
+ // init ui
+ this.menu.querySelector("#cfg-theme-select").value = pointsGlobalConfig.theme;
+ this.menu.querySelector("#cfg-data-aux-lidar-checkbox").checked = pointsGlobalConfig.enableAuxLidar;
+ this.menu.querySelector("#cfg-data-radar-checkbox").checked = pointsGlobalConfig.enableRadar;
+ this.menu.querySelector("#cfg-color-points-select").value = pointsGlobalConfig.color_points;
+ this.menu.querySelector("#cfg-coordinate-system-select").value = pointsGlobalConfig.coordinateSystem;
+ this.menu.querySelector("#cfg-batch-mode-inst-number").value = pointsGlobalConfig.batchModeInstNumber;
+ this.menu.querySelector("#cfg-data-filter-points-checkbox").checked = pointsGlobalConfig.enableFilterPoints;
+ this.menu.querySelector("#cfg-data-filter-points-z").value = pointsGlobalConfig.filterPointsZ;
+ this.menu.querySelector("#cfg-hide-id-checkbox").value = pointsGlobalConfig.hideId;
+ this.menu.querySelector("#cfg-hide-category-checkbox").value = pointsGlobalConfig.hideCategory;
+ this.menu.querySelector("#cfg-data-preload-checkbox").checked = pointsGlobalConfig.enablePreload;
+ this.menu.querySelector("#cfg-auto-rotate-xy-checkbox").checked = pointsGlobalConfig.enableAutoRotateXY;
+ this.menu.querySelector("#cfg-auto-update-interpolated-boxes-checkbox").checked = pointsGlobalConfig.autoUpdateInterpolatedBoxes;
+ }
+
+
+ show(target){
+ this.wrapper.style.display="inherit";
+
+ this.menu.style.right = "0px";
+ this.menu.style.top = target.offsetHeight + "px";
+
+ globalKeyDownManager.register((event)=>false, 'config');
+ }
+
+ hide(){
+ globalKeyDownManager.deregister('config');
+ this.wrapper.style.display="none";
+ }
+
+}
+
+
+export {ConfigUi}
\ No newline at end of file
diff --git a/public/js/context_menu.js b/public/js/context_menu.js
new file mode 100644
index 0000000..5cfadde
--- /dev/null
+++ b/public/js/context_menu.js
@@ -0,0 +1,199 @@
+import { globalKeyDownManager } from "./keydown_manager.js";
+
+
+class ContextMenu {
+ constructor(ui)
+ {
+ this.wrapperUi = ui;
+
+ this.menus = {
+ world: ui.querySelector("#context-menu"),
+ object: ui.querySelector("#object-context-menu"),
+ boxEditor: ui.querySelector("#box-editor-context-menu"),
+ boxEditorManager: ui.querySelector("#box-editor-manager-context-menu"),
+ playSubMenu: ui.querySelector("#play-submenu"),
+ gotoSubMenu: ui.querySelector("#goto-submenu"),
+ fitSubMenu: ui.querySelector("#cm-fit-submenu"),
+ //thisSubMenu: ui.querySelector("#cm-this-submenu"),
+ };
+
+ for (let m in this.menus){
+ for (let i = 0; i < this.menus[m].children.length; i++)
+ {
+ this.menus[m].children[i].onclick = (event) =>
+ {
+ //event.preventDefault();
+ event.stopPropagation();
+
+ let ret = this.handler.handleContextMenuEvent(event);
+ if (ret)
+ {
+ this.hide();
+ }
+ }
+ }
+ }
+
+ let motherMenu = {
+ "#cm-goto": "#goto-submenu",
+ "#cm-new": "#new-submenu",
+ "#cm-play": "#play-submenu",
+ "#cm-fit": "#cm-fit-submenu",
+ //"#cm-this": "#cm-this-submenu",
+ };
+
+
+ for (let item in motherMenu)
+ {
+ let menu = ui.querySelector(item);
+ menu.onclick = (event)=>{
+ return false;
+ }
+
+ let self = this;
+ menu.onmouseenter = function(event){
+ if (this.timerId)
+ {
+ clearTimeout(this.timerId);
+ this.timerId = null;
+ }
+
+ let menu = event.currentTarget.querySelector(motherMenu[item]);
+ menu.style.display="inherit";
+
+ let motherMenuRect = event.currentTarget.getBoundingClientRect();
+ let posX = motherMenuRect.right;
+ let posY = motherMenuRect.bottom;
+
+ if (self.wrapperUi.clientHeight < posY + menu.clientHeight){
+ menu.style.bottom = "0%";
+ menu.style.top = "";
+ }
+ else{
+ menu.style.top = "0%";
+ menu.style.bottom = "";
+ }
+
+
+ if (self.wrapperUi.clientWidth < posX + menu.clientWidth){
+ menu.style.right = "100%";
+ menu.style.left = "";
+ }
+ else{
+ menu.style.left = "100%";
+ menu.style.right = "";
+ }
+ }
+
+ menu.onmouseleave = function(event){
+ let ui = event.currentTarget.querySelector(motherMenu[item]);
+ this.timerId = setTimeout(()=>{
+ ui.style.display="none";
+ this.timerId = null;
+ },
+ 200);
+ }
+ }
+
+
+ this.wrapperUi.onclick = (event)=>{
+ this.hide();
+ event.preventDefault();
+ event.stopPropagation();
+ };
+
+ this.wrapperUi.oncontextmenu = (event)=>{
+ //event.currentTarget.style.display="none";
+ event.preventDefault();
+ event.stopPropagation();
+ };
+ }
+
+ // install dynamic menu, like object new
+ installMenu(name, ui, funcHandler)
+ {
+ this.menus[name] = ui;
+
+ for (let i = 0; i < ui.children.length; i++){
+ ui.children[i].onclick = (event) =>
+ {
+ //event.preventDefault();
+ event.stopPropagation();
+
+ let ret = funcHandler(event);
+ if (ret)
+ {
+ this.hide();
+ }
+ }
+ }
+ }
+
+ hide()
+ {
+ this.wrapperUi.style.display = "none";
+ globalKeyDownManager.deregister('context menu');
+
+ }
+
+ show(name, posX, posY, handler, funcSetPos)
+ {
+ this.handler = handler;
+
+ //hide all others
+ for (let m in this.menus) {
+ if (m !== name)
+ this.menus[m].style.display = 'none';
+ }
+
+ // show
+ this.wrapperUi.style.display = "block";
+
+ let menu = this.menus[name]
+ menu.style.display = "inherit";
+
+ this.currentMenu = menu;
+
+ if (funcSetPos)
+ {
+ funcSetPos(menu);
+ }
+ else{
+
+ if (this.wrapperUi.clientHeight < posY + menu.clientHeight){
+ menu.style.top = (this.wrapperUi.clientHeight - menu.clientHeight) + "px";
+ }
+ else{
+ menu.style.top = posY+"px";
+ }
+
+
+ if (this.wrapperUi.clientWidth < posX + menu.clientWidth){
+ menu.style.left = (this.wrapperUi.clientWidth - menu.clientWidth) + "px";
+ }
+ else{
+ menu.style.left = posX+"px";
+ }
+
+ }
+
+
+ globalKeyDownManager.register((event)=>{
+
+ let menuRect = this.currentMenu.getBoundingClientRect();
+ let ret = this.handler.handleContextMenuKeydownEvent(event,
+ {x: menuRect.left, y: menuRect.top});
+ if (!ret)
+ {
+ this.hide();
+ }
+
+ return false; // false means don't propogate
+ }, 'context menu');
+
+ }
+
+
+};
+
+export {ContextMenu};
diff --git a/public/js/crop_scene.js b/public/js/crop_scene.js
new file mode 100644
index 0000000..d967b05
--- /dev/null
+++ b/public/js/crop_scene.js
@@ -0,0 +1,64 @@
+import { PopupDialog } from "./popup_dialog.js";
+
+
+class CropScene extends PopupDialog{
+
+
+
+
+ constructor(ui, editor)
+ {
+ super(ui);
+
+ this.ui = ui; //wrapper
+ this.editor = editor;
+
+ this.contentUi = this.ui.querySelector("#content");
+
+
+ let self = this;
+
+ this.ui.querySelector("#btn-generate").onclick = (event)=>{
+ var xhr = new XMLHttpRequest();
+ // we defined the xhr
+ xhr.onreadystatechange = function () {
+ if (this.readyState != 4) return;
+
+ if (this.status == 200) {
+ let ret = JSON.parse(this.responseText);
+ self.contentUi.querySelector("#log").innerText = JSON.stringify(ret, null,"\t");
+ }
+ };
+
+ xhr.open('POST', "/cropscene", true);
+
+ let para={
+ rawSceneId: this.editor.data.world.frameInfo.scene,
+ //id: this.ui.querySelector("#scene-id").value,
+ desc: this.ui.querySelector("#scene-desc").value,
+ startTime: this.ui.querySelector("#scene-start-time").value,
+ seconds: this.ui.querySelector("#scene-seconds").value
+ };
+
+ xhr.send(JSON.stringify(para));
+ }
+ }
+
+
+
+ show()
+ {
+
+ let frameInfo = this.editor.data.world.frameInfo;
+ this.ui.querySelector("#scene-start-time").value=parseInt(frameInfo.frame)-10;
+ this.ui.querySelector("#scene-seconds").value=20;
+ this.contentUi.querySelector("#log").innerText = "";
+ super.show();
+ }
+
+
+
+}
+
+
+export {CropScene};
\ No newline at end of file
diff --git a/public/js/data.js b/public/js/data.js
new file mode 100644
index 0000000..043ddda
--- /dev/null
+++ b/public/js/data.js
@@ -0,0 +1,468 @@
+
+
+import {World} from "./world.js";
+import {Debug} from "./debug.js";
+import {logger} from "./log.js"
+
+class Data
+{
+
+ constructor(cfg)
+ {
+ this.cfg = cfg;
+
+ }
+
+ async readSceneList()
+ {
+ const req = new Request("/get_all_scene_desc");
+ let init = {
+ method: 'GET',
+ //body: JSON.stringify({"points": data})
+ };
+ // we defined the xhr
+
+ return fetch(req, init)
+ .then(response=>{
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }else{
+ return response.json();
+ }
+ })
+ .then(ret=>
+ {
+ console.log(ret);
+ this.sceneDescList = ret;
+ return ret;
+ })
+ .catch(reject=>{
+ console.log("error read scene list!");
+ });
+ }
+
+ async init(){
+ await this.readSceneList();
+ }
+
+ // multiple world support
+ // place world by a offset so they don't overlap
+ dbg = new Debug();
+
+ worldGap=1000.0;
+ worldList=[];
+ MaxWorldNumber=80;
+ createWorldIndex = 0; // this index shall not repeat, so it increases permanently
+
+ async getWorld(sceneName, frame, on_preload_finished){
+ // find in list
+
+ if (!this.meta[sceneName]){
+ await this.readSceneMetaData(sceneName)
+ }
+
+ if (!this.meta[sceneName])
+ {
+ logger.log("load scene failed", sceneName);
+ return null;
+ }
+
+ let world = this.worldList.find((w)=>{
+ return w.frameInfo.scene == sceneName && w.frameInfo.frame == frame;
+ })
+ if (world) // found!
+ return world;
+
+
+ world = this._createWorld(sceneName, frame, on_preload_finished);
+
+ return world;
+ };
+
+ _createWorld(sceneName, frame, on_preload_finished){
+
+ let [x,y,z] = this.allocateOffset();
+ console.log("create world",x,y,z);
+ let world = new World(this, sceneName, frame, [this.worldGap*x, this.worldGap*y, this.worldGap*z], on_preload_finished);
+ world.offsetIndex = [x,y,z];
+ this.createWorldIndex++;
+ this.worldList.push(world);
+
+ return world;
+
+ };
+
+ findWorld(sceneName, frameIndex){
+ let world = this.worldList.find((w)=>{
+ return w.frameInfo.scene == sceneName && w.frameInfo.frame_index == frameIndex;
+ })
+ if (world) // found!
+ return world;
+ else
+ return null;
+ };
+
+ offsetList = [[0,0,0]];
+ lastSeedOffset = [0,0,0];
+ offsetsAliveCount = 0;
+ allocateOffset()
+ {
+
+ // we need to make sure the first frame loaded in a scene
+ // got to locate in [0,0,0]
+
+ if (this.offsetsAliveCount == 0)
+ {
+ //reset offsets.
+ this.offsetList = [[0,0,0]];
+ this.lastSeedOffset = [0,0,0];
+ }
+
+
+
+ if (this.offsetList.length == 0)
+ {
+ let [x,y,z] = this.lastSeedOffset;
+
+ if (x == y)
+ {
+ x = x+1;
+ y = 0;
+ }
+ else
+ {
+ y = y+1;
+ }
+
+ this.lastSeedOffset = [x, y, 0];
+
+ this.offsetList.push([x,y,0]);
+
+ if (x != 0) this.offsetList.push([-x,y,0]);
+ if (y != 0) this.offsetList.push([x,-y,0]);
+ if (x * y != 0) this.offsetList.push([-x,-y,0]);
+
+ if (x != y) {
+ this.offsetList.push([y,x,0]);
+
+ if (y != 0) this.offsetList.push([-y,x,0]);
+ if (x != 0) this.offsetList.push([y,-x,0]);
+ if (x * y != 0) this.offsetList.push([-y,-x,0]);
+ }
+ }
+
+ let ret = this.offsetList.pop();
+ this.offsetsAliveCount++;
+
+ return ret;
+ };
+
+ returnOffset(offset)
+ {
+ this.offsetList.push(offset);
+ this.offsetsAliveCount--;
+ };
+
+ deleteDistantWorlds(world){
+ let currentWorldIndex = world.frameInfo.frame_index;
+
+ let disposable = (w)=>{
+ let distant = Math.abs(w.frameInfo.frame_index - currentWorldIndex)>this.MaxWorldNumber;
+ let active = w.everythingDone;
+ if (w.annotation.modified)
+ {
+ console.log("deleting world not saved. stop.");
+ }
+
+ return distant && !active && !w.annotation.modified;
+ }
+
+ let distantWorldList = this.worldList.filter(w=>disposable(w));
+
+ distantWorldList.forEach(w=>{
+ this.returnOffset(w.offsetIndex);
+ w.deleteAll();
+ });
+
+
+ this.worldList = this.worldList.filter(w=>!disposable(w));
+
+ };
+
+ deleteOtherWorldsExcept=function(keepScene){
+ // release resources if scene changed
+ this.worldList.forEach(w=>{
+ if (w.frameInfo.scene != keepScene){
+ this.returnOffset(w.offsetIndex);
+ w.deleteAll();
+
+ this.removeRefEgoPoseOfScene(w.frameInfo.scene);
+ }
+ })
+ this.worldList = this.worldList.filter(w=>w.frameInfo.scene==keepScene);
+ };
+
+
+ refEgoPose={};
+ getRefEgoPose(sceneName, currentPose)
+ {
+ if (this.refEgoPose[sceneName]){
+ return this.refEgoPose[sceneName];
+ }
+ else{
+ this.refEgoPose[sceneName] = currentPose;
+ return currentPose;
+ }
+ }
+
+ removeRefEgoPoseOfScene(sceneName)
+ {
+ if (this.refEgoPose[sceneName])
+ delete this.refEgoPose[sceneName];
+ }
+
+ forcePreloadScene(sceneName, currentWorld){
+ //this.deleteOtherWorldsExcept(sceneName);
+ let meta = currentWorld.sceneMeta;
+
+ let currentWorldIndex = currentWorld.frameInfo.frame_index;
+ let startIndex = Math.max(0, currentWorldIndex - this.MaxWorldNumber/2);
+ let endIndex = Math.min(meta.frames.length, startIndex + this.MaxWorldNumber);
+
+ this._doPreload(sceneName, startIndex, endIndex);
+
+ logger.log(`${endIndex - startIndex} frames created`);
+ }
+
+ preloadScene(sceneName, currentWorld){
+
+ // clean other scenes.
+ this.deleteOtherWorldsExcept(sceneName);
+ this.deleteDistantWorlds(currentWorld);
+
+ if (!this.cfg.enablePreload)
+ return;
+
+ this.forcePreloadScene(sceneName, currentWorld);
+
+ };
+
+ _doPreload(sceneName, startIndex, endIndex)
+ {
+ let meta = this.getMetaBySceneName(sceneName);
+
+ let numLoaded = 0;
+ let _need_create = (frame)=>{
+ let world = this.worldList.find((w)=>{
+ return w.frameInfo.scene == sceneName && w.frameInfo.frame == frame;
+ })
+
+ return !world;
+ }
+
+ let _do_create = (frame)=>{
+ this._createWorld(sceneName, frame);
+ numLoaded++;
+ };
+
+ let pendingFrames = meta.frames.slice(startIndex, endIndex).filter(_need_create);
+
+ logger.log(`preload ${meta.scene} ${pendingFrames}`);
+ // if (numLoaded > 0){
+ // meta.frames.slice(endIndex, Math.min(endIndex+5, meta.frames.length)).forEach(_do_create);
+ // meta.frames.slice(Math.max(0, startIndex-5), startIndex).forEach(_do_create);
+ // }
+
+ pendingFrames.forEach(_do_create);
+ }
+
+
+ reloadAllAnnotation=function(done){
+ this.worldList.forEach(w=>w.reloadAnnotation(done));
+ };
+
+ onAnnotationUpdatedByOthers(scene, frames){
+ frames.forEach(f=>{
+ let world = this.worldList.find(w=>(w.frameInfo.scene==scene && w.frameInfo.frame==f));
+ if (world)
+ world.annotation.reloadAnnotation();
+ })
+ };
+
+ webglScene = null;
+ set_webglScene=function(scene, mainScene){
+ this.webglScene = scene;
+ this.webglMainScene = mainScene;
+ };
+
+ scale_point_size(v){
+ this.cfg.point_size *= v;
+ // if (this.world){
+ // this.world.lidar.set_point_size(this.cfg.point_size);
+ // }
+
+ this.worldList.forEach(w=>{
+ w.lidar.set_point_size(this.cfg.point_size);
+ });
+ };
+
+ scale_point_brightness(v){
+ this.cfg.point_brightness *= v;
+
+ // if (this.world){
+ // this.world.lidar.recolor_all_points();
+ // }
+
+ this.worldList.forEach(w=>{
+ w.lidar.recolor_all_points();
+ })
+ };
+
+ set_box_opacity(opacity){
+ this.cfg.box_opacity = opacity;
+
+ this.worldList.forEach(w=>{
+ w.annotation.set_box_opacity(this.cfg.box_opacity);
+ });
+ };
+
+ toggle_background(){
+ this.cfg.show_background = !this.cfg.show_background;
+
+ if (this.cfg.show_background){
+ this.world.lidar.cancel_highlight();
+ }
+ else{
+ this.world.lidar.hide_background();
+ }
+ };
+
+ set_obj_color_scheme(scheme){
+
+
+ pointsGlobalConfig.color_obj = scheme;
+
+ // if (pointsGlobalConfig.color_obj != "no"){
+ // this.world.lidar.color_points();
+ // } else {
+ // this.world.lidar.set_points_color({
+ // x: this.cfg.point_brightness,
+ // y: this.cfg.point_brightness,
+ // z: this.cfg.point_brightness,
+ // });
+ // }
+
+ // this.world.lidar.update_points_color();
+ // this.world.annotation.color_boxes();
+
+
+ // toto: move to world
+ this.worldList.forEach(w=>{
+ if (pointsGlobalConfig.color_obj == "no")
+ {
+ w.lidar.color_points();
+ }
+ else
+ {
+ w.lidar.color_objects();
+ }
+
+ w.lidar.update_points_color();
+
+ w.annotation.color_boxes();
+ })
+ };
+
+ // active_camera_name = "";
+
+ // // return null means not changed.
+ // set_active_image(name){
+ // if (name === this.active_camera_name){
+ // return null;
+ // }
+
+ // this.active_camera_name = name;
+ // if (this.world){
+ // this.world.cameras.activate(name);
+ // }
+ // this.worldList.forEach(w=>w.cameras.activate(name));
+
+ // return name;
+ // };
+
+ world=null;
+
+ // this.future_world_buffer = [];
+ // this.put_world_into_buffer= function(world){
+ // this.future_world_buffer.push(world);
+ // };
+
+ // this.reset_world_buffer= function(){
+ // this.future_world_buffer=[];
+ // };
+
+ // this.activateMultiWorld=function(world, on_finished){
+ // world.activate(this.webglScene,
+ // null, //don't destroy old world
+ // on_finished);
+ // this.worldList.push(world);
+ // };
+
+ activate_world= function(world, on_finished, dontDestroyOldWorld){
+
+ if (dontDestroyOldWorld){
+ world.activate(this.webglScene, null, on_finished);
+ }
+ else{
+ var old_world = this.world; // current world, should we get current world later?
+ this.world = world; // swich when everything is ready. otherwise data.world is half-baked, causing mysterious problems.
+
+ world.activate(this.webglMainScene,
+ function(){
+ if (old_world)
+ old_world.unload();
+ },
+ on_finished);
+ }
+ };
+
+
+ meta = {}; //meta data
+
+ getMetaBySceneName = (sceneName)=>{
+ return this.meta[sceneName];
+ };
+
+
+ get_current_world_scene_meta(){
+ return this.getMetaBySceneName(this.world.frameInfo.scene);
+ };
+
+
+ readSceneMetaData(sceneName)
+ {
+ let self =this;
+ return new Promise(function(resolve, reject){
+ let xhr = new XMLHttpRequest();
+
+ xhr.onreadystatechange = function () {
+ if (this.readyState != 4)
+ return;
+
+ if (this.status == 200) {
+ let sceneMeta = JSON.parse(this.responseText);
+ self.meta[sceneName] = sceneMeta;
+ resolve(sceneMeta);
+ }
+
+ };
+
+ xhr.open('GET', `/scenemeta?scene=${sceneName}`, true);
+ xhr.send();
+ });
+ }
+};
+
+
+export {Data};
+
diff --git a/public/js/debug.js b/public/js/debug.js
new file mode 100644
index 0000000..5850cb1
--- /dev/null
+++ b/public/js/debug.js
@@ -0,0 +1,18 @@
+function Debug(){
+ this.res_count = 0;
+
+ this.alloc = function(){
+ this.res_count++;
+ };
+
+ this.free = function(){
+ this.res_count--;
+ };
+
+ this.dump = function(){
+ console.log(`number of resources: ${this.res_count}`);
+ }
+};
+
+export {Debug};
+
diff --git a/public/js/editor.js b/public/js/editor.js
new file mode 100644
index 0000000..8ea6b39
--- /dev/null
+++ b/public/js/editor.js
@@ -0,0 +1,2683 @@
+import * as THREE from './lib/three.module.js';
+
+import {ViewManager} from "./view.js";
+import {FastToolBox, FloatLabelManager} from "./floatlabel.js";
+import {Mouse} from "./mouse.js";
+import {BoxEditor, BoxEditorManager} from "./box_editor.js";
+import {ImageContextManager} from "./image.js";
+import {globalObjectCategory} from "./obj_cfg.js";
+
+import {objIdManager} from "./obj_id_list.js";
+import {Header} from "./header.js";
+import {BoxOp} from './box_op.js';
+import {AutoAdjust} from "./auto-adjust.js";
+import {PlayControl} from "./play.js";
+import {reloadWorldList, saveWorldList} from "./save.js";
+import {logger, create_logger} from "./log.js";
+import {autoAnnotate} from "./auto_annotate.js";
+import {Calib} from "./calib.js";
+import {Trajectory} from "./trajectory.js";
+import { ContextMenu } from './context_menu.js';
+import { InfoBox } from './info_box.js';
+import {CropScene} from './crop_scene.js';
+import { ConfigUi } from './config_ui.js';
+import { MovableView } from './popup_dialog.js';
+import {globalKeyDownManager} from './keydown_manager.js';
+import {vector_range} from "./util.js"
+import { checkScene } from './error_check.js';
+
+function Editor(editorUi, wrapperUi, editorCfg, data, name="editor"){
+
+ // create logger before anything else.
+ create_logger(editorUi.querySelector("#log-wrapper"), editorUi.querySelector("#log-button"));
+ this.logger = logger;
+
+ this.editorCfg = editorCfg;
+ this.sideview_enabled = true;
+ this.editorUi = editorUi;
+ this.wrapperUi = wrapperUi;
+ this.container = null;
+ this.name = name;
+
+ this.data = data;
+ this.scene = null;
+ this.renderer = null;
+ this.selected_box = null;
+ this.windowWidth = null;
+ this.windowHeight= null;
+ this.floatLabelManager = null;
+ this.operation_state = {
+ key_pressed : false,
+ box_navigate_index:0,
+ };
+ this.view_state = {
+ lock_obj_track_id : "",
+ lock_obj_in_highlight : false, // focus mode
+ autoLock: function(trackid, focus){
+ this.lock_obj_track_id = trackid;
+ this.lock_obj_in_highlight = focus;
+ }
+ };
+ this.calib = new Calib(this.data, this);
+
+ this.header = null;
+ this.imageContextManager = null;
+ this.boxOp = null;
+ this.boxEditorManager = null;
+ this.params={};
+
+ this.currentMainEditor = this; // who is on focus, this or batch-editor-manager?
+
+ this.init = function(editorUi) {
+
+ let self = this;
+ this.editorUi = editorUi;
+
+
+
+
+ this.playControl = new PlayControl(this.data);
+
+ this.configUi = new ConfigUi(editorUi.querySelector("#config-button"), editorUi.querySelector("#config-wrapper"), this);
+
+ this.header = new Header(editorUi.querySelector("#header"), this.data, this.editorCfg,
+ (e)=>{
+ this.scene_changed(e.currentTarget.value);
+ //event.currentTarget.blur();
+ },
+ (e)=>{this.frame_changed(e)},
+ (e)=>{this.object_changed(e)},
+ (e)=>{this.camera_changed(e)}
+ );
+
+
+ //
+ // that way, the operation speed may be better
+ // if we load all worlds, we can speed up batch-mode operations, but the singl-world operations slows down.
+ // if we use two seperate scenes. can we solve this problem?
+ //
+ this.scene = new THREE.Scene();
+ this.mainScene = this.scene; //new THREE.Scene();
+
+ this.data.set_webglScene(this.scene, this.mainScene);
+
+
+
+ this.renderer = new THREE.WebGLRenderer( { antialias: true, preserveDrawingBuffer: true } );
+ this.renderer.setPixelRatio( window.devicePixelRatio );
+
+ this.container = editorUi.querySelector("#container");
+ this.container.appendChild( this.renderer.domElement );
+
+
+
+ this.boxOp = new BoxOp(this.data);
+ this.viewManager = new ViewManager(this.container, this.scene, this.mainScene, this.renderer,
+ function(){self.render();},
+ function(box){self.on_box_changed(box)},
+ this.editorCfg);
+
+
+ this.imageContextManager = new ImageContextManager(
+ this.editorUi.querySelector("#content"),
+ this.editorUi.querySelector("#camera-selector"),
+ this.editorCfg,
+ (lidar_points)=>this.on_img_click(lidar_points));
+
+
+ if (!this.editorCfg.disableRangeCircle)
+ this.addRangeCircle();
+
+ this.floatLabelManager = new FloatLabelManager(this.editorUi, this.container, this.viewManager.mainView,function(box){self.selectBox(box);});
+ this.fastToolBox = new FastToolBox(this.editorUi.querySelector("#obj-editor"), (event)=>this.handleFastToolEvent(event));
+ //this.controlGui = this.init_gui();
+
+ this.axis = new THREE.AxesHelper(1);
+
+ this.scene.add(this.axis);
+
+ window.addEventListener( 'resize', function(){self.onWindowResize();}, false );
+
+
+ if (!this.editorCfg.disableMainViewKeyDown){
+ // this.container.onmouseenter = (event)=>{
+ // this.container.focus();
+ // };
+
+ // this.container.onmouseleave = (event)=>{
+ // this.container.blur();
+ // };
+
+ //this.container.addEventListener( 'keydown', function(e){self.keydown(e);} );
+ //this.editorUi.addEventListener( 'keydown', e=>this.keydown(e); );
+
+ this.keydownHandler = (event)=>this.keydown(event);
+ //this.keydownDisabled = false;
+ //document.removeEventListener('keydown', this.keydownHandler);
+ //document.addEventListener( 'keydown', this.keydownHandler);
+ globalKeyDownManager.register(this.keydownHandler, "main editor");
+ }
+
+ this.globalKeyDownManager = globalKeyDownManager;
+
+ this.objectTrackView = new Trajectory(
+ this.editorUi.querySelector("#object-track-wrapper")
+ );
+
+ this.infoBox = new InfoBox(
+ this.editorUi.querySelector("#info-wrapper")
+ );
+
+ this.cropScene = new CropScene(
+ this.editorUi.querySelector("#crop-scene-wrapper"),
+ this
+ );
+
+ this.contextMenu = new ContextMenu(this.editorUi.querySelector("#context-menu-wrapper"));
+
+
+
+ this.boxEditorManager = new BoxEditorManager(
+ document.querySelector("#batch-box-editor"),
+ this.viewManager,
+ this.objectTrackView,
+ this.editorCfg,
+ this.boxOp,
+ this.header,
+ this.contextMenu,
+ this.configUi,
+ (b)=>this.on_box_changed(b),
+ (b,r)=>this.remove_box(b,r), // on box remove
+ ()=>{
+ // this.on_load_world_finished(this.data.world);
+ // this.imageContextManager.hide();
+ // this.floatLabelManager.hide();
+
+ // this.viewManager.mainView.disable();
+ // this.boxEditor.hide();
+ // this.hideGridLines();
+ // this.controlGui.hide();
+
+ }); //func_on_annotation_reloaded
+ this.boxEditorManager.hide();
+
+ let boxEditorUi = this.editorUi.querySelector("#main-box-editor-wrapper");
+ this.boxEditor= new BoxEditor(
+ boxEditorUi,
+ null, // no box editor manager
+ this.viewManager,
+ this.editorCfg,
+ this.boxOp,
+ (b)=>this.on_box_changed(b),
+ (b)=>this.remove_box(b),
+ "main-boxe-ditor");
+ this.boxEditor.detach(); // hide it
+ this.boxEditor.setResize("both");
+ this.boxEditor.moveHandle = new MovableView(
+ boxEditorUi.querySelector("#focuscanvas"),
+ boxEditorUi.querySelector("#sub-views"),
+ ()=>{
+ this.boxEditor.update();
+ this.render();
+ }
+ );
+
+ this.mouse = new Mouse(
+ this.viewManager.mainView,
+ this.operation_state,
+ this.container,
+ this.editorUi,
+ function(ev){self.handleLeftClick(ev);},
+ function(ev){self.handleRightClick(ev);},
+ function(x,y,w,h,ctl,shift){self.handleSelectRect(x,y,w,h,ctl,shift);});
+
+ this.autoAdjust=new AutoAdjust(this.boxOp, this.mouse, this.header);
+
+
+ //this.projectiveViewOps.hide();
+
+ if (!this.editorCfg.disableGrid)
+ this.installGridLines()
+
+ window.onbeforeunload = function() {
+ return "Exit?";
+ //if we return nothing here (just calling return;) then there will be no pop-up question at all
+ //return;
+ };
+
+ this.onWindowResize();
+ };
+
+
+
+ this.run = function(){
+ //this.animate();
+ this.render();
+ //$( "#maincanvas" ).resizable();
+
+
+ this.imageContextManager.init_image_op(()=>this.selected_box);
+
+ this.add_global_obj_type();
+ };
+
+ this.hide = function(){
+ this.wrapperUi.style.display="none";
+ };
+ this.show = function(){
+ this.wrapperUi.style.display="block";
+ };
+
+
+
+
+ this.moveRangeCircle = function(world){
+ if (this.rangeCircle.parent){
+ world.webglGroup.add(this.rangeCircle);
+ }
+ };
+
+ this.addRangeCircle= function(){
+
+ var h = 1;
+
+ var body = [
+ ];
+
+ var segments=64;
+ for (var i = 0; ithis.on_box_changed(b));
+ this.boxOp.auto_rotate_xyz(this.selected_box, null, null,
+ (b)=>this.on_box_changed(b),
+ "noscaling");
+ //event.currentTarget.blur();
+ break;
+
+ case "label-batchedit":
+ {
+
+ if (!this.ensureBoxTrackIdExist())
+ break;
+
+ if (!this.ensurePreloaded())
+ break;
+
+ this.header.setObject(this.selected_box.obj_track_id);
+ this.editBatch(
+ this.data.world.frameInfo.scene,
+ this.data.world.frameInfo.frame,
+ this.selected_box.obj_track_id,
+ this.selected_box.obj_type
+ );
+ }
+ break;
+
+
+ case "label-trajectory":
+ this.showTrajectory();
+ break;
+
+ case "label-edit":
+ event.currentTarget.blur();
+ self.selectBox(self.selected_box);
+ break;
+
+
+ // case "label-reset":
+ // event.currentTarget.blur();
+ // if (self.selected_box){
+ // //switch_bbox_type(this.selected_box.obj_type);
+ // self.transform_bbox("reset");
+ // }
+ // break;
+
+ case "label-highlight":
+ event.currentTarget.blur();
+ if (self.selected_box.in_highlight){
+ self.cancelFocus(self.selected_box);
+ self.view_state.lock_obj_in_highlight = false
+ }
+ else {
+ self.focusOnSelectedBox(self.selected_box);
+ }
+ break;
+
+ case "label-rotate":
+ event.currentTarget.blur();
+ self.transform_bbox("z_rotate_reverse");
+ break;
+
+ case "object-category-selector":
+ this.object_category_changed(event);
+ break;
+ case "object-track-id-editor":
+ this.object_track_id_changed(event);
+ break;
+ case "attr-input":
+ this.object_attribute_changed(event.currentTarget.value);
+ break;
+ default:
+ this.handleContextMenuEvent(event);
+ break;
+ }
+
+ };
+
+ this.cancelFocus= function(box){
+
+ box.in_highlight = false;
+ //view_state.lock_obj_in_highlight = false; // when user unhighlight explicitly, set it to false
+ this.data.world.lidar.cancel_highlight(box);
+ this.floatLabelManager.restore_all();
+
+ this.viewManager.mainView.save_orbit_state(box.scale);
+ this.viewManager.mainView.orbit.reset();
+ };
+
+ this.focusOnSelectedBox = function(box){
+ if (this.editorCfg.disableMainView)
+ return;
+
+ if (box){
+ this.data.world.lidar.highlight_box_points(box);
+
+ this.floatLabelManager.hide_all();
+ this.viewManager.mainView.orbit.saveState();
+
+ //this.viewManager.mainView.camera.position.set(this.selected_box.position.x+this.selected_box.scale.x*3, this.selected_box.position.y+this.selected_box.scale.y*3, this.selected_box.position.z+this.selected_box.scale.z*3);
+
+ let posG = this.data.world.lidarPosToScene(box.position);
+ this.viewManager.mainView.orbit.target.x = posG.x;
+ this.viewManager.mainView.orbit.target.y = posG.y;
+ this.viewManager.mainView.orbit.target.z = posG.z;
+
+ this.viewManager.mainView.restore_relative_orbit_state(box.scale);
+ this.viewManager.mainView.orbit.update();
+
+ this.render();
+ box.in_highlight=true;
+ this.view_state.lock_obj_in_highlight = true;
+ }
+ };
+
+ this.showTrajectory = function(){
+
+ if (!this.selected_box)
+ return;
+
+ if (!this.selected_box.obj_track_id){
+ console.error("no track id");
+ return;
+ }
+
+ let tracks = this.data.worldList.map(w=>{
+ let box = w.annotation.findBoxByTrackId(this.selected_box.obj_track_id);
+ let ann = null;
+ if (box){
+ ann = w.annotation.boxToAnn(box);
+ ann.psr.position = w.lidarPosToUtm(ann.psr.position);
+ ann.psr.rotation = w.lidarRotToUtm(ann.psr.rotation);
+ }
+ return [w.frameInfo.frame, ann, w===this.data.world]
+ });
+
+
+ tracks.sort((a,b)=> (a[0] > b[0])? 1 : -1);
+
+ this.objectTrackView.setObject(
+ this.selected_box.obj_type,
+ this.selected_box.obj_track_id,
+ tracks,
+ (targetFrame)=>{ //onExit
+ this.load_world(this.data.world.frameInfo.scene, targetFrame);
+ }
+ );
+ }
+
+ // return true to close contextmenu
+ // return false to keep contextmenu
+ this.handleContextMenuEvent = function(event){
+
+ switch(event.currentTarget.id)
+ {
+
+ case "cm-play-2fps":
+ this.playControl.play((w)=>{this.on_load_world_finished(w)}, 2);
+ break;
+ case "cm-play-10fps":
+ this.playControl.play((w)=>{this.on_load_world_finished(w)}, 10);
+ break;
+ case "cm-play-20fps":
+ this.playControl.play((w)=>{this.on_load_world_finished(w)}, 20);
+ break;
+ case "cm-play-50fps":
+ this.playControl.play((w)=>{this.on_load_world_finished(w)}, 50);
+ break;
+ case 'cm-paste':
+ {
+ let box = this.add_box_on_mouse_pos_by_ref();
+
+ if (!event.shiftKey)
+ {
+ logger.log('paste without auto-adjusting');
+ this.boxOp.auto_rotate_xyz(box, null, null,
+ b=>this.on_box_changed(b),
+ "noscaling");
+ }
+ }
+ break;
+ case 'cm-prev-frame':
+ this.previous_frame();
+ break;
+ case 'cm-next-frame':
+ this.next_frame();
+ break;
+ case 'cm-last-frame':
+ this.last_frame();
+ break;
+ case 'cm-first-frame':
+ this.first_frame();
+ break;
+ case 'cm-go-to-10hz':
+ this.load_world(this.data.world.frameInfo.scene+"_10hz", this.data.world.frameInfo.frame)
+
+ // {
+ // let link = document.createElement("a");
+ // //link.download=`${this.data.world.frameInfo.scene}-${this.data.world.frameInfo.frame}-webgl`;
+ // link.href="http://localhost";
+ // link.target="_blank";
+ // link.click();
+ // }
+ break;
+ case 'cm-go-to-full-2hz':
+ this.load_world(this.data.world.frameInfo.scene+"_full_2hz", this.data.world.frameInfo.frame)
+ break;
+
+ case 'cm-go-to-2hz':
+ this.load_world(this.data.world.frameInfo.scene.split("_")[0], this.data.world.frameInfo.frame)
+ break;
+
+
+
+ case 'cm-save':
+ saveWorldList(this.data.worldList);
+ break;
+
+ case "cm-reload":
+ {
+ reloadWorldList([this.data.world], ()=>{
+ this.on_load_world_finished(this.data.world);
+ this.header.updateModifiedStatus();
+ });
+
+ }
+ break;
+
+ case "cm-reload-all":
+ {
+ let modifiedFrames = this.data.worldList.filter(w=>w.annotation.modified);
+
+ if (modifiedFrames.length > 0)
+ {
+ this.infoBox.show(
+ "Confirm",
+ `Discard changes to ${modifiedFrames.length} frames, continue to reload?`,
+ ["yes","no"],
+ (choice)=>{
+ if (choice=="yes")
+ {
+ reloadWorldList(this.data.worldList, ()=>{
+ this.on_load_world_finished(this.data.world);
+ this.header.updateModifiedStatus();
+ });
+ }
+ }
+ );
+ }
+ else
+ {
+ reloadWorldList(this.data.worldList, ()=>{
+ this.on_load_world_finished(this.data.world);
+ this.header.updateModifiedStatus();
+ });
+
+ objIdManager.forceUpdate();
+ }
+ }
+ break;
+
+
+ case "cm-stop":
+ this.playControl.stop_play();
+ break;
+ case "cm-pause":
+ this.playControl.pause_resume_play();
+ break;
+
+ case "cm-prev-object":
+ this.select_previous_object();
+ break;
+
+ case "cm-next-object":
+ this.select_previous_object();
+ break;
+
+ case "cm-show-frame-info":
+ {
+ let info = {"scend-id": this.data.world.frameInfo.scene,
+ "frame": this.data.world.frameInfo.frame
+ };
+
+ if (this.data.world.frameInfo.sceneMeta.desc)
+ {
+ info = {
+ ...info,
+ ...this.data.world.frameInfo.sceneMeta.desc,
+ };
+ }
+
+ this.infoBox.show("Frame info - " + this.data.world.frameInfo.scene, JSON.stringify(info,null," "));
+ }
+ break;
+
+ case "cm-show-stat":
+ {
+ let scene = this.data.world.frameInfo.scene;
+ objIdManager.load_obj_ids_of_scene(scene, (objs)=>{
+ let info = {
+ objects: objs.length,
+ boxes: objs.reduce((a,b)=>a+b.count, 0),
+ frames: this.data.world.frameInfo.sceneMeta.frames.length,
+ };
+
+ this.infoBox.show("Stat - " + scene, JSON.stringify(info, null," "));
+ });
+ }
+ break;
+ /// object
+
+ case 'cm-check-scene':
+ {
+ let scene = this.data.world.frameInfo.scene;
+ checkScene(scene);
+ logger.show();
+ logger.errorBtn.onclick();
+ }
+ break;
+ case "cm-reset-view":
+ this.resetView();
+ break;
+ case "cm-delete":
+ this.remove_selected_box();
+ this.header.updateModifiedStatus();
+ break;
+
+ case "cm-edit-multiple-instances":
+ this.enterBatchEditMode();
+
+ break;
+ case "cm-auto-ann-background":
+ {
+ this.autoAnnInBackground();
+
+ }
+ break;
+ case "cm-interpolate-background":
+ {
+ this.interpolateInBackground();
+ }
+ break;
+ case "cm-show-trajectory":
+
+ this.showTrajectory();
+ break;
+
+ case "cm-select-as-ref":
+ if (!this.selected_box.obj_track_id)
+ {
+ this.infoBox.show("Error", "Please assign object track ID.");
+ return false;
+ }
+ else
+ {
+ this.autoAdjust.mark_bbox(this.selected_box);
+ }
+ break;
+
+ case "cm-change-id-to-ref":
+ if (!this.ensureRefObjExist())
+ break;
+
+ this.setObjectId(this.autoAdjust.marked_object.ann.obj_id);
+ this.fastToolBox.setValue(this.selected_box.obj_type,
+ this.selected_box.obj_track_id,
+ this.selected_box.obj_attr);
+
+ break;
+ case "cm-change-id-to-ref-in-scene":
+
+ if (!this.ensureBoxTrackIdExist())
+ break;
+ if (!this.ensurePreloaded())
+ break;
+ if (!this.ensureRefObjExist())
+ break;
+
+ this.data.worldList.forEach(w=>{
+ let box = w.annotation.boxes.find(b=>b.obj_track_id === this.selected_box.obj_track_id && b.obj_type === this.selected_box.obj_type);
+ if (box && box !== this.selected_box){
+ box.obj_track_id = this.autoAdjust.marked_object.ann.obj_id;
+ w.annotation.setModified();
+ }
+ });
+
+
+ this.setObjectId(this.autoAdjust.marked_object.ann.obj_id);
+ this.fastToolBox.setValue(this.selected_box.obj_type,
+ this.selected_box.obj_track_id,
+ this.selected_box.obj_attr);
+
+ break;
+ case "cm-follow-ref":
+
+ if (!this.ensureBoxTrackIdExist())
+ break;
+ if (!this.ensurePreloaded())
+ break;
+ this.autoAdjust.followsRef(this.selected_box);
+ this.header.updateModifiedStatus();
+ this.editBatch(
+ this.data.world.frameInfo.scene,
+ this.data.world.frameInfo.frame,
+ this.selected_box.obj_track_id,
+ this.selected_box.obj_type
+ );
+ break;
+ case 'cm-follow-static-objects':
+ if (!this.ensureBoxTrackIdExist())
+ break;
+ if (!this.ensurePreloaded())
+ break;
+ this.autoAdjust.followStaticObjects(this.selected_box);
+ this.header.updateModifiedStatus();
+
+ this.editBatch(
+ this.data.world.frameInfo.scene,
+ this.data.world.frameInfo.frame,
+ this.selected_box.obj_track_id,
+ this.selected_box.obj_type
+ );
+
+ break;
+ case "cm-sync-followers":
+
+ if (!this.ensurePreloaded())
+ break;
+ this.autoAdjust.syncFollowers(this.selected_box);
+ this.header.updateModifiedStatus();
+ this.render();
+ break;
+
+
+ case "cm-delete-obj":
+ {
+ //let saveList=[];
+ this.data.worldList.forEach(w=>{
+ let box = w.annotation.boxes.find(b=>b.obj_track_id === this.selected_box.obj_track_id);
+ if (box && box !== this.selected_box){
+ w.annotation.unload_box(box);
+ w.annotation.remove_box(box);
+ //saveList.push(w);
+ w.annotation.setModified();
+ }
+ });
+
+ //saveWorldList(saveList);
+ this.remove_selected_box();
+ this.header.updateModifiedStatus();
+ }
+ break;
+
+ case "cm-modify-obj-type":
+ {
+ if (!this.ensurePreloaded())
+ break;
+ //let saveList=[];
+ this.data.worldList.forEach(w=>{
+ let box = w.annotation.boxes.find(b=>b.obj_track_id === this.selected_box.obj_track_id);
+ if (box && box !== this.selected_box){
+ box.obj_type = this.selected_box.obj_type;
+ box.obj_attr = this.selected_box.obj_attr;
+ //saveList.push(w);
+ w.annotation.setModified();
+ }
+
+ });
+
+ //saveWorldList(saveList);
+ this.header.updateModifiedStatus();
+ }
+ break;
+
+ case "cm-modify-obj-size":
+ {
+ if (!this.ensurePreloaded())
+ break;
+ //let saveList=[];
+ this.data.worldList.forEach(w=>{
+ let box = w.annotation.boxes.find(b=>b.obj_track_id == this.selected_box.obj_track_id);
+ if (box && box !== this.selected_box){
+ box.scale.x = this.selected_box.scale.x;
+ box.scale.y = this.selected_box.scale.y;
+ box.scale.z = this.selected_box.scale.z;
+ //saveList.push(w);
+
+ w.annotation.setModified();
+ }
+
+ });
+
+ //saveWorldList(saveList);
+ this.header.updateModifiedStatus();
+ }
+ break;
+
+
+ default:
+ console.log('unhandled', event.currentTarget.id, event.type);
+ }
+
+ return true;
+ };
+
+ // this.animate= function() {
+ // let self=this;
+ // requestAnimationFrame( function(){self.animate();} );
+ // this.viewManager.mainView.orbit_orth.update();
+ // };
+
+
+
+ this.render= function(){
+
+ this.viewManager.mainView.render();
+ this.boxEditor.boxView.render();
+
+ this.floatLabelManager.update_all_position();
+ if (this.selected_box){
+ this.fastToolBox.setPos(this.floatLabelManager.getLabelEditorPos(this.selected_box.obj_local_id));
+ }
+ };
+
+
+ this.resetView = function(targetPos){
+
+ if (!targetPos){
+ let center = this.data.world.lidar.computeCenter();
+ targetPos = {...center};//{x:0, y:0, z:50};
+ targetPos.z += 50;
+ }
+ else
+ targetPos.z = 50;
+
+ let pos = this.data.world.lidarPosToScene(targetPos);
+ this.viewManager.mainView.orbit.object.position.set(pos.x, pos.y, pos.z); //object is camera
+ this.viewManager.mainView.orbit.target.set(pos.x, pos.y, 0);
+ this.viewManager.mainView.orbit.update();
+ this.render();
+ };
+
+ this.scene_changed= async function(sceneName){
+
+ //var sceneName = event.currentTarget.value;
+
+ if (sceneName.length == 0){
+ return;
+ }
+
+ console.log("choose sceneName " + sceneName);
+ var meta = this.data.getMetaBySceneName(sceneName);
+
+ if (!meta)
+ {
+ this.editorUi.querySelector("#frame-selector").innerHTML = "--frame-- ";
+ meta = await this.data.readSceneMetaData(sceneName);
+ }
+
+ var frame_selector_str = meta.frames.map(function(f){
+ return ""+f + " ";
+ }).reduce(function(x,y){return x+y;}, "--frame-- ");
+
+ this.editorUi.querySelector("#frame-selector").innerHTML = frame_selector_str;
+
+
+ if (meta.camera){
+ this.imageContextManager.updateCameraList(meta.camera);
+ }
+
+ //load_obj_ids_of_scene(sceneName);
+ };
+
+ this.frame_changed= function(event){
+ var sceneName = this.editorUi.querySelector("#scene-selector").value;
+
+ if (sceneName.length == 0 && this.data.world)
+ {
+ sceneName = this.data.world.frameInfo.scene;
+ }
+
+ if (sceneName.length == 0){
+ return;
+ }
+
+ var frame = event.currentTarget.value;
+ console.log(sceneName, frame);
+ this.load_world(sceneName, frame);
+ event.currentTarget.blur();
+ };
+
+
+ this.ensureBoxTrackIdExist = function()
+ {
+ if (!this.selected_box.obj_track_id)
+ {
+ this.infoBox.show("Error", "Please assign object track ID.");
+ return false;
+ }
+
+ return true;
+ }
+
+ this.ensureRefObjExist = function()
+ {
+ if (!this.autoAdjust.marked_object)
+ {
+ this.infoBox.show("Notice", 'No reference object was selected');
+ return false;
+ }
+
+
+ return true;
+ }
+ this.ensurePreloaded = function()
+ {
+ let worldList = this.data.worldList.filter(w=>w.frameInfo.scene == this.data.world.frameInfo.scene);
+ worldList = worldList.sort((a,b)=>a.frameInfo.frame_index - b.frameInfo.frame_index);
+
+ let meta = this.data.get_current_world_scene_meta();
+
+
+ let allLoaded = worldList.map(w=>w.preloaded()).reduce((a,b)=>a && b, true);
+
+ if ((worldList.length < meta.frames.length && worldList.length <= 60) || (!allLoaded))
+ {
+ this.data.forcePreloadScene(this.data.world.frameInfo.scene, this.data.world);
+
+ this.infoBox.show("Notice",
+ `Loading scene in background. Please try again later.`);
+ return false;
+ }
+
+
+ return true;
+ }
+
+
+ this.interpolateInBackground = function()
+ {
+
+ if (!this.ensureBoxTrackIdExist())
+ return;
+
+ if (!this.ensurePreloaded())
+ return;
+
+ let worldList = this.data.worldList.filter(w=>w.frameInfo.scene == this.data.world.frameInfo.scene);
+ worldList = worldList.sort((a,b)=>a.frameInfo.frame_index - b.frameInfo.frame_index);
+ let boxList = worldList.map(w=>w.annotation.findBoxByTrackId(this.selected_box.obj_track_id));
+
+ let applyIndList = boxList.map(b=>true);
+ this.boxOp.interpolateAsync(worldList, boxList, applyIndList).then(ret=>{
+ this.header.updateModifiedStatus();
+ this.viewManager.render();
+ });
+ };
+ this.enterBatchEditMode = function()
+ {
+ if (!this.ensureBoxTrackIdExist())
+ return;
+
+ if (!this.ensurePreloaded())
+ return;
+
+ this.header.setObject(this.selected_box.obj_track_id);
+
+ this.editBatch(
+ this.data.world.frameInfo.scene,
+ this.data.world.frameInfo.frame,
+ this.selected_box.obj_track_id,
+ this.selected_box.obj_type
+ );
+ };
+
+ this.autoAnnInBackground = function()
+ {
+ if (!this.ensureBoxTrackIdExist())
+ return;
+
+ if (!this.ensurePreloaded())
+ return;
+
+ let worldList = this.data.worldList.filter(w=>w.frameInfo.scene == this.data.world.frameInfo.scene);
+ worldList = worldList.sort((a,b)=>a.frameInfo.frame_index - b.frameInfo.frame_index);
+
+
+
+ let boxList = worldList.map(w=>w.annotation.findBoxByTrackId(this.selected_box.obj_track_id));
+
+ let onFinishOneBox = (i)=>{
+ this.viewManager.render();
+ }
+ let applyIndList = boxList.map(b=>true);
+ let dontRotate = false;
+
+ this.boxOp.interpolateAndAutoAdjustAsync(worldList, boxList, onFinishOneBox, applyIndList, dontRotate).then(ret=>{
+ this.header.updateModifiedStatus();
+ });
+ };
+
+
+ this.editBatch = function(sceneName, frame, objectTrackId, objectType){
+
+
+
+ //this.keydownDisabled = true;
+ // hide something
+ this.imageContextManager.hide();
+ this.floatLabelManager.hide();
+
+ //this.floatLabelManager.showFastToolbox();
+
+ this.viewManager.mainView.disable();
+ this.boxEditor.hide();
+ this.hideGridLines();
+ //this.controlGui.hide();
+ this.editorUi.querySelector("#selectors").style.display='none';
+ //this.editorUi.querySelector("#object-selector").style.display='none';
+ this.currentMainEditor = this.boxEditorManager;
+
+ this.boxEditorManager.edit(this.data,
+ this.data.getMetaBySceneName(sceneName),
+ frame,
+ objectTrackId,
+ objectType,
+ (targetFrame, targetTrackId)=>{ //on exit
+ this.currentMainEditor = this
+ //this.keydownDisabled = false;
+ this.viewManager.mainView.enable();
+
+ this.imageContextManager.show();
+ this.floatLabelManager.show();
+
+ if (targetTrackId)
+ this.view_state.lock_obj_track_id = targetTrackId;
+
+ this.on_load_world_finished(this.data.world);
+
+ // if (this.selected_box){
+ // // attach again, restore box.boxEditor
+ // // obj type/id may have changed in batch mode
+ // this.floatLabelManager.set_object_track_id(this.selected_box.obj_local_id, this.selected_box.obj_track_id);
+ // this.boxEditor.attachBox(this.selected_box);
+ // this.boxEditor.update();
+
+ // // update fasttoolbox
+ // this.fastToolBox.setValue(this.selected_box.obj_type, this.selected_box.obj_track_id, this.selected_box.obj_attr);
+ // }
+
+
+ this.showGridLines();
+ this.render();
+ //this.controlGui.show();
+ this.editorUi.querySelector("#selectors").style.display='inherit';
+
+ if (targetFrame)
+ {
+ this.load_world(this.data.world.frameInfo.scene, targetFrame, ()=>{ // onfinished
+ this.makeVisible(targetTrackId);
+ });
+ }
+ }
+ );
+ };
+
+ this.gotoObjectFrame = function(frame, objId)
+ {
+ this.load_world(this.data.world.frameInfo.scene, frame, ()=>{ // onfinished
+ this.makeVisible(objId);
+ });
+ };
+
+ this.makeVisible = function(targetTrackId){
+ let box = this.data.world.annotation.findBoxByTrackId(targetTrackId);
+
+ if (box){
+ if (this.selected_box != box){
+ this.selectBox(box);
+ }
+
+ this.resetView({x:box.position.x, y:box.position.y, z:50});
+ }
+
+ };
+
+ this.object_changed = function(event){
+ var sceneName = this.data.world.frameInfo.scene; //this.editorUi.querySelector("#scene-selector").value;
+
+ let objectTrackId = event.currentTarget.value;
+ let obj = objIdManager.getObjById(objectTrackId);
+
+ this.editBatch(sceneName, null, objectTrackId, obj.category);
+ };
+
+ this.camera_changed= function(event){
+ var camera_name = event.currentTarget.value;
+
+ this.data.set_active_image(camera_name);
+ this.imageContextManager.render_2d_image();
+
+ event.currentTarget.blur();
+ };
+
+ this.downloadWebglScreenShot = function(){
+ let link = document.createElement("a");
+ link.download=`${this.data.world.frameInfo.scene}-${this.data.world.frameInfo.frame}-webgl`;
+ link.href=this.renderer.domElement.toDataURL("image/png", 1);
+ link.click();
+ };
+
+ this.showLog = function() {
+
+ };
+
+ this.annotateByAlg1 = function(){
+ autoAnnotate(this.data.world, ()=>this.on_load_world_finished(this.data.world));
+ };
+
+
+
+ this.object_category_changed= function(event){
+ if (this.selected_box){
+
+ let category = event.currentTarget.value;
+
+ this.selected_box.obj_type = category;
+ this.floatLabelManager.set_object_type(this.selected_box.obj_local_id, this.selected_box.obj_type);
+ // this.header.mark_changed_flag();
+ // this.updateBoxPointsColor(this.selected_box);
+ // this.imageContextManager.boxes_manager.update_obj_type(this.selected_box.obj_local_id, this.selected_box.obj_type);
+
+ // this.render();
+ this.on_box_changed(this.selected_box);
+
+ //todo: we don't know if the old one is already deleted.
+ // could use object count number?
+ objIdManager.addObject({
+ category: this.selected_box.obj_type,
+ id: this.selected_box.obj_track_id,
+ });
+
+ }
+ };
+
+ this.setObjectId = function(id)
+ {
+ this.selected_box.obj_track_id = id;
+ this.floatLabelManager.set_object_track_id(this.selected_box.obj_local_id, this.selected_box.obj_track_id);
+
+ this.view_state.lock_obj_track_id = id;
+
+ //this.header.mark_changed_flag();
+ this.on_box_changed(this.selected_box);
+
+ //
+ objIdManager.addObject({
+ category: this.selected_box.obj_type,
+ id: this.selected_box.obj_track_id,
+ });
+ }
+
+ this.object_track_id_changed= function(event){
+ if (this.selected_box){
+ var id = event.currentTarget.value;
+ this.setObjectId(id);
+ }
+ };
+
+ this.object_attribute_changed = function(value){
+ if (this.selected_box){
+ this.selected_box.obj_attr = value;
+ this.floatLabelManager.set_object_attr(this.selected_box.obj_local_id, value);
+ this.data.world.annotation.setModified();
+ this.header.updateModifiedStatus();
+ }
+ };
+
+ // this.updateSubviewRangeByWindowResize= function(box){
+
+ // if (box === null)
+ // return;
+
+ // if (box.boxEditor)
+ // box.boxEditor.onWindowResize();
+
+ // this.render();
+ // };
+
+ this.handleRightClick= function(event){
+
+ // select new object
+
+ if (!this.data.world){
+ return;
+ }
+
+
+ if (event.shiftKey || event.ctrlKey)
+ {
+ // if ctrl or shift hold, don't select any object.
+ this.contextMenu.show("world",event.layerX, event.layerY, this);
+ return;
+ }
+
+
+ var intersects = this.mouse.getIntersects( this.mouse.onUpPosition, this.data.world.annotation.boxes );
+ if ( intersects.length > 0 ) {
+ //var object = intersects[ 0 ].object;
+ var object = intersects[ 0 ].object;
+ let target_obj = object.userData.object;
+ if ( target_obj == undefined ) {
+ // helper
+ target_obj = object;
+ }
+
+ if (target_obj != this.selected_box){
+ this.selectBox(target_obj);
+ }
+
+ // this.hide_world_context_menu();
+ // this.show_object_context_menu(event.layerX, event.layerY);
+ this.contextMenu.show("object",event.layerX, event.layerY, this);
+
+ } else {
+ // if no object is selected, popup context menu
+ //var pos = getMousePosition(renderer.domElement, event.clientX, event.clientY );
+ this.contextMenu.show("world",event.layerX, event.layerY, this);
+ }
+ };
+
+ this.show_world_context_menu= function(posX, posY){
+ let menu = this.editorUi.querySelector("#context-menu");
+ menu.style.display = "inherit";
+ menu.style.left = posX+"px";
+ menu.style.top = posY+"px";
+ this.editorUi.querySelector("#context-menu-wrapper").style.display = "block";
+ };
+
+ this.hide_world_context_menu= function(){
+ let menu = this.editorUi.querySelector("#context-menu");
+ menu.style.display = "none";
+ };
+
+ this.show_object_context_menu= function(posX, posY){
+ let menu = this.editorUi.querySelector("#object-context-menu");
+ menu.style.display = "inherit";
+ menu.style.left = posX+"px";
+ menu.style.top = posY+"px";
+ this.editorUi.querySelector("#context-menu-wrapper").style.display = "block";
+ };
+
+ this.hide_object_context_menu= function(){
+ let menu = this.editorUi.querySelector("#object-context-menu");
+ menu.style.display = "none";
+ };
+
+ this.on_img_click = function(lidar_point_indices){
+
+ console.log(lidar_point_indices);
+
+ var self=this;
+ let obj_type = "Car";
+ this.data.world.lidar.set_spec_points_color(lidar_point_indices, {x:0,y:0,z:1});
+ this.data.world.lidar.update_points_color();
+ this.render();
+ //return;
+
+ let pos = this.data.world.lidar.get_centroid(lidar_point_indices);
+ pos.z = 0;
+
+ let rotation = {x:0, y:0, z:this.viewManager.mainView.camera.rotation.z+Math.PI/2};
+
+ let obj_cfg = globalObjectCategory.get_obj_cfg_by_type(obj_type);
+ let scale = {
+ x: obj_cfg.size[0],
+ y: obj_cfg.size[1],
+ z: obj_cfg.size[2]
+ };
+
+ let box = this.add_box(pos, scale, rotation, obj_type, "");
+ self.boxOp.auto_rotate_xyz(box, null, null, function(b){
+ self.on_box_changed(b);
+ });
+
+ return;
+ /*
+ var box = this.data.world.lidar.create_box_by_points(lidar_point_indices, this.viewManager.mainView.camera);
+
+
+ this.scene.add(box);
+
+ this.imageContextManager.boxes_manager.add_box(box);
+
+
+ this.boxOp.auto_shrink_box(box);
+
+
+ // guess obj type here
+
+ box.obj_type = guess_obj_type_by_dimension(box.scale);
+
+ this.floatLabelManager.add_label(box);
+
+ this.selectBox(box);
+ this.on_box_changed(box);
+
+
+ this.boxOp.auto_rotate_xyz(box, function(){
+ box.obj_type = guess_obj_type_by_dimension(box.scale);
+ self.floatLabelManager.set_object_type(box.obj_local_id, box.obj_type);
+ self.floatLabelManager.update_label_editor(box.obj_type, box.obj_track_id);
+ self.on_box_changed(box);
+ });
+ */
+
+ };
+
+ this.handleSelectRect= function(x,y,w,h, ctrl, shift){
+ // y = y+h;
+ // x = x*2-1;
+ // y = -y*2+1;
+ // w *= 2;
+ // h *= 2;
+
+ // x,y: start cornor, w: width, h: height
+
+ /*
+ console.log("main select rect", x,y,w,h);
+
+ this.viewManager.mainView.camera.updateProjectionMatrix();
+ this.data.world.select_points_by_view_rect(x,y,w,h, this.viewManager.mainView.camera);
+ render();
+ render_2d_image();
+ */
+
+ // check if any box is inside the rectangle
+
+ this.viewManager.mainView.camera.updateProjectionMatrix();
+
+ let boxes = this.data.world.annotation.find_boxes_inside_rect(x,y,w,h, this.viewManager.mainView.camera);
+ if (boxes.length > 0) {
+
+ if (boxes.length == 1){
+ this.selectBox(boxes[0])
+ }
+ else{
+ // this is dangerous
+ // for (let b in boxes){
+ // this.remove_box(boxes[b],false)
+ // }
+ // this.render();
+ }
+
+ return;
+ }
+
+ let points = this.data.world.lidar.select_points_by_view_rect(x,y,w,h, this.viewManager.mainView.camera);
+
+ // show color
+ //this.render();
+
+ // return;
+ // // create new box
+ // var self=this;
+ var center_pos = this.mouse.get_screen_location_in_world(x+w/2, y+h/2);
+ center_pos = this.data.world.scenePosToLidar(center_pos);
+
+ let initRoationZ = this.viewManager.mainView.camera.rotation.z + Math.PI/2;
+
+ let box = this.create_box_by_points(points, initRoationZ);
+
+ let id = objIdManager.generateNewUniqueId();
+ box.obj_track_id = id;
+
+
+
+ //this.scene.add(box);
+
+
+
+ if (!shift){
+ try{
+ this.boxOp.auto_shrink_box(box);
+ }
+ catch(e)
+ {
+ logger.log(e);
+ }
+ }
+
+ // guess obj type here
+
+ box.obj_type = globalObjectCategory.guess_obj_type_by_dimension(box.scale);
+
+ objIdManager.addObject({
+ category: box.obj_type,
+ id: box.obj_track_id,
+ });
+
+
+
+ this.imageContextManager.boxes_manager.add_box(box);
+ this.floatLabelManager.add_label(box);
+
+ this.selectBox(box);
+ this.on_box_changed(box);
+
+ if (!shift){
+ this.boxOp.auto_rotate_xyz(box, ()=>{
+ box.obj_type = globalObjectCategory.guess_obj_type_by_dimension(box.scale);
+ this.floatLabelManager.set_object_type(box.obj_local_id, box.obj_type);
+ this.fastToolBox.setValue(box.obj_type, box.obj_track_id, box.obj_attr);
+ this.on_box_changed(box);
+ });
+ }
+
+
+ //floatLabelManager.add_label(box);
+
+
+ };
+
+
+
+ this.create_box_by_points=function(points, rotationZ){
+
+ let localRot = this.data.world.sceneRotToLidar(new THREE.Euler(0,0,rotationZ, "XYZ"));
+
+ let transToBoxMatrix = new THREE.Matrix4().makeRotationFromEuler(localRot)
+ .setPosition(0, 0, 0)
+ .invert();
+
+ // var trans = transpose(euler_angle_to_rotate_matrix({x:0,y:0,z:rotation_z}, {x:0, y:0, z:0}), 4);
+
+ let relative_position = [];
+ let v = new THREE.Vector3();
+ points.forEach(function(p){
+ v.set(p[0],p[1],p[2]);
+ let boxP = v.applyMatrix4(transToBoxMatrix);
+ relative_position.push([boxP.x,boxP.y, boxP.z]);
+ });
+
+ var relative_extreme = vector_range(relative_position);
+ var scale = {
+ x: relative_extreme.max[0] - relative_extreme.min[0],
+ y: relative_extreme.max[1] - relative_extreme.min[1],
+ z: relative_extreme.max[2] - relative_extreme.min[2],
+ };
+
+ // enlarge scale a little
+
+ let center = this.boxOp.translateBoxInBoxCoord(
+ localRot,
+ {
+ x: (relative_extreme.max[0] + relative_extreme.min[0])/2,
+ y: (relative_extreme.max[1] + relative_extreme.min[1])/2,
+ z: (relative_extreme.max[2] + relative_extreme.min[2])/2,
+ }
+ );
+
+ return this.data.world.annotation.add_box(center, scale, localRot, "Unknown", "");
+ };
+
+
+ this.handleLeftClick= function(event) {
+
+ if (event.ctrlKey){
+ //Ctrl+left click to smart paste!
+ //smart_paste();
+ }
+ else{
+ //select box /unselect box
+ if (!this.data.world || (!this.data.world.annotation.boxes && this.data.world.radars.radarList.length==0 && !this.calib.calib_box)){
+ return;
+ }
+
+ let all_boxes = this.data.world.annotation.boxes.concat(this.data.world.radars.getAllBoxes());
+ all_boxes = all_boxes.concat(this.data.world.aux_lidars.getAllBoxes());
+
+ if (this.calib.calib_box){
+ all_boxes.push(this.calib.calib_box);
+ }
+
+ let intersects = this.mouse.getIntersects( this.mouse.onUpPosition, all_boxes);
+
+ if (intersects.length == 0){
+ if (this.data.world.radar_box){
+ intersects = this.mouse.getIntersects( this.mouse.onUpPosition, [this.data.world.radar_box]);
+ }
+ }
+
+ if ( intersects.length > 0 ) {
+ //var object = intersects[ 0 ].object;
+ var object = intersects[ 0 ].object;
+ if ( object.userData.object !== undefined ) {
+ // helper
+ this.selectBox( object.userData.object );
+ } else {
+ this.selectBox( object );
+ }
+ } else {
+ this.unselectBox(null);
+ }
+
+ //render();
+ }
+
+
+ };
+
+ this.select_locked_object= function(){
+ var self=this;
+ if (this.view_state.lock_obj_track_id != ""){
+ var box = this.data.world.annotation.boxes.find(function(x){
+ return x.obj_track_id == self.view_state.lock_obj_track_id;
+ })
+
+ if (box){
+ this.selectBox(box);
+
+ if (self.view_state.lock_obj_in_highlight){
+ this.focusOnSelectedBox(box);
+ }
+ }
+ }
+ };
+
+ // new_object
+ this.unselectBox = function(new_object, keep_lock){
+
+ if (new_object==null){
+ if (this.viewManager.mainView && this.viewManager.mainView.transform_control.visible)
+ {
+ //unselect first time
+ this.viewManager.mainView.transform_control.detach();
+ }else{
+ //unselect second time
+ if (this.selected_box){
+ // restore from highlight
+ if (this.selected_box.in_highlight){
+ this.cancelFocus(this.selected_box);
+
+ if (!keep_lock){
+ this.view_state.lock_obj_in_highlight = false;
+ }
+ } else{
+ // unselected finally
+ //this.selected_box.material.color = new THREE.Color(parseInt("0x"+get_obj_cfg_by_type(this.selected_box.obj_type).color.slice(1)));
+ //this.selected_box.material.opacity = this.data.cfg.box_opacity;
+ this.boxOp.unhighlightBox(this.selected_box);
+ //this.floatLabelManager.unselect_box(this.selected_box.obj_local_id, this.selected_box.obj_type);
+ this.fastToolBox.hide();
+
+ if (!keep_lock){
+ this.view_state.lock_obj_track_id = "";
+ }
+
+ this.imageContextManager.boxes_manager.onBoxUnselected(this.selected_box.obj_local_id, this.selected_box.obj_type);
+ this.selected_box = null;
+ this.boxEditor.detach();
+
+ this.onSelectedBoxChanged(null);
+ }
+ }
+ else{
+ // just an empty click
+ return;
+ }
+ }
+ }
+ else{
+ // selected other box
+ //unselect all
+ this.viewManager.mainView.transform_control.detach();
+
+
+ if (this.selected_box){
+
+ // restore from highlight
+
+ if (this.selected_box.in_highlight){
+ this.cancelFocus(this.selected_box);
+ if (!keep_lock){
+ this.view_state.lock_obj_in_highlight = false;
+ }
+ }
+
+ this.selected_box.material.color = new THREE.Color(parseInt("0x"+globalObjectCategory.get_obj_cfg_by_type(this.selected_box.obj_type).color.slice(1)));
+ this.selected_box.material.opacity = this.data.cfg.box_opacity;
+ //this.floatLabelManager.unselect_box(this.selected_box.obj_local_id);
+ this.fastToolBox.hide();
+ this.imageContextManager.boxes_manager.onBoxUnselected(this.selected_box.obj_local_id, this.selected_box.obj_type);
+
+ this.selected_box = null;
+ this.boxEditor.detach();
+ if (!keep_lock)
+ this.view_state.lock_obj_track_id = "";
+ }
+ }
+
+
+
+ this.render();
+
+ };
+
+
+
+ this.selectBox = function(object){
+
+ if (this.selected_box != object){
+ // unselect old bbox
+
+ var in_highlight = false;
+
+ if (this.selected_box){
+ in_highlight = this.selected_box.in_highlight;
+ this.unselectBox(this.selected_box);
+ }
+
+ // select me, the first time
+ this.selected_box = object;
+
+ // switch camera
+ if (!this.editorCfg.disableMainImageContext){
+ var best_camera = this.imageContextManager.choose_best_camera_for_point(
+ this.selected_box.world.frameInfo.sceneMeta,
+ this.selected_box.position);
+
+ if (best_camera){
+
+ //var image_changed = this.data.set_active_image(best_camera);
+
+ // if (image_changed){
+ // this.editorUi.querySelector("#camera-selector").value=best_camera;
+ // this.imageContextManager.boxes_manager.display_image();
+ // }
+
+ this.imageContextManager.setBestCamera(best_camera);
+ }
+ }
+
+ // highlight box
+ // shold change this id if the current selected box changed id.
+ this.view_state.lock_obj_track_id = object.obj_track_id;
+
+ //this.floatLabelManager.select_box(this.selected_box.obj_local_id);
+
+ this.fastToolBox.setPos(this.floatLabelManager.getLabelEditorPos(this.selected_box.obj_local_id));
+ this.fastToolBox.setValue(object.obj_type, object.obj_track_id, object.obj_attr);
+ this.fastToolBox.show();
+
+ this.boxOp.highlightBox(this.selected_box);
+
+ if (in_highlight){
+ this.focusOnSelectedBox(this.selected_box);
+ }
+
+ this.save_box_info(object); // this is needed since when a frame is loaded, all box haven't saved anything.
+ // we could move this to when a frame is loaded.
+ this.boxEditor.attachBox(object);
+ this.onSelectedBoxChanged(object);
+
+ }
+ else {
+ //reselect the same box
+ if (this.viewManager.mainView.transform_control.visible){
+ this.change_transform_control_view();
+ }
+ else{
+ //select me the second time
+ //object.add(this.viewManager.mainView.transform_control);
+ this.viewManager.mainView.transform_control.attach( object );
+ }
+ }
+
+ this.render();
+
+
+ };
+
+ this.adjustContainerSize = function()
+ {
+ let editorRect = this.editorUi.getBoundingClientRect();
+ let headerRect = this.editorUi.querySelector("#header").getBoundingClientRect();
+
+ this.container.style.height = editorRect.height - headerRect.height + "px";
+ }
+
+
+ this.onWindowResize= function() {
+
+ this.adjustContainerSize();
+ this.boxEditorManager.onWindowResize();
+
+ // use clientwidth and clientheight to resize container
+ // but use scrollwidth/height to place other things.
+ if ( this.windowWidth != this.container.clientWidth || this.windowHeight != this.container.clientHeight ) {
+
+ //update_mainview();
+ if (this.viewManager.mainView)
+ this.viewManager.mainView.onWindowResize();
+
+ if (this.boxEditor)
+ this.boxEditor.update("dontrender");
+
+ this.windowWidth = this.container.clientWidth;
+ this.windowHeight = this.container.clientHeight;
+ this.renderer.setSize( this.windowWidth, this.windowHeight );
+
+ //this.viewManager.updateViewPort();
+
+ // update sideview svg if there exists selected box
+ // the following update is called in updateSubviewRangeByWindowResize
+ // if (this.selected_box){
+ // this.projectiveViewOps.update_view_handle(this.selected_box);
+ // }
+ }
+
+ this.viewManager.render();
+ };
+
+ this.change_transform_control_view= function(){
+ if (this.viewManager.mainView.transform_control.mode=="scale"){
+ this.viewManager.mainView.transform_control.setMode( "translate" );
+ this.viewManager.mainView.transform_control.showY=true;
+ this.viewManager.mainView.transform_control.showX=true;
+ this.viewManager.mainView.transform_control.showz=true;
+ }else if (this.viewManager.mainView.transform_control.mode=="translate"){
+ this.viewManager.mainView.transform_control.setMode( "rotate" );
+ this.viewManager.mainView.transform_control.showY=false;
+ this.viewManager.mainView.transform_control.showX=false;
+ this.viewManager.mainView.transform_control.showz=true;
+ }else if (this.viewManager.mainView.transform_control.mode=="rotate"){
+ this.viewManager.mainView.transform_control.setMode( "scale" );
+ this.viewManager.mainView.transform_control.showY=true;
+ this.viewManager.mainView.transform_control.showX=true;
+ this.viewManager.mainView.transform_control.showz=true;
+ }
+ };
+
+ this.add_box_on_mouse_pos_by_ref = function(){
+
+ let globalP = this.mouse.get_mouse_location_in_world();
+ // trans pos to world local pos
+ let pos = this.data.world.scenePosToLidar(globalP);
+
+ let refbox = this.autoAdjust.marked_object.ann;
+ pos.z = refbox.psr.position.z;
+
+ let id = refbox.obj_id;
+
+ if (this.autoAdjust.marked_object.frame == this.data.world.frameInfo.frame)
+ {
+ id = "";
+ }
+
+ let box = this.add_box(pos, refbox.psr.scale, refbox.psr.rotation, refbox.obj_type, id, refbox.obj_attr);
+
+ return box;
+ };
+
+ this.add_box_on_mouse_pos= function(obj_type){
+ // todo: move to this.data.world
+ let globalP = this.mouse.get_mouse_location_in_world();
+
+ // trans pos to world local pos
+ let pos = this.data.world.scenePosToLidar(globalP);
+
+ var rotation = new THREE.Euler(0, 0, this.viewManager.mainView.camera.rotation.z+Math.PI/2, "XYZ");
+ rotation = this.data.world.sceneRotToLidar(rotation);
+
+ var obj_cfg = globalObjectCategory.get_obj_cfg_by_type(obj_type);
+ var scale = {
+ x: obj_cfg.size[0],
+ y: obj_cfg.size[1],
+ z: obj_cfg.size[2]
+ };
+
+ pos.z = -1.8 + scale.z/2; // -1.8 is height of lidar
+
+ let id = objIdManager.generateNewUniqueId();
+
+ objIdManager.addObject({
+ category: obj_type,
+ id: id,
+ });
+
+ let box = this.add_box(pos, scale, rotation, obj_type, id);
+
+ return box;
+ };
+
+ this.add_box= function(pos, scale, rotation, obj_type, obj_track_id, obj_attr){
+ let box = this.data.world.annotation.add_box(pos, scale, rotation, obj_type, obj_track_id, obj_attr);
+
+ this.floatLabelManager.add_label(box);
+
+ this.imageContextManager.boxes_manager.add_box(box);
+
+ this.selectBox(box);
+ return box;
+ };
+
+ this.save_box_info= function(box){
+ box.last_info = {
+ //obj_type: box.obj_type,
+ position: {
+ x: box.position.x,
+ y: box.position.y,
+ z: box.position.z,
+ },
+ rotation: {
+ x: box.rotation.x,
+ y: box.rotation.y,
+ z: box.rotation.z,
+ },
+ scale: {
+ x: box.scale.x,
+ y: box.scale.y,
+ z: box.scale.z,
+ }
+ }
+ };
+
+
+ // axix, xyz, action: scale, move, direction, up/down
+ this.transform_bbox= function(command){
+ if (!this.selected_box)
+ return;
+
+ switch (command){
+ case 'x_move_up':
+ this.boxOp.translate_box(this.selected_box, 'x', 0.05);
+ break;
+ case 'x_move_down':
+ this.boxOp.translate_box(this.selected_box, 'x', -0.05);
+ break;
+ case 'x_scale_up':
+ this.selected_box.scale.x *= 1.01;
+ break;
+ case 'x_scale_down':
+ this.selected_box.scale.x /= 1.01;
+ break;
+
+ case 'y_move_up':
+ this.boxOp.translate_box(this.selected_box, 'y', 0.05);
+ break;
+ case 'y_move_down':
+ this.boxOp.translate_box(this.selected_box, 'y', -0.05);
+ break;
+ case 'y_scale_up':
+ this.selected_box.scale.y *= 1.01;
+ break;
+ case 'y_scale_down':
+ this.selected_box.scale.y /= 1.01;
+ break;
+
+ case 'z_move_up':
+ this.selected_box.position.z += 0.05;
+ break;
+ case 'z_move_down':
+ this.selected_box.position.z -= 0.05;
+ break;
+ case 'z_scale_up':
+ this.selected_box.scale.z *= 1.01;
+ break;
+ case 'z_scale_down':
+ this.selected_box.scale.z /= 1.01;
+ break;
+
+ case 'z_rotate_left':
+ this.selected_box.rotation.z += 0.01;
+ break;
+ case 'z_rotate_right':
+ this.selected_box.rotation.z -= 0.01;
+ break;
+
+ case 'z_rotate_reverse':
+ if (this.selected_box.rotation.z > 0){
+ this.selected_box.rotation.z -= Math.PI;
+ }else{
+ this.selected_box.rotation.z += Math.PI;
+ }
+ break;
+ case 'reset':
+ this.selected_box.rotation.x = 0;
+ this.selected_box.rotation.y = 0;
+ this.selected_box.rotation.z = 0;
+ this.selected_box.position.z = 0;
+ break;
+
+ }
+
+ this.on_box_changed(this.selected_box);
+
+ };
+
+
+ // function switch_bbox_type(target_type){
+ // if (!this.selected_box)
+ // return;
+
+ // if (!target_type){
+ // target_type = get_next_obj_type_name(this.selected_box.obj_type);
+ // }
+
+ // this.selected_box.obj_type = target_type;
+ // var obj_cfg = get_obj_cfg_by_type(target_type);
+ // this.selected_box.scale.x=obj_cfg.size[0];
+ // this.selected_box.scale.y=obj_cfg.size[1];
+ // this.selected_box.scale.z=obj_cfg.size[2];
+
+
+ // this.floatLabelManager.set_object_type(this.selected_box.obj_local_id, this.selected_box.obj_type);
+ // this.floatLabelManager.update_label_editor(this.selected_box.obj_type, this.selected_box.obj_track_id);
+
+
+
+ // }
+
+
+
+ this.keydown= function( ev ) {
+
+ // if (this.keydownDisabled)
+ // return;
+
+ this.operation_state.key_pressed = true;
+
+ switch ( ev.key) {
+ case '+':
+ case '=':
+ this.data.scale_point_size(1.2);
+ this.render();
+ break;
+ case '-':
+ this.data.scale_point_size(0.8);
+ this.render();
+ break;
+ case '1':
+ this.select_previous_object();
+ break;
+ case '2':
+ this.select_next_object();
+ break;
+ case '3':
+ case 'PageUp':
+ this.previous_frame();
+ break;
+ case 'PageDown':
+ case '4':
+ this.next_frame();
+ break;
+ case 'p':
+ this.downloadWebglScreenShot();
+ break;
+
+ // case 'v':
+ // this.change_transform_control_view();
+ // break;
+ /*
+ case 'm':
+ case 'M':
+ smart_paste();
+ break;
+ case 'N':
+ case 'n':
+ //add_bbox();
+ //header.mark_changed_flag();
+ break;
+ case 'B':
+ case 'b':
+ switch_bbox_type();
+ self.header.mark_changed_flag();
+ self.on_box_changed(this.selected_box);
+ break;
+ */
+ case 'z': // X
+ this.viewManager.mainView.transform_control.showX = ! this.viewManager.mainView.transform_control.showX;
+ break;
+ case 'x': // Y
+ this.viewManager.mainView.transform_control.showY = ! this.viewManager.mainView.transform_control.showY;
+ break;
+ case 'c': // Z
+ if (ev.ctrlKey){
+ this.mark_bbox(this.selected_box);
+ } else {
+ this.viewManager.mainView.transform_control.showZ = ! this.viewManager.mainView.transform_control.showZ;
+ }
+ break;
+ case ' ': // Spacebar
+ //this.viewManager.mainView.transform_control.enabled = ! this.viewManager.mainView.transform_control.enabled;
+ this.playControl.pause_resume_play();
+ break;
+
+ case '5':
+ case '6':
+ case '7':
+ this.boxEditor.boxView.views[ev.key-5].cameraHelper.visible = !this.boxEditor.boxView.views[ev.key-5].cameraHelper.visible;
+ this.render();
+ break;
+
+
+ case 's':
+ if (ev.ctrlKey){
+ saveWorldList(this.data.worldList);
+ }
+ else if (this.selected_box)
+ {
+ let v = Math.max(this.editorCfg.moveStep * this.selected_box.scale.x, 0.02);
+ this.boxOp.translate_box(this.selected_box, 'x', -v);
+ this.on_box_changed(this.selected_box);
+ }
+ break;
+ case 'w':
+ if (this.selected_box){
+ let v = Math.max(this.editorCfg.moveStep * this.selected_box.scale.x, 0.02);
+ this.boxOp.translate_box(this.selected_box, 'x', v);
+ this.on_box_changed(this.selected_box);
+ }
+ break;
+ case 'a':
+ if (this.selected_box){
+ let v = Math.max(this.editorCfg.moveStep * this.selected_box.scale.y, 0.02);
+ this.boxOp.translate_box(this.selected_box, 'y', v);
+ this.on_box_changed(this.selected_box);
+ }
+ break;
+ case 'd':
+ if (this.selected_box){
+ let v = Math.max(this.editorCfg.moveStep * this.selected_box.scale.y, 0.02);
+ this.boxOp.translate_box(this.selected_box, 'y', -v);
+ this.on_box_changed(this.selected_box);
+ }
+ break;
+
+ case 'q':
+ if (this.selected_box){
+ this.boxOp.rotate_z(this.selected_box, this.editorCfg.rotateStep, false);
+ this.on_box_changed(this.selected_box);
+ }
+ break;
+ case 'e':
+ if (this.selected_box){
+ this.boxOp.rotate_z(this.selected_box, -this.editorCfg.rotateStep, false);
+ this.on_box_changed(this.selected_box);
+ }
+ break;
+ case 'r':
+ if (this.selected_box){
+ //this.transform_bbox("z_rotate_left");
+ this.boxOp.rotate_z(this.selected_box, this.editorCfg.rotateStep, true);
+ this.on_box_changed(this.selected_box);
+ }
+ break;
+
+ case 'f':
+ if (this.selected_box){
+ //this.transform_bbox("z_rotate_right");
+ this.boxOp.rotate_z(this.selected_box, -this.editorCfg.rotateStep, true);
+ this.on_box_changed(this.selected_box);
+ }
+ break;
+ case 'g':
+ this.transform_bbox("z_rotate_reverse");
+ break;
+ case 't':
+ //this.transform_bbox("reset");
+ this.showTrajectory();
+ break;
+ case 'v':
+ this.enterBatchEditMode();
+ break;
+ case 'd':
+ case 'D':
+ if (ev.ctrlKey){
+ this.remove_selected_box();
+ this.header.updateModifiedStatus();
+ }
+ break;
+ case 'Delete':
+ this.remove_selected_box();
+ this.header.updateModifiedStatus();
+ break;
+ case 'Escape':
+ if (this.selected_box){
+ this.unselectBox(null);
+ }
+ break;
+ }
+ };
+
+
+
+ this.previous_frame= function(){
+
+
+
+ if (!this.data.meta)
+ return;
+
+ var scene_meta = this.data.get_current_world_scene_meta();
+
+ var frame_index = this.data.world.frameInfo.frame_index-1;
+
+ if (frame_index < 0){
+ console.log("first frame");
+ this.infoBox.show("Notice", "This is the first frame");
+ return;
+ }
+
+ this.load_world(scene_meta.scene, scene_meta.frames[frame_index]);
+
+
+
+ };
+
+ this.last_frame = function()
+ {
+ let scene_meta = this.data.get_current_world_scene_meta();
+ this.load_world(scene_meta.scene, scene_meta.frames[scene_meta.frames.length-1]);
+ };
+ this.first_frame = function()
+ {
+ let scene_meta = this.data.get_current_world_scene_meta();
+ this.load_world(scene_meta.scene, scene_meta.frames[0]);
+ };
+
+ this.next_frame= function(){
+
+
+
+ if (!this.data.meta)
+ return;
+
+ var scene_meta = this.data.get_current_world_scene_meta();
+
+ var num_frames = scene_meta.frames.length;
+
+ var frame_index = (this.data.world.frameInfo.frame_index +1);
+
+ if (frame_index >= num_frames){
+ console.log("last frame");
+ this.infoBox.show("Notice", "This is the last frame");
+ return;
+ }
+
+ this.load_world(scene_meta.scene, scene_meta.frames[frame_index]);
+ };
+
+ this.select_next_object= function(){
+
+ var self=this;
+ if (this.data.world.annotation.boxes.length<=0)
+ return;
+
+ if (this.selected_box){
+ this.operation_state.box_navigate_index = this.data.world.annotation.boxes.findIndex(function(x){
+ return self.selected_box == x;
+ });
+ }
+
+ this.operation_state.box_navigate_index += 1;
+ this.operation_state.box_navigate_index %= this.data.world.annotation.boxes.length;
+
+ this.selectBox(this.data.world.annotation.boxes[this.operation_state.box_navigate_index]);
+
+ };
+
+ this.select_previous_object= function(){
+ var self=this;
+ if (this.data.world.annotation.boxes.length<=0)
+ return;
+
+ if (this.selected_box){
+ this.operation_state.box_navigate_index = this.data.world.annotation.boxes.findIndex(function(x){
+ return self.selected_box == x;
+ });
+ }
+
+ this.operation_state.box_navigate_index += this.data.world.annotation.boxes.length-1;
+ this.operation_state.box_navigate_index %= this.data.world.annotation.boxes.length;
+
+ this.selectBox(this.data.world.annotation.boxes[this.operation_state.box_navigate_index]);
+ };
+
+ // this.centerMainView =function(){
+ // let offset = this.data.world.coordinatesOffset;
+ // this.viewManager.mainView.orbit.target.x += offset[0];
+ // this.viewManager.mainView.orbit.target.y += offset[1];
+ // this.viewManager.mainView.orbit.target.z += offset[2];
+ // };
+
+ this.on_load_world_finished= function(world){
+
+ document.title = "SUSTech POINTS-" + world.frameInfo.scene;
+ // switch view positoin
+ this.moveAxisHelper(world);
+ this.moveRangeCircle(world);
+ this.lookAtWorld(world);
+ this.unselectBox(null, true);
+ this.unselectBox(null, true);
+ this.render();
+ this.imageContextManager.attachWorld(world);
+ this.imageContextManager.render_2d_image();
+ this.render2dLabels(world);
+ this.update_frame_info(world.frameInfo.scene, world.frameInfo.frame);
+
+ this.select_locked_object();
+
+ //load_obj_ids_of_scene(world.frameInfo.scene);
+ objIdManager.setCurrentScene(world.frameInfo.scene);
+
+ // preload after the first world loaded
+ // otherwise the loading of the first world would be too slow
+ this.data.preloadScene(world.frameInfo.scene, world);
+ };
+ this.moveAxisHelper = function(world) {
+ world.webglGroup.add(this.axis);
+ };
+
+ this.mainViewOffset = [0,0,0];
+
+ this.lookAtWorld = function(world){
+ let newOffset = [
+ world.coordinatesOffset[0] - this.mainViewOffset[0],
+ world.coordinatesOffset[1] - this.mainViewOffset[1],
+ world.coordinatesOffset[2] - this.mainViewOffset[2],
+ ];
+
+ this.mainViewOffset = world.coordinatesOffset;
+
+ this.viewManager.mainView.orbit.target.x += newOffset[0];
+ this.viewManager.mainView.orbit.target.y += newOffset[1];
+ this.viewManager.mainView.orbit.target.z += newOffset[2];
+
+ this.viewManager.mainView.camera.position.x += newOffset[0];
+ this.viewManager.mainView.camera.position.y += newOffset[1];
+ this.viewManager.mainView.camera.position.z += newOffset[2];
+
+ this.viewManager.mainView.orbit.update();
+
+ };
+
+ this.load_world = async function(sceneName, frame, onFinished){
+
+ this.data.dbg.dump();
+
+ logger.log(`load ${sceneName}, ${frame}`);
+
+ var self=this;
+ //stop if current world is not ready!
+ if (this.data.world && !this.data.world.preloaded()){
+ console.error("current world is still loading.");
+ return;
+ }
+
+ if (this.selected_box && this.selected_box.in_highlight){
+ this.cancelFocus(this.selected_box);
+ }
+
+ if (this.viewManager.mainView && this.viewManager.mainView.transform_control.visible)
+ {
+ //unselect first time
+ this.viewManager.mainView.transform_control.detach();
+ }
+
+ var world = await this.data.getWorld(sceneName, frame);
+
+ if (world)
+ {
+ this.data.activate_world(
+ world,
+ function(){
+ self.on_load_world_finished(world);
+ if (onFinished)
+ onFinished();
+
+ }
+ );
+ }
+
+
+ };
+
+
+ this.remove_box = function(box, render=true){
+ if (box === this.selected_box){
+ this.unselectBox(null,true);
+ this.unselectBox(null,true); //twice to safely unselect.
+ this.selected_box = null;
+ //this.remove_selected_box();
+ }
+
+
+
+ this.do_remove_box(box, false); // render later.
+
+ // this should be after do-remove-box
+ // subview renderings don't need to be done again after
+ // the box is removed.
+ if (box.boxEditor)
+ {
+ if (box.boxEditor){
+ box.boxEditor.detach("donthide");
+ }
+ else{
+ console.error("what?");
+ }
+ }
+
+
+ this.header.updateModifiedStatus();
+
+ if (render)
+ this.render();
+
+ };
+
+ this.remove_selected_box= function(){
+ this.remove_box(this.selected_box);
+ };
+
+ this.do_remove_box = function(box, render=true){
+
+ if (!box.annotator)
+ this.restore_box_points_color(box, render);
+
+ this.imageContextManager.boxes_manager.remove_box(box.obj_local_id);
+
+ this.floatLabelManager.remove_box(box);
+ this.fastToolBox.hide();
+
+ //this.selected_box.dispose();
+
+ box.world.annotation.unload_box(box);
+ box.world.annotation.remove_box(box);
+
+ box.world.annotation.setModified();
+ },
+
+ this.clear= function(){
+
+ this.header.clear_box_info();
+ //this.editorUi.querySelector("#image").innerHTML = '';
+
+ this.unselectBox(null);
+ this.unselectBox(null);
+
+ this.header.clear_frame_info();
+
+ this.imageContextManager.clear_main_canvas();
+ this.boxEditor.detach();
+
+
+ this.data.world.unload();
+ this.data.world= null; //dump it
+ this.floatLabelManager.remove_all_labels();
+ this.fastToolBox.hide();
+ this.render();
+ };
+
+ this.update_frame_info= function(scene, frame){
+ var self = this;
+ this.header.set_frame_info(scene, frame, function(sceneName){
+ self.scene_changed(sceneName)});
+ };
+
+ //box edited
+ this.on_box_changed = function(box){
+
+ if (!this.imageContextManager.hidden())
+ this.imageContextManager.boxes_manager.update_box(box);
+
+ this.header.update_box_info(box);
+ //floatLabelManager.update_position(box, false); don't update position, or the ui is annoying.
+
+ box.world.annotation.setModified();
+
+
+
+ this.updateBoxPointsColor(box);
+ this.save_box_info(box);
+
+
+
+ if (box.boxEditor){
+ box.boxEditor.onBoxChanged();
+ }
+ else{
+ console.error("what?");
+ }
+
+ this.autoAdjust.syncFollowers(box);
+
+ // if (box === this.data.world.radar_box){
+ // this.data.world.move_radar(box);
+ // }
+
+ if (box.on_box_changed){
+ box.on_box_changed();
+ }
+
+ this.header.updateModifiedStatus();
+ this.render();
+ };
+
+ // box removed, restore points color.
+ this.restore_box_points_color= function(box,render=true){
+ if (this.data.cfg.color_obj != "no"){
+ box.world.lidar.reset_box_points_color(box);
+ box.world.lidar.update_points_color();
+ if (render)
+ this.render();
+ }
+
+ };
+
+ this.updateBoxPointsColor= function(box){
+ if (this.data.cfg.color_obj != "no"){
+ if (box.last_info){
+ box.world.lidar.set_box_points_color(box.last_info, {x: this.data.cfg.point_brightness, y: this.data.cfg.point_brightness, z: this.data.cfg.point_brightness});
+ }
+
+ box.world.lidar.set_box_points_color(box);
+ box.world.lidar.update_points_color();
+ }
+ };
+
+ this.onSelectedBoxChanged= function(box){
+
+ if (box){
+ this.header.update_box_info(box);
+ // this.floatLabelManager.update_position(box, true);
+ // this.fastToolBox.setPos(this.floatLabelManager.getLabelEditorPos(box.obj_local_id));
+ this.imageContextManager.boxes_manager.onBoxSelected(box.obj_local_id, box.obj_type);
+
+
+ //this.boxEditor.attachBox(box);
+
+ this.render();
+ //this.boxEditor.boxView.render();
+
+ //this.updateSubviewRangeByWindowResize(box);
+
+ } else {
+ this.header.clear_box_info();
+ }
+
+ };
+
+ this.render2dLabels= function(world){
+ if (this.editorCfg.disableMainView)
+ return;
+
+ this.floatLabelManager.remove_all_labels();
+ var self=this;
+ world.annotation.boxes.forEach(function(b){
+ self.floatLabelManager.add_label(b);
+ })
+
+ if (this.selected_box){
+ //this.floatLabelManager.select_box(this.selected_box.obj_local_id)
+ this.fastToolBox.show();
+ this.fastToolBox.setValue(this.selected_box.obj_type, this.selected_box.obj_track_id, this.selected_box.obj_attr);
+ }
+ };
+
+ this.add_global_obj_type= function(){
+
+ var self = this;
+ var sheet = window.document.styleSheets[1];
+
+ let obj_type_map = globalObjectCategory.obj_type_map;
+
+ for (var o in obj_type_map){
+ var rule = '.'+o+ '{color:'+obj_type_map[o].color+';'+
+ 'stroke:' +obj_type_map[o].color+ ';'+
+ 'fill:' +obj_type_map[o].color+ '22' + ';'+
+ '}';
+ sheet.insertRule(rule, sheet.cssRules.length);
+ }
+
+ function color_str(v){
+ let c = Math.round(v*255);
+ if (c < 16)
+ return "0" + c.toString(16);
+ else
+ return c.toString(16);
+ }
+
+ for (let idx=0; idx<=32; idx++){
+ let c = globalObjectCategory.get_color_by_id(idx);
+ let color = "#" + color_str(c.x) + color_str(c.y) + color_str(c.z);
+
+ var rule = '.color-'+idx+ '{color:'+color+';'+
+ 'stroke:' +color+ ';'+
+ 'fill:' +color+ '22' + ';'+
+ '}';
+ sheet.insertRule(rule, sheet.cssRules.length);
+ }
+
+ // obj type selector
+ var options = "";
+ for (var o in obj_type_map){
+ options += ''+o+ ' ';
+ }
+
+ this.editorUi.querySelector("#floating-things #object-category-selector").innerHTML = options;
+ //this.editorUi.querySelector("#batch-editor-tools-wrapper #object-category-selector").innerHTML = options;
+
+ // submenu of new
+ var items = "";
+ for (var o in obj_type_map){
+ items += '';
+ }
+
+ this.editorUi.querySelector("#new-submenu").innerHTML = items;
+
+ this.contextMenu.installMenu("newSubMenu", this.editorUi.querySelector("#new-submenu"), (event)=>{
+ let obj_type = event.currentTarget.getAttribute("uservalue");
+ let box = self.add_box_on_mouse_pos(obj_type);
+ //switch_bbox_type(event.currentTarget.getAttribute("uservalue"));
+ //self.boxOp.grow_box(box, 0.2, {x:2, y:2, z:3});
+ //self.auto_shrink_box(box);
+ //self.on_box_changed(box);
+
+ let noscaling = event.shiftKey;
+
+ self.boxOp.auto_rotate_xyz(box, null, null, function(b){
+ self.on_box_changed(b);
+ }, noscaling);
+ return true;
+ });
+
+ // // install click actions
+ // for (var o in obj_type_map){
+ // this.editorUi.querySelector("#cm-new-"+o).onclick = (event)=>{
+
+ // // hide context men
+ // // let context menu object handle this.
+ // // this.editorUi.querySelector("#context-menu-wrapper").style.display="none";
+
+ // // process event
+ // var obj_type = event.currentTarget.getAttribute("uservalue");
+ // let box = self.add_box_on_mouse_pos(obj_type);
+ // //switch_bbox_type(event.currentTarget.getAttribute("uservalue"));
+ // //self.boxOp.grow_box(box, 0.2, {x:2, y:2, z:3});
+ // //self.auto_shrink_box(box);
+ // //self.on_box_changed(box);
+
+ // self.boxOp.auto_rotate_xyz(box, null, null, function(b){
+ // self.on_box_changed(b);
+ // });
+
+ // }
+ // }
+
+ };
+
+ this.interpolate_selected_object= function(){
+
+ let scene = this.data.world.frameInfo.scene;
+ let frame = this.data.world.frameInfo.frame;
+ let obj_id = this.selected_box.obj_track_id;
+
+ this.boxOp.interpolate_selected_object(scene, obj_id, frame, (s,fs)=>{
+ this.onAnnotationUpdatedByOthers(s, fs);
+ });
+
+
+ };
+
+ this.onAnnotationUpdatedByOthers = function(scene, frames){
+ this.data.onAnnotationUpdatedByOthers(scene, frames);
+ }
+
+ this.init(editorUi);
+
+};
+
+export{Editor}
\ No newline at end of file
diff --git a/public/js/ego_pose.js b/public/js/ego_pose.js
new file mode 100644
index 0000000..3eebf5c
--- /dev/null
+++ b/public/js/ego_pose.js
@@ -0,0 +1,78 @@
+
+
+
+
+class EgoPose
+{
+ constructor(sceneMeta, world, frameInfo)
+ {
+ this.world = world;
+ this.data = this.world.data;
+ this.sceneMeta = sceneMeta;
+ }
+
+
+ preload(on_preload_finished)
+ {
+ this.on_preload_finished = on_preload_finished;
+ this.load_ego_pose();
+ };
+
+
+ load_ego_pose(){
+
+ var xhr = new XMLHttpRequest();
+ // we defined the xhr
+ var _self = this;
+ xhr.onreadystatechange = function () {
+ if (this.readyState != 4) return;
+
+ if (this.status == 200) {
+ let egoPose = JSON.parse(this.responseText);
+ _self.egoPose = egoPose;
+ }
+
+ console.log(_self.world.frameInfo.frame, "egopose", "loaded");
+ _self.preloaded = true;
+
+ if (_self.on_preload_finished){
+ _self.on_preload_finished();
+ }
+ if (_self.go_cmd_received){
+ _self.go(this.webglScene, this.on_go_finished);
+ }
+
+ // end of state change: it can be after some time (async)
+ };
+
+ xhr.open('GET', "/load_ego_pose"+"?scene="+this.world.frameInfo.scene+"&frame="+this.world.frameInfo.frame, true);
+ xhr.send();
+ };
+
+
+ go_cmd_received = false;
+ on_go_finished = null;
+
+ go(webglScene, on_go_finished)
+ {
+ if (this.preloaded){
+ if (on_go_finished)
+ on_go_finished();
+ } else {
+ this.go_cmd_received = true;
+ this.on_go_finished = on_go_finished;
+ }
+ };
+
+
+
+
+ unload()
+ {
+
+ };
+
+}
+
+
+export{EgoPose}
\ No newline at end of file
diff --git a/public/js/error_check.js b/public/js/error_check.js
new file mode 100644
index 0000000..b64aea4
--- /dev/null
+++ b/public/js/error_check.js
@@ -0,0 +1,30 @@
+import { logger } from "./log.js";
+
+
+function checkScene(scene)
+{
+ const req = new Request(`/checkscene?scene=${scene}`);
+ let init = {
+ method: 'GET',
+ //body: JSON.stringify({"points": data})
+ };
+ // we defined the xhr
+
+ return fetch(req, init)
+ .then(response=>{
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }else{
+ return response.json();
+ }
+ })
+ .then(ret=>
+ {
+ logger.setErrorsContent(ret);
+ })
+ .catch(reject=>{
+ console.log("error check scene!");
+ });
+}
+
+export {checkScene}
\ No newline at end of file
diff --git a/public/js/floatlabel.js b/public/js/floatlabel.js
new file mode 100644
index 0000000..edf1079
--- /dev/null
+++ b/public/js/floatlabel.js
@@ -0,0 +1,592 @@
+
+
+import {psr_to_xyz} from "./util.js"
+import * as THREE from './lib/three.module.js';
+import { globalObjectCategory } from "./obj_cfg.js";
+class FastToolBox{
+ constructor(ui, eventHandler)
+ {
+ let self = this;
+ this.ui = ui;
+ this.eventHandler = eventHandler;
+
+ this.installEventHandler();
+
+ this.ui.querySelector("#attr-editor").onmouseenter=function(event){
+ if (this.timerId)
+ {
+ clearTimeout(this.timerId);
+ this.timerId = null;
+ }
+
+ event.target.querySelector("#attr-selector").style.display="";
+
+ };
+
+
+ this.ui.querySelector("#attr-editor").onmouseleave=function(event){
+ let ui = event.target.querySelector("#attr-selector");
+
+ this.timerId = setTimeout(()=>{
+ ui.style.display="none";
+ this.timerId = null;
+ },
+ 200);
+
+ };
+
+ this.ui.querySelector("#label-more").onmouseenter=function(event){
+ if (this.timerId)
+ {
+ clearTimeout(this.timerId);
+ this.timerId = null;
+ }
+ let ui = event.target.querySelector("#object-dropdown-menu");
+ ui.style.display="inherit";
+ ui.style.top = "100%";
+ ui.style.left = "0%";
+ ui.style.right = null;
+ ui.style.bottom = null;
+
+ let rect = ui.getClientRects()[0];
+ if (window.innerHeight < rect.y+rect.height)
+ {
+ ui.style.top = null;
+ ui.style.bottom = "100%";
+ }
+
+ if (window.innerWidth < rect.x+rect.width)
+ {
+ ui.style.left = null;
+ ui.style.right = "0%";
+ }
+
+ };
+
+ this.ui.querySelector("#label-more").onmouseleave=function(event){
+ let ui = event.target.querySelector("#object-dropdown-menu");
+ this.timerId = setTimeout(()=>{
+ ui.style.display="none";
+ this.timerId = null;
+ },
+ 200);
+ };
+
+
+ let dropdownMenu = this.ui.querySelector("#object-dropdown-menu");
+ for (let i = 0; i < dropdownMenu.children.length; i++)
+ {
+ dropdownMenu.children[i].onclick = (event) =>
+ {
+ //event.preventDefault();
+ event.stopPropagation();
+
+ this.eventHandler(event);
+
+ this.ui.querySelector("#object-dropdown-menu").style.display="none";
+ }
+ }
+ }
+
+ hide()
+ {
+ this.ui.style.display = "none";
+ }
+
+ show()
+ {
+ this.ui.style.display = "inline-block";
+ this.ui.querySelector("#attr-selector").style.display="none";
+ }
+
+
+ setValue(obj_type, obj_track_id, obj_attr){
+ this.ui.querySelector("#object-category-selector").value = obj_type;
+
+ this.setAttrOptions(obj_type, obj_attr);
+
+ this.ui.querySelector("#object-track-id-editor").value = obj_track_id;
+
+ if (obj_attr)
+ this.ui.querySelector("#attr-input").value = obj_attr;
+ else
+ this.ui.querySelector("#attr-input").value = "";
+
+ }
+
+ setPos(pos)
+ {
+ if (pos)
+ {
+ this.ui.style.top = pos.top;
+ this.ui.style.left = pos.left;
+ }
+ }
+
+ setAttrOptions(obj_type, obj_attr)
+ {
+
+ let attrs = ["static"];
+
+
+ if (globalObjectCategory.obj_type_map[obj_type] && globalObjectCategory.obj_type_map[obj_type].attr)
+ attrs = attrs.concat(globalObjectCategory.obj_type_map[obj_type].attr);
+
+ // merge attrs
+ let objAttrs = [];
+
+ if (obj_attr){
+ objAttrs = obj_attr.split(",").map(a=>a.trim());
+ objAttrs.forEach(a=>{
+ if (!attrs.find(x=>x==a))
+ {
+ attrs.push(a);
+ }
+ })
+ }
+
+
+ let items = ``;
+
+
+ attrs.forEach(a=>{
+ if (objAttrs.find(x=>x==a)){
+ items+= `${a}
`
+ }
+ else {
+ items+= `${a}
`
+ }
+ });
+
+
+ this.ui.querySelector("#attr-selector").innerHTML = items;
+
+ this.ui.querySelector("#attr-selector").onclick = (event)=>{
+
+ let attrs = this.ui.querySelector("#attr-input").value;
+
+ let objCurrentAttrs = [];
+ if (attrs)
+ objCurrentAttrs = attrs.split(",").map(a=>a.trim());
+
+
+ let clickedAttr = event.target.innerText;
+
+ if (objCurrentAttrs.find(x=>x==clickedAttr))
+ {
+ objCurrentAttrs = objCurrentAttrs.filter(x => x!= clickedAttr);
+ event.target.className = 'attr-item';
+ }
+ else
+ {
+ objCurrentAttrs.push(clickedAttr);
+ event.target.className = 'attr-item attr-selected';
+ }
+
+ attrs = "";
+ if (objCurrentAttrs.length > 0)
+ {
+ attrs = objCurrentAttrs.reduce((a,b)=>a+ (a?",":"") + b);
+ }
+
+ this.ui.querySelector("#attr-input").value = attrs;
+
+ this.eventHandler({
+ currentTarget:{
+ id: "attr-input",
+ value: attrs
+ }
+ });
+
+ }
+
+ }
+
+ installEventHandler(){
+
+ let btns = [
+ "#label-del",
+ "#label-gen-id",
+ "#label-copy",
+ "#label-paste",
+ "#label-batchedit",
+ "#label-trajectory",
+ "#label-edit",
+ "#label-highlight",
+ "#label-rotate",
+ ];
+
+ btns.forEach(btn=>{
+ this.ui.querySelector(btn).onclick = (event)=>{
+ this.eventHandler(event);
+ };
+ });
+
+ this.ui.querySelector("#object-category-selector").onchange = event=>{
+
+ //this.ui.querySelector("#attr-input").value="";
+ this.setAttrOptions(event.currentTarget.value, this.ui.querySelector("#attr-input").value);
+ this.eventHandler(event);
+ };
+
+
+ this.ui.querySelector("#object-track-id-editor").onchange = event=>this.eventHandler(event);
+ this.ui.querySelector("#object-track-id-editor").addEventListener("keydown", e=>e.stopPropagation());
+ this.ui.querySelector("#object-track-id-editor").addEventListener("keyup", event=>{
+ event.stopPropagation();
+ this.eventHandler(event);
+ });
+
+ this.ui.querySelector("#attr-input").onchange = event=>this.eventHandler(event);
+ this.ui.querySelector("#attr-input").addEventListener("keydown", e=>e.stopPropagation());
+ this.ui.querySelector("#attr-input").addEventListener("keyup", event=>{
+ event.stopPropagation();
+ this.eventHandler(event);
+ });
+ }
+}
+
+
+
+class FloatLabelManager {
+
+ id_enabled = true;
+ category_enabled = true;
+ color_scheme = "category";
+
+ constructor(editor_ui, container_div, view, func_on_label_clicked)
+ {
+ this.view = view; //access camera by view, since camera is dynamic
+ this.editor_ui = editor_ui;
+ this.container = container_div;
+ this.labelsUi = editor_ui.querySelector("#floating-labels");
+ this.floatingUi = editor_ui.querySelector("#floating-things");
+
+
+
+ this.style = document.createElement('style');
+ this.temp_style = document.createElement('style');
+ this.on_label_clicked = func_on_label_clicked;
+
+ document.head.appendChild(this.style);
+ document.head.appendChild(this.temp_style);
+
+ this.id_enabled = !pointsGlobalConfig.hideId;
+ this.category_enabled = !pointsGlobalConfig.hideCategory;
+ }
+
+ hide(){
+ this.floatingUi.style.display="none";
+ }
+
+ show(){
+ this.floatingUi.style.display="";
+ }
+
+
+
+
+ show_id(show){
+
+ this.id_enabled = show;
+
+ if (!show){
+ this.temp_style.sheet.insertRule(".label-obj-id-text {display: none}");
+ }
+ else{
+ for (let i = this.temp_style.sheet.cssRules.length-1; i >= 0; i--){
+ var r = this.temp_style.sheet.cssRules[i];
+ if (r.selectorText === ".label-obj-id-text"){
+ this.temp_style.sheet.deleteRule(i);
+ }
+ }
+
+ }
+
+ }
+
+ show_category(show){
+
+ this.category_enabled = show;
+
+ if (!show){
+ this.temp_style.sheet.insertRule(".label-obj-type-text {display: none}");
+ this.temp_style.sheet.insertRule(".label-obj-attr-text {display: none}");
+ }
+ else{
+ for (let i = this.temp_style.sheet.cssRules.length-1; i >= 0; i--){
+ var r = this.temp_style.sheet.cssRules[i];
+ if (r.selectorText === ".label-obj-type-text" || r.selectorText === ".label-obj-attr-text"){
+ this.temp_style.sheet.deleteRule(i);
+ }
+ }
+ }
+
+ }
+
+ // hide all temporarily when zoom in one object.
+ hide_all(){
+ // if (this.temp_style.sheet.cssRules.length == 0){
+ // this.temp_style.sheet.insertRule(".label-obj-id-text {display: none}");
+ // this.temp_style.sheet.insertRule(".label-obj-type-text {display: none}");
+ // this.temp_style.sheet.insertRule(".label-obj-attr-text {display: none}");
+ // }
+ this.labelsUi.style.display = "none";
+ }
+
+ restore_all(){
+ // this.show_category(this.category_enabled);
+ // this.show_id(this.id_enabled);
+ this.labelsUi.style.display = "";
+ }
+
+ remove_all_labels(){
+
+ var _self = this;
+
+ if (this.labelsUi.children.length>0){
+ for (var c=this.labelsUi.children.length-1; c >= 0; c--){
+ this.labelsUi.children[c].remove();
+ }
+ }
+ }
+
+
+ update_all_position(){
+ if (this.labelsUi.children.length>0){
+ for (var c=0; c < this.labelsUi.children.length; c++){
+ var element = this.labelsUi.children[c];
+
+ var best_pos = this.compute_best_position(element.vertices);
+ var pos = this.coord_to_pixel(best_pos);
+
+ element.style.top = Math.round(pos.y) + 'px';
+ element.style.left = Math.round(pos.x) + 'px';
+
+
+ element.className = element.orgClassName;
+ if (pos.out_view){
+ element.className += " label-out-view";
+ }
+
+ }
+ }
+ }
+
+ getLabelEditorPos(local_id)
+ {
+ let label = this.editor_ui.querySelector("#obj-local-"+local_id);
+ if (label)
+ {
+ // if label is hidden, we can't use its pos directly.
+ let best_pos = this.compute_best_position(label.vertices);
+ let pos = this.coord_to_pixel(best_pos);
+
+
+ return {
+ top: Math.round(pos.y) + label.offsetHeight + "px",
+ left: Math.round(pos.x) + 30 + "px",
+ };
+ }
+ }
+
+
+ set_object_type(local_id, obj_type){
+ var label = this.editor_ui.querySelector("#obj-local-"+local_id);
+ if (label){
+ label.obj_type = obj_type;
+ label.update_text();
+ this.update_color(label);
+ }
+ }
+
+ set_object_attr(local_id, obj_attr){
+ var label = this.editor_ui.querySelector("#obj-local-"+local_id);
+ if (label){
+ label.obj_attr = obj_attr;
+ label.update_text();
+ this.update_color(label);
+ }
+ }
+
+
+ set_object_track_id(local_id, track_id){
+ var label = this.editor_ui.querySelector("#obj-local-"+local_id);
+
+ if (label){
+ label.obj_track_id = track_id;
+ label.update_text();
+ this.update_color(label);
+ }
+ }
+
+ translate_vertices_to_global(world, vertices) {
+ let ret = [];
+ for (let i = 0; i< vertices.length; i+=4)
+ {
+ let p = new THREE.Vector4().fromArray(vertices, i).applyMatrix4(world.webglGroup.matrix);
+ ret.push(p.x);
+ ret.push(p.y);
+ ret.push(p.z);
+ ret.push(p.w);
+ }
+
+ return ret;
+
+ }
+
+ update_position(box, refresh){
+ var label = this.editor_ui.querySelector("#obj-local-"+box.obj_local_id);
+
+ if (label){
+
+ label.vertices = this.translate_vertices_to_global(box.world, psr_to_xyz(box.position, box.scale, box.rotation));
+
+ if (refresh){
+ var best_pos = this.compute_best_position(label.vertices);
+ var pos = this.coord_to_pixel(best_pos);
+
+ label.style.top = Math.round(pos.y) + 'px';
+ label.style.left = Math.round(pos.x) + 'px';
+
+ label.className = label.orgClassName;
+ if (pos.out_view){
+ label.className += " label-out-view";
+ }
+ }
+ }
+ }
+
+
+
+ remove_box(box){
+ var label = this.editor_ui.querySelector("#obj-local-"+box.obj_local_id);
+
+ if (label)
+ label.remove();
+ }
+
+ set_color_scheme(color_scheme){
+ this.color_scheme = color_scheme;
+ }
+ update_color(label)
+ {
+ if (this.color_scheme == "id")
+ {
+ label.className = "float-label color-"+ (label.obj_track_id % 33);
+ }
+ else // by id
+ {
+ label.className = "float-label "+label.obj_type;
+ }
+
+ label.orgClassName = label.className;
+ }
+
+ add_label(box){
+
+ var label = document.createElement('div');
+
+
+
+ label.id = "obj-local-"+box.obj_local_id;
+
+ var _self =this;
+
+ label.update_text = function(){
+ let label_text = '';
+ label_text += this.obj_type;
+ label_text += '
';
+
+ if (this.obj_attr)
+ {
+ label_text += '';
+ label_text += this.obj_attr;
+ label_text += '
';
+ }
+
+ label_text += '';
+ label_text += this.obj_track_id;
+ label_text += '
';
+
+ this.innerHTML = label_text;
+ }
+
+ label.obj_type = box.obj_type;
+ label.obj_local_id = box.obj_local_id;
+ label.obj_track_id = box.obj_track_id;
+ label.obj_attr = box.obj_attr;
+ label.update_text();
+ this.update_color(label);
+
+ label.vertices = this.translate_vertices_to_global(box.world, psr_to_xyz(box.position, box.scale, box.rotation));
+
+ var best_pos = this.compute_best_position(label.vertices);
+ best_pos = this.coord_to_pixel(best_pos);
+
+ var pos = best_pos;
+
+ label.style.top = Math.round(pos.y) + 'px';
+ label.style.left = Math.round(pos.x) + 'px';
+
+ if (pos.out_view){
+ label.className += " label-out-view";
+ }
+
+ this.labelsUi.appendChild(label);
+
+ let self = this;
+ label.onclick = ()=>{
+ this.on_label_clicked(box);
+ };
+ }
+
+
+ coord_to_pixel(p){
+ var width = this.container.clientWidth, height = this.container.clientHeight;
+ var widthHalf = width / 2, heightHalf = height / 2;
+
+ var ret={
+ x: ( p.x * widthHalf ) + widthHalf + 10,
+ y: - ( p.y * heightHalf ) + heightHalf - 10,
+ out_view: p.x>0.9 || p.x<-0.6 || p.y<-0.9 || p.y>0.9 || p.z< -1 || p.z > 1,
+ // p.x<-0.6 to prevent it from appearing ontop of sideviews.
+ }
+
+ return ret;
+ }
+
+ compute_best_position(vertices){
+ var _self = this;
+ var camera_p = [0,1,2,3,4,5,6,7].map(function(i){
+ return new THREE.Vector3(vertices[i*4+0], vertices[i*4+1], vertices[i*4+2]);
+ });
+
+ camera_p.forEach(function(x){
+ x.project(_self.view.camera);
+ });
+
+ var visible_p = camera_p;
+
+ var best_p = {x:-1, y: -1, z: -2};
+
+ visible_p.forEach(function(p){
+ if (p.x > best_p.x){
+ best_p.x = p.x;
+ }
+
+ if (p.y > best_p.y){
+ best_p.y = p.y;
+ }
+
+ if (p.z > best_p.z){
+ best_p.z = p.z;
+ }
+ })
+
+ return best_p;
+ }
+}
+
+
+export {FloatLabelManager, FastToolBox};
\ No newline at end of file
diff --git a/public/js/gui_worker.js b/public/js/gui_worker.js
new file mode 100644
index 0000000..e69de29
diff --git a/public/js/header.js b/public/js/header.js
new file mode 100644
index 0000000..b97930f
--- /dev/null
+++ b/public/js/header.js
@@ -0,0 +1,156 @@
+
+import { CubeRefractionMapping } from "./lib/three.module.js";
+import {saveWorldList} from "./save.js"
+
+var Header=function(ui, data, cfg, onSceneChanged, onFrameChanged, onObjectSelected, onCameraChanged){
+
+ this.ui = ui;
+ this.data = data;
+ this.cfg = cfg;
+ this.boxUi = ui.querySelector("#box");
+ this.refObjUi = ui.querySelector("#ref-obj");
+ this.sceneSelectorUi = ui.querySelector("#scene-selector");
+ this.frameSelectorUi = ui.querySelector("#frame-selector");
+ this.objectSelectorUi = ui.querySelector("#object-selector");
+ this.cameraSelectorUi = ui.querySelector("#camera-selector");
+ this.changedMarkUi = ui.querySelector("#changed-mark");
+
+ this.onSceneChanged = onSceneChanged;
+ this.onFrameChanged = onFrameChanged;
+ this.onObjectSelected = onObjectSelected;
+ this.onCameraChanged = onCameraChanged;
+
+
+ if (cfg.disableSceneSelector){
+ this.sceneSelectorUi.style.display="none";
+ }
+
+ if (cfg.disableFrameSelector){
+ this.frameSelectorUi.style.display="none";
+ }
+
+ if (cfg.disableCameraSelector){
+ this.cameraSelectorUi.style.display="none";
+ }
+
+ // update scene selector ui
+
+
+
+
+ this.updateSceneList = function(sceneDescList){
+ let scene_selector_str = "--scene-- ";
+ for (let scene in sceneDescList)
+ {
+ if (data.sceneDescList[scene])
+ scene_selector_str += ""+scene + " - " +data.sceneDescList[scene].scene + " ";
+ else
+ scene_selector_str += ""+scene+ " ";
+ }
+
+ this.ui.querySelector("#scene-selector").innerHTML = scene_selector_str;
+ }
+
+ this.updateSceneList(this.data.sceneDescList);
+
+ this.ui.querySelector("#btn-reload-scene-list").onclick = (event)=>{
+ let curentValue = this.sceneSelectorUi.value;
+
+ this.data.readSceneList().then((sceneDescList=>{
+ this.updateSceneList(sceneDescList);
+ this.sceneSelectorUi.value = curentValue;
+ }))
+ }
+
+
+
+ this.sceneSelectorUi.onchange = (e)=>{this.onSceneChanged(e);};
+ this.objectSelectorUi.onchange = (e)=>{this.onObjectSelected(e);};
+ this.frameSelectorUi.onchange = (e)=>{this.onFrameChanged(e);};
+ this.cameraSelectorUi.onchange = (e)=>{this.onCameraChanged(e);};
+
+ this.setObject = function(id)
+ {
+ this.objectSelectorUi.value = id;
+ }
+
+ this.clear_box_info = function(){
+ this.boxUi.innerHTML = '';
+ };
+
+ this.update_box_info = function(box){
+ var scale = box.scale;
+ var pos = box.position;
+ var rotation = box.rotation;
+ var points_number = box.world.lidar.get_box_points_number(box);
+ let distance = Math.sqrt(pos.x*pos.x + pos.y*pos.y).toFixed(2);
+
+ this.boxUi.innerHTML = "" + box.obj_type +"-"+box.obj_track_id +
+ (box.annotator? (" | " + box.annotator) : "") +
+ " | " + distance +
+ " | "+pos.x.toFixed(2) +" "+pos.y.toFixed(2) + " " + pos.z.toFixed(2) +
+ " | " +scale.x.toFixed(2) +" "+scale.y.toFixed(2) + " " + scale.z.toFixed(2) +
+ " | " +
+ (rotation.x*180/Math.PI).toFixed(2)+" "+(rotation.y*180/Math.PI).toFixed(2)+" "+(rotation.z*180/Math.PI).toFixed(2)+
+ " | " +
+ points_number + " ";
+ if (box.follows){
+ this.boxUi.innerHTML += "| F:"+box.follows.obj_track_id;
+ }
+ },
+
+ this.set_ref_obj = function(marked_object){
+ this.refObjUi.innerHTML="| Ref: "+marked_object.scene+"/"+marked_object.frame+": "+marked_object.ann.obj_type+"-"+marked_object.ann.obj_id;
+ },
+
+ this.set_frame_info =function(scene, frame, on_scene_changed){
+
+ if (this.sceneSelectorUi.value != scene){
+ this.sceneSelectorUi.value = scene;
+ on_scene_changed(scene);
+ }
+
+ this.frameSelectorUi.value = frame;
+ },
+
+ this.clear_frame_info = function(scene, frame){
+
+ },
+
+ this.updateModifiedStatus = function(){
+ let frames = this.data.worldList.filter(w=>w.annotation.modified);
+ if (frames.length > 0)
+ {
+ this.ui.querySelector("#changed-mark").className = 'ui-button alarm-mark';
+ }
+ else
+ {
+ this.ui.querySelector("#changed-mark").className = 'ui-button';
+ }
+ }
+
+ this.ui.querySelector("#changed-mark").onmouseenter = ()=>{
+
+ let items = "";
+ let frames = this.data.worldList.filter(w=>w.annotation.modified).map(w=>w.frameInfo);
+ frames.forEach(f=>{
+ items += "" + f.frame + '
';
+ });
+
+ if (frames.length > 0){
+ this.ui.querySelector("#changed-world-list").innerHTML = items;
+ this.ui.querySelector("#changed-world-list-wrapper").style.display = 'inherit';
+ }
+ }
+
+ this.ui.querySelector("#changed-mark").onmouseleave = ()=>{
+ this.ui.querySelector("#changed-world-list-wrapper").style.display = 'none';
+ }
+
+ this.ui.querySelector("#save-button").onclick = ()=>{
+ saveWorldList(this.data.worldList);
+ }
+};
+
+
+export {Header}
\ No newline at end of file
diff --git a/public/js/image.js b/public/js/image.js
new file mode 100644
index 0000000..816b8aa
--- /dev/null
+++ b/public/js/image.js
@@ -0,0 +1,1210 @@
+
+import {vector4to3, vector3_nomalize, psr_to_xyz, matmul} from "./util.js"
+import {globalObjectCategory, } from './obj_cfg.js';
+import { MovableView } from "./popup_dialog.js";
+
+function BoxImageContext(ui){
+
+ this.ui = ui;
+
+ // draw highlighted box
+ this.updateFocusedImageContext = function(box){
+ var scene_meta = box.world.frameInfo.sceneMeta;
+
+
+ let bestImage = choose_best_camera_for_point(
+ scene_meta,
+ box.position);
+
+ if (!bestImage){
+ return;
+ }
+
+ if (!scene_meta.calib.camera){
+ return;
+ }
+
+ var calib = scene_meta.calib.camera[bestImage]
+ if (!calib){
+ return;
+ }
+
+ if (calib){
+ var img = box.world.cameras.getImageByName(bestImage);
+ if (img && (img.naturalWidth > 0)){
+
+ this.clear_canvas();
+
+ var imgfinal = box_to_2d_points(box, calib)
+
+ if (imgfinal != null){ // if projection is out of range of the image, stop drawing.
+ var ctx = this.ui.getContext("2d");
+ ctx.lineWidth = 0.5;
+
+ // note: 320*240 should be adjustable
+ var crop_area = crop_image(img.naturalWidth, img.naturalHeight, ctx.canvas.width, ctx.canvas.height, imgfinal);
+
+ ctx.drawImage(img, crop_area[0], crop_area[1],crop_area[2], crop_area[3], 0, 0, ctx.canvas.width, ctx.canvas.height);// ctx.canvas.clientHeight);
+ //ctx.drawImage(img, 0,0,img.naturalWidth, img.naturalHeight, 0, 0, 320, 180);// ctx.canvas.clientHeight);
+ var imgfinal = vectorsub(imgfinal, [crop_area[0],crop_area[1]]);
+ var trans_ratio = {
+ x: ctx.canvas.height/crop_area[3],
+ y: ctx.canvas.height/crop_area[3],
+ }
+
+ draw_box_on_image(ctx, box, imgfinal, trans_ratio, true);
+ }
+ }
+ }
+ }
+
+ this.clear_canvas = function(){
+ var c = this.ui;
+ var ctx = c.getContext("2d");
+ ctx.clearRect(0, 0, c.width, c.height);
+ }
+
+
+ function vectorsub(vs, v){
+ var ret = [];
+ var vl = v.length;
+
+ for (var i = 0; imaxx) maxx=x;
+ else if (xmaxy) maxy=y;
+ else if (y clientWidth/clientHeight){
+ //increate height
+ targetHeight = targetWidth*clientHeight/clientWidth;
+ }
+ else{
+ targetWidth = targetHeight*clientWidth/clientHeight;
+ }
+
+ var centerx = (maxx+minx)/2;
+ var centery = (maxy+miny)/2;
+
+ return [
+ centerx - targetWidth/2,
+ centery - targetHeight/2,
+ targetWidth,
+ targetHeight
+ ];
+ }
+
+ function draw_box_on_image(ctx, box, box_corners, trans_ratio, selected){
+ var imgfinal = box_corners;
+
+ if (!selected){
+ let target_color = null;
+ if (box.world.data.cfg.color_obj == "category")
+ {
+ target_color = globalObjectCategory.get_color_by_category(box.obj_type);
+ }
+ else // by id
+ {
+ let idx = (box.obj_track_id)?parseInt(box.obj_track_id): box.obj_local_id;
+ target_color = globalObjectCategory.get_color_by_id(idx);
+ }
+
+
+
+ //ctx.strokeStyle = get_obj_cfg_by_type(box.obj_type).color;
+
+ //var c = get_obj_cfg_by_type(box.obj_type).color;
+ var r ="0x"+(target_color.x*256).toString(16);
+ var g ="0x"+(target_color.y*256).toString(16);;
+ var b ="0x"+(target_color.z*256).toString(16);;
+
+ ctx.fillStyle="rgba("+parseInt(r)+","+parseInt(g)+","+parseInt(b)+",0.2)";
+ }
+ else{
+ ctx.strokeStyle="#ff00ff";
+ ctx.fillStyle="rgba(255,0,255,0.2)";
+ }
+
+ // front panel
+ ctx.beginPath();
+ ctx.moveTo(imgfinal[3*2]*trans_ratio.x,imgfinal[3*2+1]*trans_ratio.y);
+
+ for (var i=0; i < imgfinal.length/2/2; i++)
+ {
+ ctx.lineTo(imgfinal[i*2+0]*trans_ratio.x, imgfinal[i*2+1]*trans_ratio.y);
+ }
+
+ ctx.closePath();
+ ctx.fill();
+
+ // frame
+ ctx.beginPath();
+
+ ctx.moveTo(imgfinal[3*2]*trans_ratio.x,imgfinal[3*2+1]*trans_ratio.y);
+
+ for (var i=0; i < imgfinal.length/2/2; i++)
+ {
+ ctx.lineTo(imgfinal[i*2+0]*trans_ratio.x, imgfinal[i*2+1]*trans_ratio.y);
+ }
+ //ctx.stroke();
+
+
+ //ctx.strokeStyle="#ff00ff";
+ //ctx.beginPath();
+
+ ctx.moveTo(imgfinal[7*2]*trans_ratio.x,imgfinal[7*2+1]*trans_ratio.y);
+
+ for (var i=4; i < imgfinal.length/2; i++)
+ {
+ ctx.lineTo(imgfinal[i*2+0]*trans_ratio.x, imgfinal[i*2+1]*trans_ratio.y);
+ }
+
+ ctx.moveTo(imgfinal[0*2]*trans_ratio.x,imgfinal[0*2+1]*trans_ratio.y);
+ ctx.lineTo(imgfinal[4*2+0]*trans_ratio.x, imgfinal[4*2+1]*trans_ratio.y);
+ ctx.moveTo(imgfinal[1*2]*trans_ratio.x,imgfinal[1*2+1]*trans_ratio.y);
+ ctx.lineTo(imgfinal[5*2+0]*trans_ratio.x, imgfinal[5*2+1]*trans_ratio.y);
+ ctx.moveTo(imgfinal[2*2]*trans_ratio.x,imgfinal[2*2+1]*trans_ratio.y);
+ ctx.lineTo(imgfinal[6*2+0]*trans_ratio.x, imgfinal[6*2+1]*trans_ratio.y);
+ ctx.moveTo(imgfinal[3*2]*trans_ratio.x,imgfinal[3*2+1]*trans_ratio.y);
+ ctx.lineTo(imgfinal[7*2+0]*trans_ratio.x, imgfinal[7*2+1]*trans_ratio.y);
+
+
+ ctx.stroke();
+ }
+}
+
+
+
+class ImageContext extends MovableView{
+
+ constructor(parentUi, name, autoSwitch, cfg, on_img_click){
+
+ // create ui
+ let template = document.getElementById("image-wrapper-template");
+ let tool = template.content.cloneNode(true);
+ // this.boxEditorHeaderUi.appendChild(tool);
+ // return this.boxEditorHeaderUi.lastElementChild;
+
+ parentUi.appendChild(tool);
+ let ui = parentUi.lastElementChild;
+ let handle = ui.querySelector("#move-handle");
+ super(handle, ui);
+
+ this.ui = ui;
+ this.cfg = cfg;
+ this.on_img_click = on_img_click;
+ this.autoSwitch = autoSwitch;
+ this.setImageName(name);
+ }
+
+ remove(){
+ this.ui.remove();
+ }
+
+
+ setImageName(name)
+ {
+ this.name = name;
+ this.ui.querySelector("#header").innerText = (this.autoSwitch?"auto-":"")+name;
+ }
+
+
+ get_selected_box = null;
+
+
+ init_image_op(func_get_selected_box){
+ this.ui.onclick = (e)=>this.on_click(e);
+ this.get_selected_box = func_get_selected_box;
+ // var h = parentUi.querySelector("#resize-handle");
+ // h.onmousedown = resize_mouse_down;
+
+ // c.onresize = on_resize;
+ }
+ clear_main_canvas(){
+
+ var boxes = this.ui.querySelector("#svg-boxes").children;
+
+ if (boxes.length>0){
+ for (var c=boxes.length-1; c >= 0; c--){
+ boxes[c].remove();
+ }
+ }
+
+ var points = this.ui.querySelector("#svg-points").children;
+
+ if (points.length>0){
+ for (var c=points.length-1; c >= 0; c--){
+ points[c].remove();
+ }
+ }
+ }
+
+
+
+
+
+
+ world = null;
+ img = null;
+
+ attachWorld(world){
+ this.world = world;
+ };
+
+ hide (){
+ this.ui.style.display="none";
+ };
+
+ hidden(){
+ this.ui.style.display=="none";
+ };
+
+ show(){
+ this.ui.style.display="";
+ };
+
+
+
+ drawing = false;
+ points = [];
+ polyline;
+
+ all_lines=[];
+
+ img_lidar_point_map = {};
+
+ point_color_by_distance(x,y)
+ {
+ // x,y are image coordinates
+ let p = this.img_lidar_point_map[y*this.img.width+x];
+
+ let distance = Math.sqrt(p[1]*p[1] + p[2]*p[2] + p[3]*p[3] );
+
+ if (distance > 60.0)
+ distance = 60.0;
+ else if (distance < 10.0)
+ distance = 10.0;
+
+ return [(distance-10)/50.0, 1- (distance-10)/50.0, 0].map(c=>{
+ let hex = Math.floor(c*255).toString(16);
+ if (hex.length == 1)
+ hex = "0"+hex;
+ return hex;
+ }).reduce((a,b)=>a+b,"#");
+
+ }
+
+ to_polyline_attr(points){
+ return points.reduce(function(x,y){
+ return String(x)+","+y;
+ }
+ )
+ }
+
+
+ to_viewbox_coord(x,y){
+ var div = this.ui.querySelector("#maincanvas-svg");
+
+ x = Math.round(x*2048/div.clientWidth);
+ y = Math.round(y*1536/div.clientHeight);
+ return [x,y];
+
+ }
+
+ on_click(e){
+ var p= this.to_viewbox_coord(e.layerX, e.layerY);
+ var x=p[0];
+ var y=p[1];
+
+ console.log("clicked",x,y);
+
+
+ if (!this.drawing){
+
+ if (e.ctrlKey){
+ this.drawing = true;
+ var svg = this.ui.querySelector("#maincanvas-svg");
+ //svg.style.position = "absolute";
+
+ this.polyline = document.createElementNS("http://www.w3.org/2000/svg", 'polyline');
+ svg.appendChild(this.polyline);
+ this.points.push(x);
+ this.points.push(y);
+
+
+ this.polyline.setAttribute("class", "maincanvas-line")
+ this.polyline.setAttribute("points", this.to_polyline_attr(this.points));
+
+ var c = this.ui;
+ c.onmousemove = on_move;
+ c.ondblclick = on_dblclick;
+ c.onkeydown = on_key;
+
+ }
+ else{
+ // not drawing
+ //this is a test
+ if (false){
+ let nearest_x = 100000;
+ let nearest_y = 100000;
+ let selected_pts = [];
+
+ for (let i =x-100; i= this.img.width)
+ continue;
+
+ for (let j = y-100; j= this.img.height)
+ continue;
+
+ let lidarpoint = this.img_lidar_point_map[j*this.img.width+i];
+ if (lidarpoint){
+ //console.log(i,j, lidarpoint);
+ selected_pts.push(lidarpoint); //index of lidar point
+
+ if (((i-x) * (i-x) + (j-y)*(j-y)) < ((nearest_x-x)*(nearest_x-x) + (nearest_y-y)*(nearest_y-y))){
+ nearest_x = i;
+ nearest_y = j;
+ }
+ }
+
+ }
+ }
+ console.log("nearest", nearest_x, nearest_y);
+ this.draw_point(nearest_x, nearest_y);
+ if (nearest_x < 100000)
+ {
+ this.on_img_click([this.img_lidar_point_map[nearest_y*this.img.width+nearest_x][0]]);
+ }
+ }
+
+ }
+
+ } else {
+ if (this.points[this.points.length-2]!=x || this.points[this.points.length-1]!=y){
+ this.points.push(x);
+ this.points.push(y);
+ this.polyline.setAttribute("points", this.to_polyline_attr(this.points));
+ }
+
+ }
+
+
+ function on_move(e){
+ var p= to_viewbox_coord(e.layerX, e.layerY);
+ var x=p[0];
+ var y=p[1];
+
+ console.log(x,y);
+ this.polyline.setAttribute("points", this.to_polyline_attr(this.points) + ',' + x + ',' + y);
+ }
+
+ function on_dblclick(e){
+
+ this.points.push(this.points[0]);
+ this.points.push(this.points[1]);
+
+ this.polyline.setAttribute("points", this.to_polyline_attr(this.points));
+ console.log(this.points)
+
+ all_lines.push(this.points);
+
+ this.drawing = false;
+ this.points = [];
+
+ var c = this.ui;
+ c.onmousemove = null;
+ c.ondblclick = null;
+ c.onkeypress = null;
+ c.blur();
+ }
+
+ function cancel(){
+
+ polyline.remove();
+
+ this.drawing = false;
+ this.points = [];
+ var c = this.ui;
+ c.onmousemove = null;
+ c.ondblclick = null;
+ c.onkeypress = null;
+
+ c.blur();
+ }
+
+ function on_key(e){
+ if (e.key == "Escape"){
+ cancel();
+
+ }
+ }
+ }
+
+
+ // all boxes
+
+
+
+ getCalib(){
+ var scene_meta = this.world.sceneMeta;
+
+ if (!scene_meta.calib.camera){
+ return null;
+ }
+
+ //var active_camera_name = this.world.cameras.active_name;
+ var calib = scene_meta.calib.camera[this.name];
+
+ return calib;
+ }
+
+
+
+
+ get_trans_ratio(){
+ var img = this.world.cameras.getImageByName(this.name);
+
+ if (!img || img.width==0){
+ return null;
+ }
+
+ var clientWidth, clientHeight;
+
+ clientWidth = 2048;
+ clientHeight = 1536;
+
+ var trans_ratio ={
+ x: clientWidth/img.naturalWidth,
+ y: clientHeight/img.naturalHeight,
+ };
+
+ return trans_ratio;
+ }
+
+ show_image(){
+ var svgimage = this.ui.querySelector("#svg-image");
+
+ // active img is set by global, it's not set sometimes.
+ var img = this.world.cameras.getImageByName(this.name);
+ if (img){
+ svgimage.setAttribute("xlink:href", img.src);
+ }
+
+ this.img = img;
+
+
+ }
+
+
+ points_to_svg(points, trans_ratio, cssclass, radius=2){
+ var ptsFinal = points.map(function(x, i){
+ if (i%2==0){
+ return Math.round(x * trans_ratio.x);
+ }else {
+ return Math.round(x * trans_ratio.y);
+ }
+ });
+
+ var svg = document.createElementNS("http://www.w3.org/2000/svg", 'g');
+
+ if (cssclass)
+ {
+ svg.setAttribute("class", cssclass);
+ }
+
+ for (let i = 0; i < ptsFinal.length; i+=2){
+
+
+ let x = ptsFinal[i];
+ let y = ptsFinal[i+1];
+
+ let p = document.createElementNS("http://www.w3.org/2000/svg", 'circle');
+ p.setAttribute("cx", x);
+ p.setAttribute("cy", y);
+ p.setAttribute("r", 2);
+ p.setAttribute("stroke-width", "1");
+
+ if (! cssclass){
+ let image_x = points[i];
+ let image_y = points[i+1];
+ let color = point_color_by_distance(image_x, image_y);
+ color += "24"; //transparency
+ p.setAttribute("stroke", color);
+ p.setAttribute("fill", color);
+ }
+
+ svg.appendChild(p);
+ }
+
+ return svg;
+ }
+
+ draw_point(x,y){
+ let trans_ratio = this.get_trans_ratio();
+ let svg = this.ui.querySelector("#svg-points");
+ let pts_svg = this.points_to_svg([x,y], trans_ratio, "radar-points");
+ svg.appendChild(pts_svg);
+ };
+
+
+
+
+ render_2d_image (){
+
+
+ if (this.cfg.disableMainImageContext)
+ return;
+ this.clear_main_canvas();
+
+ this.show_image();
+ this.draw_svg();
+ }
+
+
+ hide_canvas(){
+ //document.getElementsByClassName("ui-wrapper")[0].style.display="none";
+ this.ui.style.display="none";
+ }
+
+ show_canvas(){
+ this.ui.style.display="inline";
+ }
+
+
+ draw_svg(){
+ // draw picture
+ var img = this.world.cameras.getImageByName(this.name);
+
+ if (!img || img.width==0){
+ this.hide_canvas();
+ return;
+ }
+
+ this.show_canvas();
+
+ var trans_ratio = this.get_trans_ratio();
+
+ var calib = this.getCalib();
+ if (!calib){
+ return;
+ }
+
+ let svg = this.ui.querySelector("#svg-boxes");
+
+ // draw boxes
+ this.world.annotation.boxes.forEach((box)=>{
+ var imgfinal = box_to_2d_points(box, calib);
+ if (imgfinal){
+ var box_svg = this.box_to_svg (box, imgfinal, trans_ratio, this.get_selected_box() == box);
+ svg.appendChild(box_svg);
+ }
+
+ });
+
+ svg = this.ui.querySelector("#svg-points");
+
+ // draw radar points
+ if (this.cfg.projectRadarToImage)
+ {
+ this.world.radars.radarList.forEach(radar=>{
+ let pts = radar.get_unoffset_radar_points();
+ let ptsOnImg = points3d_to_image2d(pts, calib);
+
+ // there may be none after projecting
+ if (ptsOnImg && ptsOnImg.length>0){
+ let pts_svg = this.points_to_svg(ptsOnImg, trans_ratio, radar.cssStyleSelector);
+ svg.appendChild(pts_svg);
+ }
+ });
+ }
+
+
+
+ // project lidar points onto camera image
+ if (this.cfg.projectLidarToImage){
+ let pts = this.world.lidar.get_all_points();
+ let ptsOnImg = points3d_to_image2d(pts, calib, true, this.img_lidar_point_map, img.width, img.height);
+
+ // there may be none after projecting
+ if (ptsOnImg && ptsOnImg.length>0){
+ let pts_svg = this.points_to_svg(ptsOnImg, trans_ratio);
+ svg.appendChild(pts_svg);
+ }
+ }
+
+ }
+
+ box_to_svg(box, box_corners, trans_ratio, selected){
+
+
+ var imgfinal = box_corners.map(function(x, i){
+ if (i%2==0){
+ return Math.round(x * trans_ratio.x);
+ }else {
+ return Math.round(x * trans_ratio.y);
+ }
+ })
+
+
+ var svg = document.createElementNS("http://www.w3.org/2000/svg", 'g');
+ svg.setAttribute("id", "svg-box-local-"+box.obj_local_id);
+
+ if (selected){
+ svg.setAttribute("class", box.obj_type+" box-svg box-svg-selected");
+ } else{
+ if (box.world.data.cfg.color_obj == "id")
+ {
+ svg.setAttribute("class", "color-"+box.obj_track_id%33);
+ }
+ else // by id
+ {
+ svg.setAttribute("class", box.obj_type + " box-svg");
+ }
+ }
+
+
+ var front_panel = document.createElementNS("http://www.w3.org/2000/svg", 'polygon');
+ svg.appendChild(front_panel);
+ front_panel.setAttribute("points",
+ imgfinal.slice(0, 4*2).reduce(function(x,y){
+ return String(x)+","+y;
+ })
+ )
+
+ /*
+ var back_panel = document.createElementNS("http://www.w3.org/2000/svg", 'polygon');
+ svg.appendChild(back_panel);
+ back_panel.setAttribute("points",
+ imgfinal.slice(4*2).reduce(function(x,y){
+ return String(x)+","+y;
+ })
+ )
+ */
+
+ for (var i = 0; i<4; ++i){
+ var line = document.createElementNS("http://www.w3.org/2000/svg", 'line');
+ svg.appendChild(line);
+ line.setAttribute("x1", imgfinal[(4+i)*2]);
+ line.setAttribute("y1", imgfinal[(4+i)*2+1]);
+ line.setAttribute("x2", imgfinal[(4+(i+1)%4)*2]);
+ line.setAttribute("y2", imgfinal[(4+(i+1)%4)*2+1]);
+ }
+
+
+ for (var i = 0; i<4; ++i){
+ var line = document.createElementNS("http://www.w3.org/2000/svg", 'line');
+ svg.appendChild(line);
+ line.setAttribute("x1", imgfinal[i*2]);
+ line.setAttribute("y1", imgfinal[i*2+1]);
+ line.setAttribute("x2", imgfinal[(i+4)*2]);
+ line.setAttribute("y2", imgfinal[(i+4)*2+1]);
+ }
+
+ return svg;
+ }
+
+
+ boxes_manager = {
+ display_image: ()=>{
+ if (!this.cfg.disableMainImageContext)
+ this.render_2d_image();
+ },
+
+ add_box: (box)=>{
+ var calib = this.getCalib();
+ if (!calib){
+ return;
+ }
+ var trans_ratio = this.get_trans_ratio();
+ if (trans_ratio){
+ var imgfinal = box_to_2d_points(box, calib);
+ if (imgfinal){
+ var imgfinal = imgfinal.map(function(x, i){
+ if (i%2==0){
+ return Math.round(x * trans_ratio.x);
+ }else {
+ return Math.round(x * trans_ratio.y);
+ }
+ })
+
+ var svg_box = this.box_to_svg(box, imgfinal, trans_ratio);
+ var svg = this.ui.querySelector("#svg-boxes");
+ svg.appendChild(svg_box);
+ }
+ }
+ },
+
+
+ onBoxSelected: (box_obj_local_id, obj_type)=>{
+ var b = this.ui.querySelector("#svg-box-local-"+box_obj_local_id);
+ if (b){
+ b.setAttribute("class", "box-svg-selected");
+ }
+ },
+
+
+ onBoxUnselected: (box_obj_local_id, obj_type)=>{
+ var b = this.ui.querySelector("#svg-box-local-"+box_obj_local_id);
+
+ if (b)
+ b.setAttribute("class", obj_type);
+ },
+
+ remove_box: (box_obj_local_id)=>{
+ var b = this.ui.querySelector("#svg-box-local-"+box_obj_local_id);
+
+ if (b)
+ b.remove();
+ },
+
+ update_obj_type: (box_obj_local_id, obj_type)=>{
+ this.onBoxSelected(box_obj_local_id, obj_type);
+ },
+
+ update_box: (box)=>{
+ var b = this.ui.querySelector("#svg-box-local-"+box.obj_local_id);
+ if (!b){
+ return;
+ }
+
+ var children = b.childNodes;
+
+ var calib = this.getCalib();
+ if (!calib){
+ return;
+ }
+
+ var trans_ratio = this.get_trans_ratio();
+ var imgfinal = box_to_2d_points(box, calib);
+
+ if (!imgfinal){
+ //box may go out of image
+ return;
+ }
+ var imgfinal = imgfinal.map(function(x, i){
+ if (i%2==0){
+ return Math.round(x * trans_ratio.x);
+ }else {
+ return Math.round(x * trans_ratio.y);
+ }
+ })
+
+ if (imgfinal){
+ var front_panel = children[0];
+ front_panel.setAttribute("points",
+ imgfinal.slice(0, 4*2).reduce(function(x,y){
+ return String(x)+","+y;
+ })
+ )
+
+
+
+ for (var i = 0; i<4; ++i){
+ var line = children[1+i];
+ line.setAttribute("x1", imgfinal[(4+i)*2]);
+ line.setAttribute("y1", imgfinal[(4+i)*2+1]);
+ line.setAttribute("x2", imgfinal[(4+(i+1)%4)*2]);
+ line.setAttribute("y2", imgfinal[(4+(i+1)%4)*2+1]);
+ }
+
+
+ for (var i = 0; i<4; ++i){
+ var line = children[5+i];
+ line.setAttribute("x1", imgfinal[i*2]);
+ line.setAttribute("y1", imgfinal[i*2+1]);
+ line.setAttribute("x2", imgfinal[(i+4)*2]);
+ line.setAttribute("y2", imgfinal[(i+4)*2+1]);
+ }
+ }
+
+ }
+ }
+
+}
+
+
+class ImageContextManager {
+ constructor(parentUi, selectorUi, cfg, on_img_click){
+ this.parentUi = parentUi;
+ this.selectorUi = selectorUi;
+ this.cfg = cfg;
+ this.on_img_click = on_img_click;
+
+ this.addImage("", true);
+
+
+ this.selectorUi.onmouseenter=function(event){
+ if (this.timerId)
+ {
+ clearTimeout(this.timerId);
+ this.timerId = null;
+ }
+
+ event.target.querySelector("#camera-list").style.display="";
+
+ };
+
+
+ this.selectorUi.onmouseleave=function(event){
+ let ui = event.target.querySelector("#camera-list");
+
+ this.timerId = setTimeout(()=>{
+ ui.style.display="none";
+ this.timerId = null;
+ },
+ 200);
+
+ };
+
+ this.selectorUi.querySelector("#camera-list").onclick = (event)=>{
+ let cameraName = event.target.innerText;
+
+ if (cameraName == "auto"){
+
+ let existed = this.images.find(x=>x.autoSwitch);
+
+ if (existed)
+ {
+ this.removeImage(existed);
+ }
+ else
+ {
+ this.addImage("", true);
+ }
+
+ }
+ else{
+ let existed = this.images.find(x=>!x.autoSwitch && x.name == cameraName);
+
+ if (existed)
+ {
+ this.removeImage(existed);
+
+ }
+ else
+ {
+ this.addImage(cameraName);
+ }
+ }
+ };
+
+ }
+
+ updateCameraList(cameras){
+
+ let autoCamera = 'auto
';
+
+ if (this.images.find(i=>i.autoSwitch)){
+ autoCamera = 'auto
';
+ }
+
+ let camera_selector_str = cameras.map(c=>{
+
+ let existed = this.images.find(i=>i.name == c && !i.autoSwitch);
+ let className = existed?"camera-item camera-selected":"camera-item";
+
+ return `${c}
`;
+ }).reduce((x,y)=>x+y, autoCamera);
+
+ let ui = this.selectorUi.querySelector("#camera-list");
+ ui.innerHTML = camera_selector_str;
+ ui.style.display="none";
+
+ this.setDefaultBestCamera(cameras[0]);
+ }
+
+ setDefaultBestCamera(c){
+
+ if (!this.bestCamera)
+ {
+ let existed = this.images.find(x=>x.autoSwitch);
+ if (existed)
+ {
+ existed.setImageName(c);
+ }
+
+ this.bestCamera = c;
+ }
+ }
+
+ images = [];
+ addImage(name, autoSwitch)
+ {
+
+ if (autoSwitch && this.bestCamera && !name)
+ name = this.bestCamera;
+
+ let image = new ImageContext(this.parentUi, name, autoSwitch, this.cfg, this.on_img_click);
+
+ this.images.push(image);
+
+ if (this.init_image_op_para){
+ image.init_image_op(this.init_image_op_para);
+ }
+
+ if (this.world){
+ image.attachWorld(this.world);
+ image.render_2d_image();
+ }
+
+
+ let selectorName = autoSwitch?"auto":name;
+
+ let ui = this.selectorUi.querySelector("#camera-item-"+selectorName);
+ if (ui)
+ ui.className = "camera-item camera-selected";
+
+
+ return image;
+ }
+
+ removeImage(image){
+
+ let selectorName = image.autoSwitch?"auto":image.name;
+ this.selectorUi.querySelector("#camera-item-"+selectorName).className = "camera-item";
+ this.images = this.images.filter(x=>x!=image);
+ image.remove();
+
+
+ }
+
+ setBestCamera(camera)
+ {
+ this.images.filter(i=>i.autoSwitch).forEach(i=>{
+ i.setImageName(camera);
+ i.boxes_manager.display_image();
+ });
+
+ this.bestCamera = camera;
+ }
+
+ render_2d_image(){
+ this.images.forEach(i=>i.render_2d_image());
+ }
+
+ attachWorld(world){
+
+ this.world = world;
+ this.images.forEach(i=>i.attachWorld(world));
+ }
+
+ hide(){
+ this.images.forEach(i=>i.hide());
+ }
+
+
+ show(){
+ this.images.forEach(i=>i.show());
+ }
+
+ clear_main_canvas(){
+ this.images.forEach(i=>i.clear_main_canvas());
+ }
+
+ init_image_op(op){
+ this.init_image_op_para = op;
+ this.images.forEach(i=>i.init_image_op(op));
+ }
+ hidden(){
+ return false;
+ }
+
+ choose_best_camera_for_point = choose_best_camera_for_point;
+
+ self = this;
+
+ boxes_manager = {
+
+ display_image: ()=>{
+ if (!this.cfg.disableMainImageContext)
+ this.render_2d_image();
+ },
+
+ add_box: (box)=>{
+ this.images.forEach(i=>i.boxes_manager.add_box(box));
+ },
+
+
+ onBoxSelected: (box_obj_local_id, obj_type)=>{
+ this.images.forEach(i=>i.boxes_manager.onBoxSelected(box_obj_local_id, obj_type));
+ },
+
+
+ onBoxUnselected: (box_obj_local_id, obj_type)=>{
+ this.images.forEach(i=>i.boxes_manager.onBoxUnselected(box_obj_local_id, obj_type));
+ },
+
+ remove_box: (box_obj_local_id)=>{
+ this.images.forEach(i=>i.boxes_manager.remove_box(box_obj_local_id));
+ },
+
+ update_obj_type: (box_obj_local_id, obj_type)=>{
+ this.images.forEach(i=>i.boxes_manager.update_obj_type(box_obj_local_id, obj_type));
+ },
+
+ update_box: (box)=>{
+ this.images.forEach(i=>i.boxes_manager.update_box(box));
+ }
+ }
+
+
+}
+
+function box_to_2d_points(box, calib){
+ var scale = box.scale;
+ var pos = box.position;
+ var rotation = box.rotation;
+
+ var box3d = psr_to_xyz(pos, scale, rotation);
+
+ //console.log(box.obj_track_id, box3d.slice(8*4));
+
+ box3d = box3d.slice(0,8*4);
+ return points3d_homo_to_image2d(box3d, calib);
+}
+
+// points3d is length 4 row vector, homogeneous coordinates
+// returns 2d row vectors
+function points3d_homo_to_image2d(points3d, calib, accept_partial=false,save_map, img_dx, img_dy){
+ var imgpos = matmul(calib.extrinsic, points3d, 4);
+
+ //rect matrix shall be applied here, for kitti
+ if (calib.rect){
+ imgpos = matmul(calib.rect, imgpos, 4);
+ }
+
+ var imgpos3 = vector4to3(imgpos);
+
+ var imgpos2;
+ if (calib.intrinsic.length>9) {
+ imgpos2 = matmul(calib.intrinsic, imgpos, 4);
+ }
+ else
+ imgpos2 = matmul(calib.intrinsic, imgpos3, 3);
+
+ let imgfinal = vector3_nomalize(imgpos2);
+ let imgfinal_filterd = [];
+
+ if (accept_partial){
+ let temppos=[];
+ let p = imgpos3;
+ for (var i = 0; i0){
+ let x = imgfinal[i*2];
+ let y = imgfinal[i*2+1];
+
+ x = Math.round(x);
+ y = Math.round(y);
+ if (x > 0 && x < img_dx && y > 0 && y < img_dy){
+ if (save_map){
+ save_map[img_dx*y+x] = [i, points3d[i*4+0], points3d[i*4+1], points3d[i*4+2]]; //save index? a little dangerous! //[points3d[i*4+0], points3d[i*4+1], points3d[i*4+2]];
+ }
+
+ imgfinal_filterd.push(x);
+ imgfinal_filterd.push(y);
+
+ }
+ else{
+ // console.log("points outside of image",x,y);
+ }
+
+ }
+ }
+
+ imgfinal = imgfinal_filterd;
+ //warning: what if calib.intrinsic.length
+ //todo: this function need clearance
+ //imgpos2 = matmul(calib.intrinsic, temppos, 3);
+ }
+ else if (!accept_partial && !all_points_in_image_range(imgpos3)){
+ return null;
+ }
+
+ return imgfinal;
+}
+
+function point3d_to_homo(points){
+ let homo=[];
+ for (let i =0; i0){
+ return valid_proj_pos[0].calib;
+ }
+
+ return null;
+
+}
+
+
+export {ImageContextManager, BoxImageContext};
diff --git a/public/js/info_box.js b/public/js/info_box.js
new file mode 100644
index 0000000..425053e
--- /dev/null
+++ b/public/js/info_box.js
@@ -0,0 +1,115 @@
+import { globalKeyDownManager } from "./keydown_manager.js";
+import { PopupDialog } from "./popup_dialog.js";
+
+
+
+class InfoBox extends PopupDialog{
+
+ mouseDown = false;
+ mouseDwwnPos = {};
+
+
+ constructor(ui)
+ {
+ super(ui);
+
+ this.contentUi = this.ui.querySelector("#info-content");
+
+ this.buttons = {
+ "yes": this.ui.querySelector("#btn-yes"),
+ "no": this.ui.querySelector("#btn-no"),
+ "maximize": this.ui.querySelector("#btn-maximize"),
+ "restore": this.ui.querySelector("#btn-restore"),
+ "exit": this.ui.querySelector("#btn-exit"),
+ };
+
+ for (let btn in this.buttons)
+ {
+ this.buttons[btn].onclick = ()=>{
+ this.hide(btn);
+ }
+ }
+
+ this.ui.addEventListener("keydown", (ev)=>{
+ //anykey
+ if ( ev.shiftKey || ev.ctrlKey || ev.altKey)
+ {
+ //
+ }
+ else
+ {
+ this.hide();
+ ev.preventDefault();
+ ev.stopPropagation();
+ }
+
+ });
+ }
+
+
+
+ showButtons(btns){
+ for (let btn in this.buttons)
+ {
+ this.buttons[btn].style.display = 'none';
+ }
+
+ for (let btn in btns)
+ {
+ this.buttons[btns[btn]].style.display = '';
+ }
+ }
+
+ makeVisible(pointerPosition)
+ {
+ if (!pointerPosition)
+ {
+
+ //default pos
+ let parentRect = this.ui.getBoundingClientRect();
+ let viewRect = this.viewUi.getBoundingClientRect();
+
+ this.viewUi.style.top = (parentRect.top+parentRect.height/3) + "px";
+ this.viewUi.style.left = (parentRect.left+parentRect.width/2-viewRect.width/2) + "px";
+ }
+ else
+ {
+ let parentRect = this.ui.getBoundingClientRect();
+ let viewRect = this.viewUi.getBoundingClientRect();
+
+ let left = pointerPosition.x - viewRect.width/2;
+ if (left < parentRect.left) left = parentRect.left;
+ if (left + viewRect.width > parentRect.right)
+ left -= left + viewRect.width - parentRect.right;
+
+ let top = pointerPosition.y - viewRect.height/2;
+ if (top < parentRect.top)
+ top = parentRect.top;
+
+ if (top + viewRect.height > parentRect.bottom)
+ top -= top + viewRect.height - parentRect.bottom;
+
+ this.viewUi.style.top = top + "px";
+ this.viewUi.style.left = left + "px";
+ }
+ }
+
+
+ show(title, content, btnList, onexit, pointerPosition)
+ {
+ this.showButtons(btnList);
+
+ this.titleUi.innerText = title;
+ this.contentUi.innerHTML = content;
+
+ super.show(onexit);
+
+ this.makeVisible(pointerPosition);
+
+ this.ui.focus();
+ }
+
+}
+
+
+export {InfoBox};
\ No newline at end of file
diff --git a/public/js/keydown_manager.js b/public/js/keydown_manager.js
new file mode 100644
index 0000000..dca2829
--- /dev/null
+++ b/public/js/keydown_manager.js
@@ -0,0 +1,42 @@
+
+
+class KeyDownManager
+{
+
+ handlerList = [];
+
+ // return id;
+ register(handler, name)
+ {
+ this.handlerList.push([name, handler]);
+ console.log("register keydown", name);
+ }
+
+ deregister(name)
+ {
+ console.log("deregister keydown", name);
+ this.handlerList = this.handlerList.filter(v=>v[0]!== name);
+ }
+
+ constructor()
+ {
+ document.addEventListener( 'keydown', (event)=>{
+
+ for (let i = this.handlerList.length-1; i >= 0; i--)
+ {
+ let ret = this.handlerList[i][1](event);
+
+ if (!ret)
+ {
+ break;
+ }
+ }
+ });
+ }
+
+}
+
+
+var globalKeyDownManager = new KeyDownManager();
+
+export{globalKeyDownManager};
\ No newline at end of file
diff --git a/public/js/lib/OrbitControls.js b/public/js/lib/OrbitControls.js
new file mode 100644
index 0000000..904a2fd
--- /dev/null
+++ b/public/js/lib/OrbitControls.js
@@ -0,0 +1,1299 @@
+import {
+ EventDispatcher,
+ MOUSE,
+ Quaternion,
+ Spherical,
+ TOUCH,
+ Vector2,
+ Vector3
+} from './three.module.js';
+
+// This set of controls performs orbiting, dollying (zooming), and panning.
+// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
+//
+// Orbit - left mouse / touch: one-finger move
+// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
+// Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move
+
+const _changeEvent = { type: 'change' };
+const _startEvent = { type: 'start' };
+const _endEvent = { type: 'end' };
+
+class OrbitControls extends EventDispatcher {
+
+ constructor( object, domElement ) {
+
+ super();
+
+ if ( domElement === undefined ) console.warn( 'THREE.OrbitControls: The second parameter "domElement" is now mandatory.' );
+ if ( domElement === document ) console.error( 'THREE.OrbitControls: "document" should not be used as the target "domElement". Please use "renderer.domElement" instead.' );
+
+ this.object = object;
+ this.domElement = domElement;
+ this.domElement.style.touchAction = 'none'; // disable touch scroll
+
+ // Set to false to disable this control
+ this.enabled = true;
+
+ // "target" sets the location of focus, where the object orbits around
+ this.target = new Vector3();
+
+ // How far you can dolly in and out ( PerspectiveCamera only )
+ this.minDistance = 0;
+ this.maxDistance = Infinity;
+
+ // How far you can zoom in and out ( OrthographicCamera only )
+ this.minZoom = 0;
+ this.maxZoom = Infinity;
+
+ // How far you can orbit vertically, upper and lower limits.
+ // Range is 0 to Math.PI radians.
+ this.minPolarAngle = 0; // radians
+ this.maxPolarAngle = Math.PI; // radians
+
+ // How far you can orbit horizontally, upper and lower limits.
+ // If set, the interval [ min, max ] must be a sub-interval of [ - 2 PI, 2 PI ], with ( max - min < 2 PI )
+ this.minAzimuthAngle = - Infinity; // radians
+ this.maxAzimuthAngle = Infinity; // radians
+
+ // Set to true to enable damping (inertia)
+ // If damping is enabled, you must call controls.update() in your animation loop
+ this.enableDamping = false;
+ this.dampingFactor = 0.05;
+
+ // This option actually enables dollying in and out; left as "zoom" for backwards compatibility.
+ // Set to false to disable zooming
+ this.enableZoom = true;
+ this.zoomSpeed = 1.0;
+
+ // Set to false to disable rotating
+ this.enableRotate = true;
+ this.rotateSpeed = 1.0;
+
+ // Set to false to disable panning
+ this.enablePan = true;
+ this.panSpeed = 1.0;
+ this.screenSpacePanning = true; // if false, pan orthogonal to world-space direction camera.up
+ this.keyPanSpeed = 7.0; // pixels moved per arrow key push
+
+ // Set to true to automatically rotate around the target
+ // If auto-rotate is enabled, you must call controls.update() in your animation loop
+ this.autoRotate = false;
+ this.autoRotateSpeed = 2.0; // 30 seconds per orbit when fps is 60
+
+ // The four arrow keys
+ this.keys = { LEFT: 'ArrowLeft', UP: 'ArrowUp', RIGHT: 'ArrowRight', BOTTOM: 'ArrowDown' };
+
+ // Mouse buttons
+ this.mouseButtons = { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.PAN };
+
+ // Touch fingers
+ this.touches = { ONE: TOUCH.ROTATE, TWO: TOUCH.DOLLY_PAN };
+
+ // for reset
+ this.target0 = this.target.clone();
+ this.position0 = this.object.position.clone();
+ this.zoom0 = this.object.zoom;
+
+ // the target DOM element for key events
+ this._domElementKeyEvents = null;
+
+ //
+ // public methods
+ //
+
+ this.getPolarAngle = function () {
+
+ return spherical.phi;
+
+ };
+
+ this.getAzimuthalAngle = function () {
+
+ return spherical.theta;
+
+ };
+
+ this.getDistance = function () {
+
+ return this.object.position.distanceTo( this.target );
+
+ };
+
+ this.listenToKeyEvents = function ( domElement ) {
+
+ domElement.addEventListener( 'keydown', onKeyDown );
+ this._domElementKeyEvents = domElement;
+
+ };
+
+ this.saveState = function () {
+
+ scope.target0.copy( scope.target );
+ scope.position0.copy( scope.object.position );
+ scope.zoom0 = scope.object.zoom;
+
+ };
+
+ this.reset = function () {
+
+ scope.target.copy( scope.target0 );
+ scope.object.position.copy( scope.position0 );
+ scope.object.zoom = scope.zoom0;
+
+ scope.object.updateProjectionMatrix();
+ scope.dispatchEvent( _changeEvent );
+
+ scope.update();
+
+ state = STATE.NONE;
+
+ };
+
+ // this method is exposed, but perhaps it would be better if we can make it private...
+ this.update = function () {
+
+ const offset = new Vector3();
+
+ // so camera.up is the orbit axis
+ const quat = new Quaternion().setFromUnitVectors( object.up, new Vector3( 0, 1, 0 ) );
+ const quatInverse = quat.clone().invert();
+
+ const lastPosition = new Vector3();
+ const lastQuaternion = new Quaternion();
+
+ const twoPI = 2 * Math.PI;
+
+ return function update() {
+
+ const position = scope.object.position;
+
+ offset.copy( position ).sub( scope.target );
+
+ // rotate offset to "y-axis-is-up" space
+ offset.applyQuaternion( quat );
+
+ // angle from z-axis around y-axis
+ spherical.setFromVector3( offset );
+
+ if ( scope.autoRotate && state === STATE.NONE ) {
+
+ rotateLeft( getAutoRotationAngle() );
+
+ }
+
+ if ( scope.enableDamping ) {
+
+ spherical.theta += sphericalDelta.theta * scope.dampingFactor;
+ spherical.phi += sphericalDelta.phi * scope.dampingFactor;
+
+ } else {
+
+ spherical.theta += sphericalDelta.theta;
+ spherical.phi += sphericalDelta.phi;
+
+ }
+
+ // restrict theta to be between desired limits
+
+ let min = scope.minAzimuthAngle;
+ let max = scope.maxAzimuthAngle;
+
+ if ( isFinite( min ) && isFinite( max ) ) {
+
+ if ( min < - Math.PI ) min += twoPI; else if ( min > Math.PI ) min -= twoPI;
+
+ if ( max < - Math.PI ) max += twoPI; else if ( max > Math.PI ) max -= twoPI;
+
+ if ( min <= max ) {
+
+ spherical.theta = Math.max( min, Math.min( max, spherical.theta ) );
+
+ } else {
+
+ spherical.theta = ( spherical.theta > ( min + max ) / 2 ) ?
+ Math.max( min, spherical.theta ) :
+ Math.min( max, spherical.theta );
+
+ }
+
+ }
+
+ // restrict phi to be between desired limits
+ spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) );
+
+ spherical.makeSafe();
+
+
+ spherical.radius *= scale;
+
+ // restrict radius to be between desired limits
+ spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) );
+
+ // move target to panned location
+
+ if ( scope.enableDamping === true ) {
+
+ scope.target.addScaledVector( panOffset, scope.dampingFactor );
+
+ } else {
+
+ scope.target.add( panOffset );
+
+ }
+
+ offset.setFromSpherical( spherical );
+
+ // rotate offset back to "camera-up-vector-is-up" space
+ offset.applyQuaternion( quatInverse );
+
+ position.copy( scope.target ).add( offset );
+
+ scope.object.lookAt( scope.target );
+
+ if ( scope.enableDamping === true ) {
+
+ sphericalDelta.theta *= ( 1 - scope.dampingFactor );
+ sphericalDelta.phi *= ( 1 - scope.dampingFactor );
+
+ panOffset.multiplyScalar( 1 - scope.dampingFactor );
+
+ } else {
+
+ sphericalDelta.set( 0, 0, 0 );
+
+ panOffset.set( 0, 0, 0 );
+
+ }
+
+ scale = 1;
+
+ // update condition is:
+ // min(camera displacement, camera rotation in radians)^2 > EPS
+ // using small-angle approximation cos(x/2) = 1 - x^2 / 8
+
+ if ( zoomChanged ||
+ lastPosition.distanceToSquared( scope.object.position ) > EPS ||
+ 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) {
+
+ scope.dispatchEvent( _changeEvent );
+
+ lastPosition.copy( scope.object.position );
+ lastQuaternion.copy( scope.object.quaternion );
+ zoomChanged = false;
+
+ return true;
+
+ }
+
+ return false;
+
+ };
+
+ }();
+
+ this.dispose = function () {
+
+ scope.domElement.removeEventListener( 'contextmenu', onContextMenu );
+
+ scope.domElement.removeEventListener( 'pointerdown', onPointerDown );
+ scope.domElement.removeEventListener( 'pointercancel', onPointerCancel );
+ scope.domElement.removeEventListener( 'wheel', onMouseWheel );
+
+ scope.domElement.removeEventListener( 'pointermove', onPointerMove );
+ scope.domElement.removeEventListener( 'pointerup', onPointerUp );
+
+
+ if ( scope._domElementKeyEvents !== null ) {
+
+ scope._domElementKeyEvents.removeEventListener( 'keydown', onKeyDown );
+
+ }
+
+ //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here?
+
+ };
+
+ //
+ // internals
+ //
+
+ const scope = this;
+
+ const STATE = {
+ NONE: - 1,
+ ROTATE: 0,
+ DOLLY: 1,
+ PAN: 2,
+ TOUCH_ROTATE: 3,
+ TOUCH_PAN: 4,
+ TOUCH_DOLLY_PAN: 5,
+ TOUCH_DOLLY_ROTATE: 6
+ };
+
+ let state = STATE.NONE;
+
+ const EPS = 0.000001;
+
+ // current position in spherical coordinates
+ const spherical = new Spherical();
+ const sphericalDelta = new Spherical();
+
+ let scale = 1;
+ const panOffset = new Vector3();
+ let zoomChanged = false;
+
+ const rotateStart = new Vector2();
+ const rotateEnd = new Vector2();
+ const rotateDelta = new Vector2();
+
+ const panStart = new Vector2();
+ const panEnd = new Vector2();
+ const panDelta = new Vector2();
+
+ const dollyStart = new Vector2();
+ const dollyEnd = new Vector2();
+ const dollyDelta = new Vector2();
+
+ const pointers = [];
+ const pointerPositions = {};
+
+ function getAutoRotationAngle() {
+
+ return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
+
+ }
+
+ function getZoomScale() {
+
+ return Math.pow( 0.95, scope.zoomSpeed );
+
+ }
+
+ function rotateLeft( angle ) {
+
+ sphericalDelta.theta -= angle;
+
+ }
+
+ function rotateUp( angle ) {
+
+ sphericalDelta.phi -= angle;
+
+ }
+
+ const panLeft = function () {
+
+ const v = new Vector3();
+
+ return function panLeft( distance, objectMatrix ) {
+
+ v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix
+ v.multiplyScalar( - distance );
+
+ panOffset.add( v );
+
+ };
+
+ }();
+
+ const panUp = function () {
+
+ const v = new Vector3();
+
+ return function panUp( distance, objectMatrix ) {
+
+ if ( scope.screenSpacePanning === true ) {
+
+ v.setFromMatrixColumn( objectMatrix, 1 );
+
+ } else {
+
+ v.setFromMatrixColumn( objectMatrix, 0 );
+ v.crossVectors( scope.object.up, v );
+
+ }
+
+ v.multiplyScalar( distance );
+
+ panOffset.add( v );
+
+ };
+
+ }();
+
+ // deltaX and deltaY are in pixels; right and down are positive
+ const pan = function () {
+
+ const offset = new Vector3();
+
+ return function pan( deltaX, deltaY ) {
+
+ const element = scope.domElement;
+
+ if ( scope.object.isPerspectiveCamera ) {
+
+ // perspective
+ const position = scope.object.position;
+ offset.copy( position ).sub( scope.target );
+ let targetDistance = offset.length();
+
+ // half of the fov is center to top of screen
+ targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 );
+
+ // we use only clientHeight here so aspect ratio does not distort speed
+ panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix );
+ panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix );
+
+ } else if ( scope.object.isOrthographicCamera ) {
+
+ // orthographic
+ panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix );
+ panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix );
+
+ } else {
+
+ // camera neither orthographic nor perspective
+ console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
+ scope.enablePan = false;
+
+ }
+
+ };
+
+ }();
+
+ function dollyOut( dollyScale ) {
+
+ if ( scope.object.isPerspectiveCamera ) {
+
+ scale /= dollyScale;
+
+ } else if ( scope.object.isOrthographicCamera ) {
+
+ scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) );
+ scope.object.updateProjectionMatrix();
+ zoomChanged = true;
+
+ } else {
+
+ console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
+ scope.enableZoom = false;
+
+ }
+
+ }
+
+ function dollyIn( dollyScale ) {
+
+ if ( scope.object.isPerspectiveCamera ) {
+
+ scale *= dollyScale;
+
+ } else if ( scope.object.isOrthographicCamera ) {
+
+ scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) );
+ scope.object.updateProjectionMatrix();
+ zoomChanged = true;
+
+ } else {
+
+ console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
+ scope.enableZoom = false;
+
+ }
+
+ }
+
+ //
+ // event callbacks - update the object state
+ //
+
+ function handleMouseDownRotate( event ) {
+
+ rotateStart.set( event.clientX, event.clientY );
+
+ }
+
+ function handleMouseDownDolly( event ) {
+
+ dollyStart.set( event.clientX, event.clientY );
+
+ }
+
+ function handleMouseDownPan( event ) {
+
+ panStart.set( event.clientX, event.clientY );
+
+ }
+
+ function handleMouseMoveRotate( event ) {
+
+ rotateEnd.set( event.clientX, event.clientY );
+
+ rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
+
+ const element = scope.domElement;
+
+ rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
+
+ rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
+
+ rotateStart.copy( rotateEnd );
+
+ scope.update();
+
+ }
+
+ function handleMouseMoveDolly( event ) {
+
+ dollyEnd.set( event.clientX, event.clientY );
+
+ dollyDelta.subVectors( dollyEnd, dollyStart );
+
+ if ( dollyDelta.y > 0 ) {
+
+ dollyOut( getZoomScale() );
+
+ } else if ( dollyDelta.y < 0 ) {
+
+ dollyIn( getZoomScale() );
+
+ }
+
+ dollyStart.copy( dollyEnd );
+
+ scope.update();
+
+ }
+
+ function handleMouseMovePan( event ) {
+
+ panEnd.set( event.clientX, event.clientY );
+
+ panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
+
+ pan( panDelta.x, panDelta.y );
+
+ panStart.copy( panEnd );
+
+ scope.update();
+
+ }
+
+ function handleMouseUp( /*event*/ ) {
+
+ // no-op
+
+ }
+
+ function handleMouseWheel( event ) {
+
+ if ( event.deltaY < 0 ) {
+
+ dollyIn( getZoomScale() );
+
+ } else if ( event.deltaY > 0 ) {
+
+ dollyOut( getZoomScale() );
+
+ }
+
+ scope.update();
+
+ }
+
+ function handleKeyDown( event ) {
+
+ let needsUpdate = false;
+
+ switch ( event.code ) {
+
+ case scope.keys.UP:
+ pan( 0, scope.keyPanSpeed );
+ needsUpdate = true;
+ break;
+
+ case scope.keys.BOTTOM:
+ pan( 0, - scope.keyPanSpeed );
+ needsUpdate = true;
+ break;
+
+ case scope.keys.LEFT:
+ pan( scope.keyPanSpeed, 0 );
+ needsUpdate = true;
+ break;
+
+ case scope.keys.RIGHT:
+ pan( - scope.keyPanSpeed, 0 );
+ needsUpdate = true;
+ break;
+
+ }
+
+ if ( needsUpdate ) {
+
+ // prevent the browser from scrolling on cursor keys
+ event.preventDefault();
+
+ scope.update();
+
+ }
+
+
+ }
+
+ function handleTouchStartRotate() {
+
+ if ( pointers.length === 1 ) {
+
+ rotateStart.set( pointers[ 0 ].pageX, pointers[ 0 ].pageY );
+
+ } else {
+
+ const x = 0.5 * ( pointers[ 0 ].pageX + pointers[ 1 ].pageX );
+ const y = 0.5 * ( pointers[ 0 ].pageY + pointers[ 1 ].pageY );
+
+ rotateStart.set( x, y );
+
+ }
+
+ }
+
+ function handleTouchStartPan() {
+
+ if ( pointers.length === 1 ) {
+
+ panStart.set( pointers[ 0 ].pageX, pointers[ 0 ].pageY );
+
+ } else {
+
+ const x = 0.5 * ( pointers[ 0 ].pageX + pointers[ 1 ].pageX );
+ const y = 0.5 * ( pointers[ 0 ].pageY + pointers[ 1 ].pageY );
+
+ panStart.set( x, y );
+
+ }
+
+ }
+
+ function handleTouchStartDolly() {
+
+ const dx = pointers[ 0 ].pageX - pointers[ 1 ].pageX;
+ const dy = pointers[ 0 ].pageY - pointers[ 1 ].pageY;
+
+ const distance = Math.sqrt( dx * dx + dy * dy );
+
+ dollyStart.set( 0, distance );
+
+ }
+
+ function handleTouchStartDollyPan() {
+
+ if ( scope.enableZoom ) handleTouchStartDolly();
+
+ if ( scope.enablePan ) handleTouchStartPan();
+
+ }
+
+ function handleTouchStartDollyRotate() {
+
+ if ( scope.enableZoom ) handleTouchStartDolly();
+
+ if ( scope.enableRotate ) handleTouchStartRotate();
+
+ }
+
+ function handleTouchMoveRotate( event ) {
+
+ if ( pointers.length == 1 ) {
+
+ rotateEnd.set( event.pageX, event.pageY );
+
+ } else {
+
+ const position = getSecondPointerPosition( event );
+
+ const x = 0.5 * ( event.pageX + position.x );
+ const y = 0.5 * ( event.pageY + position.y );
+
+ rotateEnd.set( x, y );
+
+ }
+
+ rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
+
+ const element = scope.domElement;
+
+ rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
+
+ rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
+
+ rotateStart.copy( rotateEnd );
+
+ }
+
+ function handleTouchMovePan( event ) {
+
+ if ( pointers.length === 1 ) {
+
+ panEnd.set( event.pageX, event.pageY );
+
+ } else {
+
+ const position = getSecondPointerPosition( event );
+
+ const x = 0.5 * ( event.pageX + position.x );
+ const y = 0.5 * ( event.pageY + position.y );
+
+ panEnd.set( x, y );
+
+ }
+
+ panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
+
+ pan( panDelta.x, panDelta.y );
+
+ panStart.copy( panEnd );
+
+ }
+
+ function handleTouchMoveDolly( event ) {
+
+ const position = getSecondPointerPosition( event );
+
+ const dx = event.pageX - position.x;
+ const dy = event.pageY - position.y;
+
+ const distance = Math.sqrt( dx * dx + dy * dy );
+
+ dollyEnd.set( 0, distance );
+
+ dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) );
+
+ dollyOut( dollyDelta.y );
+
+ dollyStart.copy( dollyEnd );
+
+ }
+
+ function handleTouchMoveDollyPan( event ) {
+
+ if ( scope.enableZoom ) handleTouchMoveDolly( event );
+
+ if ( scope.enablePan ) handleTouchMovePan( event );
+
+ }
+
+ function handleTouchMoveDollyRotate( event ) {
+
+ if ( scope.enableZoom ) handleTouchMoveDolly( event );
+
+ if ( scope.enableRotate ) handleTouchMoveRotate( event );
+
+ }
+
+ function handleTouchEnd( /*event*/ ) {
+
+ // no-op
+
+ }
+
+ //
+ // event handlers - FSM: listen for events and reset state
+ //
+
+ function onPointerDown( event ) {
+
+ if ( scope.enabled === false ) return;
+
+ if ( pointers.length === 0 ) {
+
+ scope.domElement.setPointerCapture( event.pointerId );
+
+ scope.domElement.addEventListener( 'pointermove', onPointerMove );
+ scope.domElement.addEventListener( 'pointerup', onPointerUp );
+
+ }
+
+ //
+
+ addPointer( event );
+
+ if ( event.pointerType === 'touch' ) {
+
+ onTouchStart( event );
+
+ } else {
+
+ onMouseDown( event );
+
+ }
+
+ }
+
+ function onPointerMove( event ) {
+
+ if ( scope.enabled === false ) return;
+
+ if ( event.pointerType === 'touch' ) {
+
+ onTouchMove( event );
+
+ } else {
+
+ onMouseMove( event );
+
+ }
+
+ }
+
+ function onPointerUp( event ) {
+
+ if ( scope.enabled === false ) return;
+
+ if ( event.pointerType === 'touch' ) {
+
+ onTouchEnd();
+
+ } else {
+
+ onMouseUp( event );
+
+ }
+
+ removePointer( event );
+
+ //
+
+ if ( pointers.length === 0 ) {
+
+ scope.domElement.releasePointerCapture( event.pointerId );
+
+ scope.domElement.removeEventListener( 'pointermove', onPointerMove );
+ scope.domElement.removeEventListener( 'pointerup', onPointerUp );
+
+ }
+
+ }
+
+ function onPointerCancel( event ) {
+
+ removePointer( event );
+
+ }
+
+ function onMouseDown( event ) {
+
+
+ // lie, redefined key usage.
+ if ( event.ctrlKey || event.metaKey || event.shiftKey )
+ return;
+
+ let mouseAction;
+
+ switch ( event.button ) {
+
+ case 0:
+
+ mouseAction = scope.mouseButtons.LEFT;
+ break;
+
+ case 1:
+
+ mouseAction = scope.mouseButtons.MIDDLE;
+ break;
+
+ case 2:
+
+ mouseAction = scope.mouseButtons.RIGHT;
+ break;
+
+ default:
+
+ mouseAction = - 1;
+
+ }
+
+ switch ( mouseAction ) {
+
+ case MOUSE.DOLLY:
+
+ if ( scope.enableZoom === false ) return;
+
+ handleMouseDownDolly( event );
+
+ state = STATE.DOLLY;
+
+ break;
+
+ case MOUSE.ROTATE:
+
+ if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
+
+ if ( scope.enablePan === false ) return;
+
+ handleMouseDownPan( event );
+
+ state = STATE.PAN;
+
+ } else {
+
+ if ( scope.enableRotate === false ) return;
+
+ handleMouseDownRotate( event );
+
+ state = STATE.ROTATE;
+
+ }
+
+ break;
+
+ case MOUSE.PAN:
+
+ if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
+
+ if ( scope.enableRotate === false ) return;
+
+ handleMouseDownRotate( event );
+
+ state = STATE.ROTATE;
+
+ } else {
+
+ if ( scope.enablePan === false ) return;
+
+ handleMouseDownPan( event );
+
+ state = STATE.PAN;
+
+ }
+
+ break;
+
+ default:
+
+ state = STATE.NONE;
+
+ }
+
+ if ( state !== STATE.NONE ) {
+
+ scope.dispatchEvent( _startEvent );
+
+ }
+
+ }
+
+ function onMouseMove( event ) {
+
+ if ( scope.enabled === false ) return;
+
+ switch ( state ) {
+
+ case STATE.ROTATE:
+
+ if ( scope.enableRotate === false ) return;
+
+ handleMouseMoveRotate( event );
+
+ break;
+
+ case STATE.DOLLY:
+
+ if ( scope.enableZoom === false ) return;
+
+ handleMouseMoveDolly( event );
+
+ break;
+
+ case STATE.PAN:
+
+ if ( scope.enablePan === false ) return;
+
+ handleMouseMovePan( event );
+
+ break;
+
+ }
+
+ }
+
+ function onMouseUp( event ) {
+
+ handleMouseUp( event );
+
+ scope.dispatchEvent( _endEvent );
+
+ state = STATE.NONE;
+
+ }
+
+ function onMouseWheel( event ) {
+
+ if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return;
+
+ event.preventDefault();
+
+ scope.dispatchEvent( _startEvent );
+
+ handleMouseWheel( event );
+
+ scope.dispatchEvent( _endEvent );
+
+ }
+
+ function onKeyDown( event ) {
+
+ if ( scope.enabled === false || scope.enablePan === false ) return;
+
+ handleKeyDown( event );
+
+ }
+
+ function onTouchStart( event ) {
+
+ trackPointer( event );
+
+ switch ( pointers.length ) {
+
+ case 1:
+
+ switch ( scope.touches.ONE ) {
+
+ case TOUCH.ROTATE:
+
+ if ( scope.enableRotate === false ) return;
+
+ handleTouchStartRotate();
+
+ state = STATE.TOUCH_ROTATE;
+
+ break;
+
+ case TOUCH.PAN:
+
+ if ( scope.enablePan === false ) return;
+
+ handleTouchStartPan();
+
+ state = STATE.TOUCH_PAN;
+
+ break;
+
+ default:
+
+ state = STATE.NONE;
+
+ }
+
+ break;
+
+ case 2:
+
+ switch ( scope.touches.TWO ) {
+
+ case TOUCH.DOLLY_PAN:
+
+ if ( scope.enableZoom === false && scope.enablePan === false ) return;
+
+ handleTouchStartDollyPan();
+
+ state = STATE.TOUCH_DOLLY_PAN;
+
+ break;
+
+ case TOUCH.DOLLY_ROTATE:
+
+ if ( scope.enableZoom === false && scope.enableRotate === false ) return;
+
+ handleTouchStartDollyRotate();
+
+ state = STATE.TOUCH_DOLLY_ROTATE;
+
+ break;
+
+ default:
+
+ state = STATE.NONE;
+
+ }
+
+ break;
+
+ default:
+
+ state = STATE.NONE;
+
+ }
+
+ if ( state !== STATE.NONE ) {
+
+ scope.dispatchEvent( _startEvent );
+
+ }
+
+ }
+
+ function onTouchMove( event ) {
+
+ trackPointer( event );
+
+ switch ( state ) {
+
+ case STATE.TOUCH_ROTATE:
+
+ if ( scope.enableRotate === false ) return;
+
+ handleTouchMoveRotate( event );
+
+ scope.update();
+
+ break;
+
+ case STATE.TOUCH_PAN:
+
+ if ( scope.enablePan === false ) return;
+
+ handleTouchMovePan( event );
+
+ scope.update();
+
+ break;
+
+ case STATE.TOUCH_DOLLY_PAN:
+
+ if ( scope.enableZoom === false && scope.enablePan === false ) return;
+
+ handleTouchMoveDollyPan( event );
+
+ scope.update();
+
+ break;
+
+ case STATE.TOUCH_DOLLY_ROTATE:
+
+ if ( scope.enableZoom === false && scope.enableRotate === false ) return;
+
+ handleTouchMoveDollyRotate( event );
+
+ scope.update();
+
+ break;
+
+ default:
+
+ state = STATE.NONE;
+
+ }
+
+ }
+
+ function onTouchEnd( event ) {
+
+ handleTouchEnd( event );
+
+ scope.dispatchEvent( _endEvent );
+
+ state = STATE.NONE;
+
+ }
+
+ function onContextMenu( event ) {
+
+ if ( scope.enabled === false ) return;
+
+ event.preventDefault();
+
+ }
+
+ function addPointer( event ) {
+
+ pointers.push( event );
+
+ }
+
+ function removePointer( event ) {
+
+ delete pointerPositions[ event.pointerId ];
+
+ for ( let i = 0; i < pointers.length; i ++ ) {
+
+ if ( pointers[ i ].pointerId == event.pointerId ) {
+
+ pointers.splice( i, 1 );
+ return;
+
+ }
+
+ }
+
+ }
+
+ function trackPointer( event ) {
+
+ let position = pointerPositions[ event.pointerId ];
+
+ if ( position === undefined ) {
+
+ position = new Vector2();
+ pointerPositions[ event.pointerId ] = position;
+
+ }
+
+ position.set( event.pageX, event.pageY );
+
+ }
+
+ function getSecondPointerPosition( event ) {
+
+ const pointer = ( event.pointerId === pointers[ 0 ].pointerId ) ? pointers[ 1 ] : pointers[ 0 ];
+
+ return pointerPositions[ pointer.pointerId ];
+
+ }
+
+ //
+
+ scope.domElement.addEventListener( 'contextmenu', onContextMenu );
+
+ scope.domElement.addEventListener( 'pointerdown', onPointerDown );
+ scope.domElement.addEventListener( 'pointercancel', onPointerCancel );
+ scope.domElement.addEventListener( 'wheel', onMouseWheel, { passive: false } );
+
+ // force an update at start
+
+ this.update();
+
+ }
+
+}
+
+
+// This set of controls performs orbiting, dollying (zooming), and panning.
+// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
+// This is very similar to OrbitControls, another set of touch behavior
+//
+// Orbit - right mouse, or left mouse + ctrl/meta/shiftKey / touch: two-finger rotate
+// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
+// Pan - left mouse, or arrow keys / touch: one-finger move
+
+class MapControls extends OrbitControls {
+
+ constructor( object, domElement ) {
+
+ super( object, domElement );
+
+ this.screenSpacePanning = false; // pan orthogonal to world-space direction camera.up
+
+ this.mouseButtons.LEFT = MOUSE.PAN;
+ this.mouseButtons.RIGHT = MOUSE.ROTATE;
+
+ this.touches.ONE = TOUCH.PAN;
+ this.touches.TWO = TOUCH.DOLLY_ROTATE;
+
+ }
+
+}
+
+export { OrbitControls, MapControls };
diff --git a/public/js/lib/PCDLoader.js b/public/js/lib/PCDLoader.js
new file mode 100644
index 0000000..b84f1b8
--- /dev/null
+++ b/public/js/lib/PCDLoader.js
@@ -0,0 +1,534 @@
+/**
+ * @author Filipe Caixeta / http://filipecaixeta.com.br
+ * @author Mugen87 / https://github.com/Mugen87
+ *
+ * Description: A THREE loader for PCD ascii and binary files.
+ *
+ * Limitations: Compressed binary files are not supported.
+ *
+ */
+
+ import {
+ DefaultLoadingManager,
+ FileLoader,
+ LoaderUtils,
+} from "./three.module.js";
+
+
+var PCDLoader = function ( manager ) {
+
+ this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;
+ this.littleEndian = true;
+
+};
+
+function decompressLZF(inData, outLength) {
+ var inLength = inData.length
+ var outData = new Uint8Array(outLength)
+ var inPtr = 0
+ var outPtr = 0
+ var ctrl
+ var len
+ var ref
+ do {
+ ctrl = inData[inPtr++]
+ if (ctrl < 1 << 5) {
+ ctrl++
+ if (outPtr + ctrl > outLength) throw new Error('Output buffer is not large enough')
+ if (inPtr + ctrl > inLength) throw new Error('Invalid compressed data')
+ do {
+ outData[outPtr++] = inData[inPtr++]
+ } while (--ctrl)
+ } else {
+ len = ctrl >> 5
+ ref = outPtr - ((ctrl & 0x1f) << 8) - 1
+ if (inPtr >= inLength) throw new Error('Invalid compressed data')
+ if (len === 7) {
+ len += inData[inPtr++]
+ if (inPtr >= inLength) throw new Error('Invalid compressed data')
+ }
+
+ ref -= inData[inPtr++]
+ if (outPtr + len + 2 > outLength) throw new Error('Output buffer is not large enough')
+ if (ref < 0) throw new Error('Invalid compressed data')
+ if (ref >= outPtr) throw new Error('Invalid compressed data')
+ do {
+ outData[outPtr++] = outData[ref++]
+ } while (--len + 2)
+ }
+ } while (inPtr < inLength)
+
+ return outData
+ }
+
+
+PCDLoader.prototype = {
+
+ constructor: PCDLoader,
+
+ load: function ( url, onLoad, onProgress, onError, onFileLoaded ) {
+
+ var scope = this;
+
+ var loader = new FileLoader( scope.manager );
+ loader.setPath( scope.path );
+ loader.setResponseType( 'arraybuffer' );
+ loader.load( url, function ( data ) {
+
+ try {
+ if (onFileLoaded)
+ onFileLoaded();
+ onLoad( scope.parse( data, url) );
+ } catch ( e ) {
+
+ if ( onError ) {
+
+ onError( e );
+
+ } else {
+
+ throw e;
+
+ }
+
+ }
+
+ }, onProgress, onError );
+
+ },
+
+ setPath: function ( value ) {
+
+ this.path = value;
+ return this;
+
+ },
+
+ parse: function(data, url){
+ var addr = url.split(".");
+ var file_ext = addr[addr.length-1];
+
+ if (file_ext === "pcd")
+ return this.parsePcd(data, url);
+ else {
+ console.log("load", file_ext, "file");
+ return this.parseBin(data, url);
+ }
+
+ },
+
+ parseBin: function(data, url){
+ var dataview = new DataView( data, 0);
+
+ var position = [];
+ var normal = [];
+ var color = [];
+ var intensity = [];
+ //kitti format, xyzi
+ var offset = 0;
+
+ for ( var row = 0; row < data.byteLength/(4*4); row += 1 ) {
+ position.push( dataview.getFloat32( row*16 + 0, this.littleEndian ) );
+ position.push( dataview.getFloat32( row*16 + 4, this.littleEndian ) );
+ position.push( dataview.getFloat32( row*16 + 8, this.littleEndian ) );
+ intensity.push(dataview.getFloat32( row*16 + 12, this.littleEndian ) )
+ }
+
+ return {
+ position: position,
+ color: color,
+ normal: normal,
+ intensity: intensity,
+ };
+ },
+
+ parsePcd: function ( data, url) {
+
+ function parseHeader( data ) {
+
+ var PCDheader = {};
+ var result1 = data.search( /[\r\n]DATA\s(\S*)\s/i );
+ var result2 = /[\r\n]DATA\s(\S*)\s/i.exec( data.substr( result1 - 1 ) );
+
+ PCDheader.data = result2[ 1 ];
+ PCDheader.headerLen = result2[ 0 ].length + result1;
+ PCDheader.str = data.substr( 0, PCDheader.headerLen );
+
+ // remove comments
+
+ PCDheader.str = PCDheader.str.replace( /\#.*/gi, '' );
+
+ // parse
+
+ PCDheader.version = /VERSION (.*)/i.exec( PCDheader.str );
+ PCDheader.fields = /FIELDS (.*)/i.exec( PCDheader.str );
+ PCDheader.size = /SIZE (.*)/i.exec( PCDheader.str );
+ PCDheader.type = /TYPE (.*)/i.exec( PCDheader.str );
+ PCDheader.count = /COUNT (.*)/i.exec( PCDheader.str );
+ PCDheader.width = /WIDTH (.*)/i.exec( PCDheader.str );
+ PCDheader.height = /HEIGHT (.*)/i.exec( PCDheader.str );
+ PCDheader.viewpoint = /VIEWPOINT (.*)/i.exec( PCDheader.str );
+ PCDheader.points = /POINTS (.*)/i.exec( PCDheader.str );
+
+ // evaluate
+
+ if ( PCDheader.version !== null )
+ PCDheader.version = parseFloat( PCDheader.version[ 1 ] );
+
+ if ( PCDheader.fields !== null )
+ PCDheader.fields = PCDheader.fields[ 1 ].split( ' ' );
+
+ if ( PCDheader.type !== null )
+ PCDheader.type = PCDheader.type[ 1 ].split( ' ' );
+
+ if ( PCDheader.width !== null )
+ PCDheader.width = parseInt( PCDheader.width[ 1 ] );
+
+ if ( PCDheader.height !== null )
+ PCDheader.height = parseInt( PCDheader.height[ 1 ] );
+
+ if ( PCDheader.viewpoint !== null )
+ PCDheader.viewpoint = PCDheader.viewpoint[ 1 ];
+
+ if ( PCDheader.points !== null )
+ PCDheader.points = parseInt( PCDheader.points[ 1 ], 10 );
+
+ if ( PCDheader.points === null )
+ PCDheader.points = PCDheader.width * PCDheader.height;
+
+ if ( PCDheader.size !== null ) {
+
+ PCDheader.size = PCDheader.size[ 1 ].split( ' ' ).map( function ( x ) {
+
+ return parseInt( x, 10 );
+
+ } );
+
+ }
+
+ if ( PCDheader.count !== null ) {
+
+ PCDheader.count = PCDheader.count[ 1 ].split( ' ' ).map( function ( x ) {
+
+ return parseInt( x, 10 );
+
+ } );
+
+ } else {
+
+ PCDheader.count = [];
+
+ for ( var i = 0, l = PCDheader.fields.length; i < l; i ++ ) {
+
+ PCDheader.count.push( 1 );
+
+ }
+
+ }
+
+ PCDheader.offset = {};
+
+ var sizeSum = 0;
+
+ for ( var i = 0, l = PCDheader.fields.length; i < l; i ++ ) {
+
+ if ( PCDheader.data === 'ascii' || PCDheader.data === 'ascill') {
+
+ PCDheader.offset[ PCDheader.fields[ i ] ] = i;
+
+ } else {
+
+ PCDheader.offset[ PCDheader.fields[ i ] ] = sizeSum;
+ sizeSum += PCDheader.size[ i ] * PCDheader.count[ i ];
+
+ }
+
+ }
+
+ // for binary only
+
+ PCDheader.rowSize = sizeSum;
+
+ return PCDheader;
+
+ }
+
+ var textData = LoaderUtils.decodeText( new Uint8Array( data ) );
+
+ // parse header (always ascii format)
+
+ var PCDheader = parseHeader( textData );
+
+ // parse data
+
+ var position = [];
+ var normal = [];
+ var color = [];
+ var velocity = [];
+ var intensity = [];
+
+ // ascii
+
+ function filterPoint(x,y,z)
+ {
+ if (isNaN(x))
+ return true;
+ if (x == 0 && y== 0 && z==0)
+ return true;
+ // if (z >=2)
+ // return true;
+ }
+
+
+ if ( PCDheader.data === 'ascii' || PCDheader.data === 'ascill') {
+
+ var offset = PCDheader.offset;
+ var pcdData = textData.substr( PCDheader.headerLen );
+ var lines = pcdData.split( '\n' );
+
+ var intensity_index = PCDheader.fields.findIndex(n=>n==="intensity");
+ var intensity_type = "F";
+ var intensity_size = 4;
+
+ if (intensity_index >= 0){
+ intensity_type = PCDheader.type[intensity_index];
+ intensity_size = PCDheader.size[intensity_index];
+ }
+
+ for ( var i = 0, l = lines.length; i < l; i ++ ) {
+
+ if ( lines[ i ] === '' ) continue;
+
+ var line = lines[ i ].split( ' ' );
+
+ if ( offset.x !== undefined ) {
+ var x,y,z;
+ x = parseFloat( line[ offset.x ] );
+ y = parseFloat( line[ offset.y ] );
+ z = parseFloat( line[ offset.z ] );
+
+ if (filterPoint(x,y,z)){
+ continue;
+ }
+
+ position.push( x );
+ position.push( y );
+ position.push( z );
+
+ }
+
+ if ( offset.rgb !== undefined ) {
+
+ var rgb = parseFloat( line[ offset.rgb ] );
+ var r = ( rgb >> 16 ) & 0x0000ff;
+ var g = ( rgb >> 8 ) & 0x0000ff;
+ var b = ( rgb >> 0 ) & 0x0000ff;
+ color.push( r / 255, g / 255, b / 255 );
+
+ }
+
+ if ( offset.normal_x !== undefined ) {
+
+ normal.push( parseFloat( line[ offset.normal_x ] ) );
+ normal.push( parseFloat( line[ offset.normal_y ] ) );
+ normal.push( parseFloat( line[ offset.normal_z ] ) );
+
+ }
+
+ if ( offset.vx !== undefined ) {
+ var vx,vy;
+ vx = parseFloat( line[ offset.vx ] );
+ vy = parseFloat( line[ offset.vy ] );
+
+ velocity.push(vx);
+ velocity.push(vy);
+ velocity.push(0);
+ }
+
+
+ if (offset.intensity !== undefined) {
+ intensity.push( parseInt( line[ offset.intensity ] ));
+ }
+
+ }
+
+ }
+
+ // binary
+
+ if ( PCDheader.data === 'binary_compressed' ) {
+
+ var sizes = new Uint32Array( data.slice( PCDheader.headerLen, PCDheader.headerLen + 8 ) );
+ var compressedSize = sizes[ 0 ];
+ var decompressedSize = sizes[ 1 ];
+ var decompressed = decompressLZF( new Uint8Array( data, PCDheader.headerLen + 8, compressedSize ), decompressedSize );
+ var dataview = new DataView( decompressed.buffer );
+
+ var offset = PCDheader.offset;
+ var intensity_index = PCDheader.fields.findIndex(n=>n==="intensity");
+ var intensity_type = "F";
+ var intensity_size = 4;
+
+ if (intensity_index >= 0){
+ intensity_type = PCDheader.type[intensity_index];
+ intensity_size = PCDheader.size[intensity_index];
+ }
+
+ let size = {};
+
+ PCDheader.fields.forEach((n,i)=>size[n]=PCDheader.size[i])
+
+
+ for ( var i = 0; i < PCDheader.points; i ++ ) {
+
+ if ( offset.x !== undefined ) {
+
+ if (size.x==8)
+ {
+ position.push( dataview.getFloat64( ( PCDheader.points * offset.x ) + size.x * i, this.littleEndian ) );
+ position.push( dataview.getFloat64( ( PCDheader.points * offset.y ) + size.y * i, this.littleEndian ) );
+ position.push( dataview.getFloat64( ( PCDheader.points * offset.z ) + size.z * i, this.littleEndian ) );
+ }
+ else
+ {
+ position.push( dataview.getFloat32( ( PCDheader.points * offset.x ) + size.x * i, this.littleEndian ) );
+ position.push( dataview.getFloat32( ( PCDheader.points * offset.y ) + size.y * i, this.littleEndian ) );
+ position.push( dataview.getFloat32( ( PCDheader.points * offset.z ) + size.z * i, this.littleEndian ) );
+ }
+
+ }
+
+ if ( offset.vx !== undefined ) {
+
+ if (size.vx==8)
+ {
+ velocity.push( dataview.getFloat64( ( PCDheader.points * offset.vx ) + size.vx * i, this.littleEndian ) );
+ velocity.push( dataview.getFloat64( ( PCDheader.points * offset.vy ) + size.vy * i, this.littleEndian ) );
+ velocity.push( 0 );
+ }
+ else
+ {
+ velocity.push( dataview.getFloat32( ( PCDheader.points * offset.vx ) + size.vx * i, this.littleEndian ) );
+ velocity.push( dataview.getFloat32( ( PCDheader.points * offset.vy ) + size.vy * i, this.littleEndian ) );
+ velocity.push( 0 );
+ }
+
+ }
+
+ // if ( offset.rgb !== undefined ) {
+
+ // color.push( dataview.getUint8( ( PCDheader.points * ( offset.rgb + 2 ) ) + PCDheader.size[ 3 ] * i ) / 255.0 );
+ // color.push( dataview.getUint8( ( PCDheader.points * ( offset.rgb + 1 ) ) + PCDheader.size[ 3 ] * i ) / 255.0 );
+ // color.push( dataview.getUint8( ( PCDheader.points * ( offset.rgb + 0 ) ) + PCDheader.size[ 3 ] * i ) / 255.0 );
+
+ // }
+
+ // if ( offset.normal_x !== undefined ) {
+
+ // normal.push( dataview.getFloat32( ( PCDheader.points * offset.normal_x ) + PCDheader.size[ 4 ] * i, this.littleEndian ) );
+ // normal.push( dataview.getFloat32( ( PCDheader.points * offset.normal_y ) + PCDheader.size[ 5 ] * i, this.littleEndian ) );
+ // normal.push( dataview.getFloat32( ( PCDheader.points * offset.normal_z ) + PCDheader.size[ 6 ] * i, this.littleEndian ) );
+
+ // }
+
+ if (offset.intensity !== undefined) {
+ if (intensity_type == "U" && intensity_size == 1){
+ intensity.push( dataview.getUint8(PCDheader.points * offset.intensity + size.intensity*i));
+ }
+ else if (intensity_type == "F" && intensity_size == 4){
+ intensity.push( dataview.getFloat32(PCDheader.points * offset.intensity + size.intensity*i, this.littleEndian));
+ }
+ }
+ }
+
+ }
+ else if ( PCDheader.data === 'binary' ) {
+
+ var dataview = new DataView( data, PCDheader.headerLen );
+ var offset = PCDheader.offset;
+
+ var intensity_index = PCDheader.fields.findIndex(n=>n==="intensity");
+ var intensity_type = "F";
+ var intensity_size = 4;
+
+ if (intensity_index >= 0){
+ intensity_type = PCDheader.type[intensity_index];
+ intensity_size = PCDheader.size[intensity_index];
+ }
+
+ let x_index = PCDheader.fields.findIndex(n=>n==="x");
+ let x_size = 4;
+ let x_type = 'F';
+ if (x_index >= 0){
+ x_type = PCDheader.type[x_index];
+ x_size = PCDheader.size[x_index];
+ }
+
+
+ for ( var i = 0, row = 0; i < PCDheader.points; i ++, row += PCDheader.rowSize ) {
+
+ if ( offset.x !== undefined ) {
+
+ let getFloat = (x_size==8)? dataview.getFloat64.bind(dataview) : dataview.getFloat32.bind(dataview);
+
+ let x = getFloat( row + offset.x, this.littleEndian );
+ let y = getFloat( row + offset.y, this.littleEndian );
+ let z = getFloat( row + offset.z, this.littleEndian );
+
+ if (filterPoint(x,y,z)){
+ continue;
+ }
+
+ position.push( x );
+ position.push( y );
+ position.push( z );
+
+ }
+
+ if ( offset.rgb !== undefined ) {
+
+ color.push( dataview.getUint8( row + offset.rgb + 2 ) / 255.0 );
+ color.push( dataview.getUint8( row + offset.rgb + 1 ) / 255.0 );
+ color.push( dataview.getUint8( row + offset.rgb + 0 ) / 255.0 );
+
+ }
+
+ if ( offset.normal_x !== undefined ) {
+
+ normal.push( dataview.getFloat32( row + offset.normal_x, this.littleEndian ) );
+ normal.push( dataview.getFloat32( row + offset.normal_y, this.littleEndian ) );
+ normal.push( dataview.getFloat32( row + offset.normal_z, this.littleEndian ) );
+
+ }
+
+ if ( offset.vx !== undefined ) {
+ velocity.push( dataview.getFloat32( row + offset.vx, this.littleEndian ) );
+ velocity.push( dataview.getFloat32( row + offset.vy, this.littleEndian ) );
+ velocity.push( 0 );
+ }
+
+ if (offset.intensity !== undefined) {
+ if (intensity_type == "U" && intensity_size == 1){
+ intensity.push( dataview.getUint8(row + offset.intensity));
+ }
+ else if (intensity_type == "F" && intensity_size == 4){
+ intensity.push( dataview.getFloat32(row + offset.intensity, this.littleEndian));
+ }
+ }
+ }
+
+ }
+
+ return {
+ position: position,
+ color: color,
+ normal: normal,
+ velocity: velocity,
+ intensity: intensity,
+ };
+
+ }
+
+};
+
+export { PCDLoader };
diff --git a/public/js/lib/TransformControls.js b/public/js/lib/TransformControls.js
new file mode 100644
index 0000000..dc95281
--- /dev/null
+++ b/public/js/lib/TransformControls.js
@@ -0,0 +1,1533 @@
+import {
+ BoxGeometry,
+ BufferGeometry,
+ CylinderGeometry,
+ DoubleSide,
+ Euler,
+ Float32BufferAttribute,
+ Line,
+ LineBasicMaterial,
+ Matrix4,
+ Mesh,
+ MeshBasicMaterial,
+ Object3D,
+ OctahedronGeometry,
+ PlaneGeometry,
+ Quaternion,
+ Raycaster,
+ SphereGeometry,
+ TorusGeometry,
+ Vector3
+} from './three.module.js';
+
+const _raycaster = new Raycaster();
+
+const _tempVector = new Vector3();
+const _tempVector2 = new Vector3();
+const _tempQuaternion = new Quaternion();
+const _unit = {
+ X: new Vector3( 1, 0, 0 ),
+ Y: new Vector3( 0, 1, 0 ),
+ Z: new Vector3( 0, 0, 1 )
+};
+
+const _changeEvent = { type: 'change' };
+const _mouseDownEvent = { type: 'mouseDown' };
+const _mouseUpEvent = { type: 'mouseUp', mode: null };
+const _objectChangeEvent = { type: 'objectChange' };
+
+class TransformControls extends Object3D {
+
+ constructor( camera, domElement ) {
+
+ super();
+
+ if ( domElement === undefined ) {
+
+ console.warn( 'THREE.TransformControls: The second parameter "domElement" is now mandatory.' );
+ domElement = document;
+
+ }
+
+ this.visible = false;
+ this.domElement = domElement;
+ this.domElement.style.touchAction = 'none'; // disable touch scroll
+
+ const _gizmo = new TransformControlsGizmo();
+ this._gizmo = _gizmo;
+ this.add( _gizmo );
+
+ const _plane = new TransformControlsPlane();
+ this._plane = _plane;
+ this.add( _plane );
+
+ const scope = this;
+
+ // Defined getter, setter and store for a property
+ function defineProperty( propName, defaultValue ) {
+
+ let propValue = defaultValue;
+
+ Object.defineProperty( scope, propName, {
+
+ get: function () {
+
+ return propValue !== undefined ? propValue : defaultValue;
+
+ },
+
+ set: function ( value ) {
+
+ if ( propValue !== value ) {
+
+ propValue = value;
+ _plane[ propName ] = value;
+ _gizmo[ propName ] = value;
+
+ scope.dispatchEvent( { type: propName + '-changed', value: value } );
+ scope.dispatchEvent( _changeEvent );
+
+ }
+
+ }
+
+ } );
+
+ scope[ propName ] = defaultValue;
+ _plane[ propName ] = defaultValue;
+ _gizmo[ propName ] = defaultValue;
+
+ }
+
+ // Define properties with getters/setter
+ // Setting the defined property will automatically trigger change event
+ // Defined properties are passed down to gizmo and plane
+
+ defineProperty( 'camera', camera );
+ defineProperty( 'object', undefined );
+ defineProperty( 'enabled', true );
+ defineProperty( 'axis', null );
+ defineProperty( 'mode', 'translate' );
+ defineProperty( 'translationSnap', null );
+ defineProperty( 'rotationSnap', null );
+ defineProperty( 'scaleSnap', null );
+ defineProperty( 'space', 'world' );
+ defineProperty( 'size', 1 );
+ defineProperty( 'dragging', false );
+ defineProperty( 'showX', true );
+ defineProperty( 'showY', true );
+ defineProperty( 'showZ', true );
+
+ // Reusable utility variables
+
+ const worldPosition = new Vector3();
+ const worldPositionStart = new Vector3();
+ const worldQuaternion = new Quaternion();
+ const worldQuaternionStart = new Quaternion();
+ const cameraPosition = new Vector3();
+ const cameraQuaternion = new Quaternion();
+ const pointStart = new Vector3();
+ const pointEnd = new Vector3();
+ const rotationAxis = new Vector3();
+ const rotationAngle = 0;
+ const eye = new Vector3();
+
+ // TODO: remove properties unused in plane and gizmo
+
+ defineProperty( 'worldPosition', worldPosition );
+ defineProperty( 'worldPositionStart', worldPositionStart );
+ defineProperty( 'worldQuaternion', worldQuaternion );
+ defineProperty( 'worldQuaternionStart', worldQuaternionStart );
+ defineProperty( 'cameraPosition', cameraPosition );
+ defineProperty( 'cameraQuaternion', cameraQuaternion );
+ defineProperty( 'pointStart', pointStart );
+ defineProperty( 'pointEnd', pointEnd );
+ defineProperty( 'rotationAxis', rotationAxis );
+ defineProperty( 'rotationAngle', rotationAngle );
+ defineProperty( 'eye', eye );
+
+ this._offset = new Vector3();
+ this._startNorm = new Vector3();
+ this._endNorm = new Vector3();
+ this._cameraScale = new Vector3();
+
+ this._parentPosition = new Vector3();
+ this._parentQuaternion = new Quaternion();
+ this._parentQuaternionInv = new Quaternion();
+ this._parentScale = new Vector3();
+
+ this._worldScaleStart = new Vector3();
+ this._worldQuaternionInv = new Quaternion();
+ this._worldScale = new Vector3();
+
+ this._positionStart = new Vector3();
+ this._quaternionStart = new Quaternion();
+ this._scaleStart = new Vector3();
+
+ this._getPointer = getPointer.bind( this );
+ this._onPointerDown = onPointerDown.bind( this );
+ this._onPointerHover = onPointerHover.bind( this );
+ this._onPointerMove = onPointerMove.bind( this );
+ this._onPointerUp = onPointerUp.bind( this );
+
+ this.domElement.addEventListener( 'pointerdown', this._onPointerDown );
+ this.domElement.addEventListener( 'pointermove', this._onPointerHover );
+ this.domElement.addEventListener( 'pointerup', this._onPointerUp );
+
+ }
+
+ // updateMatrixWorld updates key transformation variables
+ updateMatrixWorld() {
+
+ if ( this.object !== undefined ) {
+
+ this.object.updateMatrixWorld();
+
+ if ( this.object.parent === null ) {
+
+ console.error( 'TransformControls: The attached 3D object must be a part of the scene graph.' );
+
+ } else {
+
+ this.object.parent.matrixWorld.decompose( this._parentPosition, this._parentQuaternion, this._parentScale );
+
+ }
+
+ this.object.matrixWorld.decompose( this.worldPosition, this.worldQuaternion, this._worldScale );
+
+ this._parentQuaternionInv.copy( this._parentQuaternion ).invert();
+ this._worldQuaternionInv.copy( this.worldQuaternion ).invert();
+
+ }
+
+ this.camera.updateMatrixWorld();
+ this.camera.matrixWorld.decompose( this.cameraPosition, this.cameraQuaternion, this._cameraScale );
+
+ this.eye.copy( this.cameraPosition ).sub( this.worldPosition ).normalize();
+
+ super.updateMatrixWorld( this );
+
+ }
+
+ pointerHover( pointer ) {
+
+ if ( this.object === undefined || this.dragging === true ) return;
+
+ _raycaster.setFromCamera( pointer, this.camera );
+
+ const intersect = intersectObjectWithRay( this._gizmo.picker[ this.mode ], _raycaster );
+
+ if ( intersect ) {
+
+ this.axis = intersect.object.name;
+
+ } else {
+
+ this.axis = null;
+
+ }
+
+ }
+
+ pointerDown( pointer ) {
+
+ if ( this.object === undefined || this.dragging === true || pointer.button !== 0 ) return;
+
+ if ( this.axis !== null ) {
+
+ _raycaster.setFromCamera( pointer, this.camera );
+
+ const planeIntersect = intersectObjectWithRay( this._plane, _raycaster, true );
+
+ if ( planeIntersect ) {
+
+ this.object.updateMatrixWorld();
+ this.object.parent.updateMatrixWorld();
+
+ this._positionStart.copy( this.object.position );
+ this._quaternionStart.copy( this.object.quaternion );
+ this._scaleStart.copy( this.object.scale );
+
+ this.object.matrixWorld.decompose( this.worldPositionStart, this.worldQuaternionStart, this._worldScaleStart );
+
+ this.pointStart.copy( planeIntersect.point ).sub( this.worldPositionStart );
+
+ }
+
+ this.dragging = true;
+ _mouseDownEvent.mode = this.mode;
+ this.dispatchEvent( _mouseDownEvent );
+
+ }
+
+ }
+
+ pointerMove( pointer ) {
+
+ const axis = this.axis;
+ const mode = this.mode;
+ const object = this.object;
+ let space = this.space;
+
+ if ( mode === 'scale' ) {
+
+ space = 'local';
+
+ } else if ( axis === 'E' || axis === 'XYZE' || axis === 'XYZ' ) {
+
+ space = 'world';
+
+ }
+
+ if ( object === undefined || axis === null || this.dragging === false || pointer.button !== - 1 ) return;
+
+ _raycaster.setFromCamera( pointer, this.camera );
+
+ const planeIntersect = intersectObjectWithRay( this._plane, _raycaster, true );
+
+ if ( ! planeIntersect ) return;
+
+ this.pointEnd.copy( planeIntersect.point ).sub( this.worldPositionStart );
+
+ if ( mode === 'translate' ) {
+
+ // Apply translate
+
+ this._offset.copy( this.pointEnd ).sub( this.pointStart );
+
+ if ( space === 'local' && axis !== 'XYZ' ) {
+
+ this._offset.applyQuaternion( this._worldQuaternionInv );
+
+ }
+
+ if ( axis.indexOf( 'X' ) === - 1 ) this._offset.x = 0;
+ if ( axis.indexOf( 'Y' ) === - 1 ) this._offset.y = 0;
+ if ( axis.indexOf( 'Z' ) === - 1 ) this._offset.z = 0;
+
+ if ( space === 'local' && axis !== 'XYZ' ) {
+
+ this._offset.applyQuaternion( this._quaternionStart ).divide( this._parentScale );
+
+ } else {
+
+ this._offset.applyQuaternion( this._parentQuaternionInv ).divide( this._parentScale );
+
+ }
+
+ object.position.copy( this._offset ).add( this._positionStart );
+
+ // Apply translation snap
+
+ if ( this.translationSnap ) {
+
+ if ( space === 'local' ) {
+
+ object.position.applyQuaternion( _tempQuaternion.copy( this._quaternionStart ).invert() );
+
+ if ( axis.search( 'X' ) !== - 1 ) {
+
+ object.position.x = Math.round( object.position.x / this.translationSnap ) * this.translationSnap;
+
+ }
+
+ if ( axis.search( 'Y' ) !== - 1 ) {
+
+ object.position.y = Math.round( object.position.y / this.translationSnap ) * this.translationSnap;
+
+ }
+
+ if ( axis.search( 'Z' ) !== - 1 ) {
+
+ object.position.z = Math.round( object.position.z / this.translationSnap ) * this.translationSnap;
+
+ }
+
+ object.position.applyQuaternion( this._quaternionStart );
+
+ }
+
+ if ( space === 'world' ) {
+
+ if ( object.parent ) {
+
+ object.position.add( _tempVector.setFromMatrixPosition( object.parent.matrixWorld ) );
+
+ }
+
+ if ( axis.search( 'X' ) !== - 1 ) {
+
+ object.position.x = Math.round( object.position.x / this.translationSnap ) * this.translationSnap;
+
+ }
+
+ if ( axis.search( 'Y' ) !== - 1 ) {
+
+ object.position.y = Math.round( object.position.y / this.translationSnap ) * this.translationSnap;
+
+ }
+
+ if ( axis.search( 'Z' ) !== - 1 ) {
+
+ object.position.z = Math.round( object.position.z / this.translationSnap ) * this.translationSnap;
+
+ }
+
+ if ( object.parent ) {
+
+ object.position.sub( _tempVector.setFromMatrixPosition( object.parent.matrixWorld ) );
+
+ }
+
+ }
+
+ }
+
+ } else if ( mode === 'scale' ) {
+
+ if ( axis.search( 'XYZ' ) !== - 1 ) {
+
+ let d = this.pointEnd.length() / this.pointStart.length();
+
+ if ( this.pointEnd.dot( this.pointStart ) < 0 ) d *= - 1;
+
+ _tempVector2.set( d, d, d );
+
+ } else {
+
+ _tempVector.copy( this.pointStart );
+ _tempVector2.copy( this.pointEnd );
+
+ _tempVector.applyQuaternion( this._worldQuaternionInv );
+ _tempVector2.applyQuaternion( this._worldQuaternionInv );
+
+ _tempVector2.divide( _tempVector );
+
+ if ( axis.search( 'X' ) === - 1 ) {
+
+ _tempVector2.x = 1;
+
+ }
+
+ if ( axis.search( 'Y' ) === - 1 ) {
+
+ _tempVector2.y = 1;
+
+ }
+
+ if ( axis.search( 'Z' ) === - 1 ) {
+
+ _tempVector2.z = 1;
+
+ }
+
+ }
+
+ // Apply scale
+
+ object.scale.copy( this._scaleStart ).multiply( _tempVector2 );
+
+ if ( this.scaleSnap ) {
+
+ if ( axis.search( 'X' ) !== - 1 ) {
+
+ object.scale.x = Math.round( object.scale.x / this.scaleSnap ) * this.scaleSnap || this.scaleSnap;
+
+ }
+
+ if ( axis.search( 'Y' ) !== - 1 ) {
+
+ object.scale.y = Math.round( object.scale.y / this.scaleSnap ) * this.scaleSnap || this.scaleSnap;
+
+ }
+
+ if ( axis.search( 'Z' ) !== - 1 ) {
+
+ object.scale.z = Math.round( object.scale.z / this.scaleSnap ) * this.scaleSnap || this.scaleSnap;
+
+ }
+
+ }
+
+ } else if ( mode === 'rotate' ) {
+
+ this._offset.copy( this.pointEnd ).sub( this.pointStart );
+
+ const ROTATION_SPEED = 20 / this.worldPosition.distanceTo( _tempVector.setFromMatrixPosition( this.camera.matrixWorld ) );
+
+ if ( axis === 'E' ) {
+
+ this.rotationAxis.copy( this.eye );
+ this.rotationAngle = this.pointEnd.angleTo( this.pointStart );
+
+ this._startNorm.copy( this.pointStart ).normalize();
+ this._endNorm.copy( this.pointEnd ).normalize();
+
+ this.rotationAngle *= ( this._endNorm.cross( this._startNorm ).dot( this.eye ) < 0 ? 1 : - 1 );
+
+ } else if ( axis === 'XYZE' ) {
+
+ this.rotationAxis.copy( this._offset ).cross( this.eye ).normalize();
+ this.rotationAngle = this._offset.dot( _tempVector.copy( this.rotationAxis ).cross( this.eye ) ) * ROTATION_SPEED;
+
+ } else if ( axis === 'X' || axis === 'Y' || axis === 'Z' ) {
+
+ this.rotationAxis.copy( _unit[ axis ] );
+
+ _tempVector.copy( _unit[ axis ] );
+
+ if ( space === 'local' ) {
+
+ _tempVector.applyQuaternion( this.worldQuaternion );
+
+ }
+
+ this.rotationAngle = this._offset.dot( _tempVector.cross( this.eye ).normalize() ) * ROTATION_SPEED;
+
+ }
+
+ // Apply rotation snap
+
+ if ( this.rotationSnap ) this.rotationAngle = Math.round( this.rotationAngle / this.rotationSnap ) * this.rotationSnap;
+
+ // Apply rotate
+ if ( space === 'local' && axis !== 'E' && axis !== 'XYZE' ) {
+
+ object.quaternion.copy( this._quaternionStart );
+ object.quaternion.multiply( _tempQuaternion.setFromAxisAngle( this.rotationAxis, this.rotationAngle ) ).normalize();
+
+ } else {
+
+ this.rotationAxis.applyQuaternion( this._parentQuaternionInv );
+ object.quaternion.copy( _tempQuaternion.setFromAxisAngle( this.rotationAxis, this.rotationAngle ) );
+ object.quaternion.multiply( this._quaternionStart ).normalize();
+
+ }
+
+ }
+
+ this.dispatchEvent( _changeEvent );
+ this.dispatchEvent( _objectChangeEvent );
+
+ }
+
+ pointerUp( pointer ) {
+
+ if ( pointer.button !== 0 ) return;
+
+ if ( this.dragging && ( this.axis !== null ) ) {
+
+ _mouseUpEvent.mode = this.mode;
+ this.dispatchEvent( _mouseUpEvent );
+
+ }
+
+ this.dragging = false;
+ this.axis = null;
+
+ }
+
+ dispose() {
+
+ this.domElement.removeEventListener( 'pointerdown', this._onPointerDown );
+ this.domElement.removeEventListener( 'pointermove', this._onPointerHover );
+ this.domElement.removeEventListener( 'pointermove', this._onPointerMove );
+ this.domElement.removeEventListener( 'pointerup', this._onPointerUp );
+
+ this.traverse( function ( child ) {
+
+ if ( child.geometry ) child.geometry.dispose();
+ if ( child.material ) child.material.dispose();
+
+ } );
+
+ }
+
+ // Set current object
+ attach( object ) {
+
+ this.object = object;
+ this.visible = true;
+
+ return this;
+
+ }
+
+ // Detatch from object
+ detach() {
+
+ this.object = undefined;
+ this.visible = false;
+ this.axis = null;
+
+ return this;
+
+ }
+
+ getRaycaster() {
+
+ return _raycaster;
+
+ }
+
+ // TODO: deprecate
+
+ getMode() {
+
+ return this.mode;
+
+ }
+
+ setMode( mode ) {
+
+ this.mode = mode;
+
+ }
+
+ setTranslationSnap( translationSnap ) {
+
+ this.translationSnap = translationSnap;
+
+ }
+
+ setRotationSnap( rotationSnap ) {
+
+ this.rotationSnap = rotationSnap;
+
+ }
+
+ setScaleSnap( scaleSnap ) {
+
+ this.scaleSnap = scaleSnap;
+
+ }
+
+ setSize( size ) {
+
+ this.size = size;
+
+ }
+
+ setSpace( space ) {
+
+ this.space = space;
+
+ }
+
+ update() {
+
+ console.warn( 'THREE.TransformControls: update function has no more functionality and therefore has been deprecated.' );
+
+ }
+
+}
+
+TransformControls.prototype.isTransformControls = true;
+
+// mouse / touch event handlers
+
+function getPointer( event ) {
+
+ if ( this.domElement.ownerDocument.pointerLockElement ) {
+
+ return {
+ x: 0,
+ y: 0,
+ button: event.button
+ };
+
+ } else {
+
+ const rect = this.domElement.getBoundingClientRect();
+
+ return {
+ x: ( event.clientX - rect.left ) / rect.width * 2 - 1,
+ y: - ( event.clientY - rect.top ) / rect.height * 2 + 1,
+ button: event.button
+ };
+
+ }
+
+}
+
+function onPointerHover( event ) {
+
+ if ( ! this.enabled ) return;
+
+ switch ( event.pointerType ) {
+
+ case 'mouse':
+ case 'pen':
+ this.pointerHover( this._getPointer( event ) );
+ break;
+
+ }
+
+}
+
+function onPointerDown( event ) {
+
+ if ( ! this.enabled ) return;
+
+ this.domElement.setPointerCapture( event.pointerId );
+
+ this.domElement.addEventListener( 'pointermove', this._onPointerMove );
+
+ this.pointerHover( this._getPointer( event ) );
+ this.pointerDown( this._getPointer( event ) );
+
+}
+
+function onPointerMove( event ) {
+
+ if ( ! this.enabled ) return;
+
+ this.pointerMove( this._getPointer( event ) );
+
+}
+
+function onPointerUp( event ) {
+
+ if ( ! this.enabled ) return;
+
+ this.domElement.releasePointerCapture( event.pointerId );
+
+ this.domElement.removeEventListener( 'pointermove', this._onPointerMove );
+
+ this.pointerUp( this._getPointer( event ) );
+
+}
+
+function intersectObjectWithRay( object, raycaster, includeInvisible ) {
+
+ const allIntersections = raycaster.intersectObject( object, true );
+
+ for ( let i = 0; i < allIntersections.length; i ++ ) {
+
+ if ( allIntersections[ i ].object.visible || includeInvisible ) {
+
+ return allIntersections[ i ];
+
+ }
+
+ }
+
+ return false;
+
+}
+
+//
+
+// Reusable utility variables
+
+const _tempEuler = new Euler();
+const _alignVector = new Vector3( 0, 1, 0 );
+const _zeroVector = new Vector3( 0, 0, 0 );
+const _lookAtMatrix = new Matrix4();
+const _tempQuaternion2 = new Quaternion();
+const _identityQuaternion = new Quaternion();
+const _dirVector = new Vector3();
+const _tempMatrix = new Matrix4();
+
+const _unitX = new Vector3( 1, 0, 0 );
+const _unitY = new Vector3( 0, 1, 0 );
+const _unitZ = new Vector3( 0, 0, 1 );
+
+const _v1 = new Vector3();
+const _v2 = new Vector3();
+const _v3 = new Vector3();
+
+class TransformControlsGizmo extends Object3D {
+
+ constructor() {
+
+ super();
+
+ this.type = 'TransformControlsGizmo';
+
+ // shared materials
+
+ const gizmoMaterial = new MeshBasicMaterial( {
+ depthTest: false,
+ depthWrite: false,
+ fog: false,
+ toneMapped: false,
+ transparent: true
+ } );
+
+ const gizmoLineMaterial = new LineBasicMaterial( {
+ depthTest: false,
+ depthWrite: false,
+ fog: false,
+ toneMapped: false,
+ transparent: true
+ } );
+
+ // Make unique material for each axis/color
+
+ const matInvisible = gizmoMaterial.clone();
+ matInvisible.opacity = 0.15;
+
+ const matHelper = gizmoLineMaterial.clone();
+ matHelper.opacity = 0.5;
+
+ const matRed = gizmoMaterial.clone();
+ matRed.color.setHex( 0xff0000 );
+
+ const matGreen = gizmoMaterial.clone();
+ matGreen.color.setHex( 0x00ff00 );
+
+ const matBlue = gizmoMaterial.clone();
+ matBlue.color.setHex( 0x0000ff );
+
+ const matRedTransparent = gizmoMaterial.clone();
+ matRedTransparent.color.setHex( 0xff0000 );
+ matRedTransparent.opacity = 0.5;
+
+ const matGreenTransparent = gizmoMaterial.clone();
+ matGreenTransparent.color.setHex( 0x00ff00 );
+ matGreenTransparent.opacity = 0.5;
+
+ const matBlueTransparent = gizmoMaterial.clone();
+ matBlueTransparent.color.setHex( 0x0000ff );
+ matBlueTransparent.opacity = 0.5;
+
+ const matWhiteTransparent = gizmoMaterial.clone();
+ matWhiteTransparent.opacity = 0.25;
+
+ const matYellowTransparent = gizmoMaterial.clone();
+ matYellowTransparent.color.setHex( 0xffff00 );
+ matYellowTransparent.opacity = 0.25;
+
+ const matYellow = gizmoMaterial.clone();
+ matYellow.color.setHex( 0xffff00 );
+
+ const matGray = gizmoMaterial.clone();
+ matGray.color.setHex( 0x787878 );
+
+ // reusable geometry
+
+ const arrowGeometry = new CylinderGeometry( 0, 0.04, 0.1, 12 );
+ arrowGeometry.translate( 0, 0.05, 0 );
+
+ const scaleHandleGeometry = new BoxGeometry( 0.08, 0.08, 0.08 );
+ scaleHandleGeometry.translate( 0, 0.04, 0 );
+
+ const lineGeometry = new BufferGeometry();
+ lineGeometry.setAttribute( 'position', new Float32BufferAttribute( [ 0, 0, 0, 1, 0, 0 ], 3 ) );
+
+ const lineGeometry2 = new CylinderGeometry( 0.0075, 0.0075, 0.5, 3 );
+ lineGeometry2.translate( 0, 0.25, 0 );
+
+ function CircleGeometry( radius, arc ) {
+
+ const geometry = new TorusGeometry( radius, 0.0075, 3, 64, arc * Math.PI * 2 );
+ geometry.rotateY( Math.PI / 2 );
+ geometry.rotateX( Math.PI / 2 );
+ return geometry;
+
+ }
+
+ // Special geometry for transform helper. If scaled with position vector it spans from [0,0,0] to position
+
+ function TranslateHelperGeometry() {
+
+ const geometry = new BufferGeometry();
+
+ geometry.setAttribute( 'position', new Float32BufferAttribute( [ 0, 0, 0, 1, 1, 1 ], 3 ) );
+
+ return geometry;
+
+ }
+
+ // Gizmo definitions - custom hierarchy definitions for setupGizmo() function
+
+ const gizmoTranslate = {
+ X: [
+ [ new Mesh( arrowGeometry, matRed ), [ 0.5, 0, 0 ], [ 0, 0, - Math.PI / 2 ]],
+ [ new Mesh( arrowGeometry, matRed ), [ - 0.5, 0, 0 ], [ 0, 0, Math.PI / 2 ]],
+ [ new Mesh( lineGeometry2, matRed ), [ 0, 0, 0 ], [ 0, 0, - Math.PI / 2 ]]
+ ],
+ Y: [
+ [ new Mesh( arrowGeometry, matGreen ), [ 0, 0.5, 0 ]],
+ [ new Mesh( arrowGeometry, matGreen ), [ 0, - 0.5, 0 ], [ Math.PI, 0, 0 ]],
+ [ new Mesh( lineGeometry2, matGreen ) ]
+ ],
+ Z: [
+ [ new Mesh( arrowGeometry, matBlue ), [ 0, 0, 0.5 ], [ Math.PI / 2, 0, 0 ]],
+ [ new Mesh( arrowGeometry, matBlue ), [ 0, 0, - 0.5 ], [ - Math.PI / 2, 0, 0 ]],
+ [ new Mesh( lineGeometry2, matBlue ), null, [ Math.PI / 2, 0, 0 ]]
+ ],
+ XYZ: [
+ [ new Mesh( new OctahedronGeometry( 0.1, 0 ), matWhiteTransparent.clone() ), [ 0, 0, 0 ]]
+ ],
+ XY: [
+ [ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matBlueTransparent.clone() ), [ 0.15, 0.15, 0 ]]
+ ],
+ YZ: [
+ [ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matRedTransparent.clone() ), [ 0, 0.15, 0.15 ], [ 0, Math.PI / 2, 0 ]]
+ ],
+ XZ: [
+ [ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matGreenTransparent.clone() ), [ 0.15, 0, 0.15 ], [ - Math.PI / 2, 0, 0 ]]
+ ]
+ };
+
+ const pickerTranslate = {
+ X: [
+ [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0.3, 0, 0 ], [ 0, 0, - Math.PI / 2 ]],
+ [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ - 0.3, 0, 0 ], [ 0, 0, Math.PI / 2 ]]
+ ],
+ Y: [
+ [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, 0.3, 0 ]],
+ [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, - 0.3, 0 ], [ 0, 0, Math.PI ]]
+ ],
+ Z: [
+ [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, 0, 0.3 ], [ Math.PI / 2, 0, 0 ]],
+ [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, 0, - 0.3 ], [ - Math.PI / 2, 0, 0 ]]
+ ],
+ XYZ: [
+ [ new Mesh( new OctahedronGeometry( 0.2, 0 ), matInvisible ) ]
+ ],
+ XY: [
+ [ new Mesh( new BoxGeometry( 0.2, 0.2, 0.01 ), matInvisible ), [ 0.15, 0.15, 0 ]]
+ ],
+ YZ: [
+ [ new Mesh( new BoxGeometry( 0.2, 0.2, 0.01 ), matInvisible ), [ 0, 0.15, 0.15 ], [ 0, Math.PI / 2, 0 ]]
+ ],
+ XZ: [
+ [ new Mesh( new BoxGeometry( 0.2, 0.2, 0.01 ), matInvisible ), [ 0.15, 0, 0.15 ], [ - Math.PI / 2, 0, 0 ]]
+ ]
+ };
+
+ const helperTranslate = {
+ START: [
+ [ new Mesh( new OctahedronGeometry( 0.01, 2 ), matHelper ), null, null, null, 'helper' ]
+ ],
+ END: [
+ [ new Mesh( new OctahedronGeometry( 0.01, 2 ), matHelper ), null, null, null, 'helper' ]
+ ],
+ DELTA: [
+ [ new Line( TranslateHelperGeometry(), matHelper ), null, null, null, 'helper' ]
+ ],
+ X: [
+ [ new Line( lineGeometry, matHelper.clone() ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ]
+ ],
+ Y: [
+ [ new Line( lineGeometry, matHelper.clone() ), [ 0, - 1e3, 0 ], [ 0, 0, Math.PI / 2 ], [ 1e6, 1, 1 ], 'helper' ]
+ ],
+ Z: [
+ [ new Line( lineGeometry, matHelper.clone() ), [ 0, 0, - 1e3 ], [ 0, - Math.PI / 2, 0 ], [ 1e6, 1, 1 ], 'helper' ]
+ ]
+ };
+
+ const gizmoRotate = {
+ XYZE: [
+ [ new Mesh( CircleGeometry( 0.5, 1 ), matGray ), null, [ 0, Math.PI / 2, 0 ]]
+ ],
+ X: [
+ [ new Mesh( CircleGeometry( 0.5, 0.5 ), matRed ) ]
+ ],
+ Y: [
+ [ new Mesh( CircleGeometry( 0.5, 0.5 ), matGreen ), null, [ 0, 0, - Math.PI / 2 ]]
+ ],
+ Z: [
+ [ new Mesh( CircleGeometry( 0.5, 0.5 ), matBlue ), null, [ 0, Math.PI / 2, 0 ]]
+ ],
+ E: [
+ [ new Mesh( CircleGeometry( 0.75, 1 ), matYellowTransparent ), null, [ 0, Math.PI / 2, 0 ]]
+ ]
+ };
+
+ const helperRotate = {
+ AXIS: [
+ [ new Line( lineGeometry, matHelper.clone() ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ]
+ ]
+ };
+
+ const pickerRotate = {
+ XYZE: [
+ [ new Mesh( new SphereGeometry( 0.25, 10, 8 ), matInvisible ) ]
+ ],
+ X: [
+ [ new Mesh( new TorusGeometry( 0.5, 0.1, 4, 24 ), matInvisible ), [ 0, 0, 0 ], [ 0, - Math.PI / 2, - Math.PI / 2 ]],
+ ],
+ Y: [
+ [ new Mesh( new TorusGeometry( 0.5, 0.1, 4, 24 ), matInvisible ), [ 0, 0, 0 ], [ Math.PI / 2, 0, 0 ]],
+ ],
+ Z: [
+ [ new Mesh( new TorusGeometry( 0.5, 0.1, 4, 24 ), matInvisible ), [ 0, 0, 0 ], [ 0, 0, - Math.PI / 2 ]],
+ ],
+ E: [
+ [ new Mesh( new TorusGeometry( 0.75, 0.1, 2, 24 ), matInvisible ) ]
+ ]
+ };
+
+ const gizmoScale = {
+ X: [
+ [ new Mesh( scaleHandleGeometry, matRed ), [ 0.5, 0, 0 ], [ 0, 0, - Math.PI / 2 ]],
+ [ new Mesh( lineGeometry2, matRed ), [ 0, 0, 0 ], [ 0, 0, - Math.PI / 2 ]],
+ [ new Mesh( scaleHandleGeometry, matRed ), [ - 0.5, 0, 0 ], [ 0, 0, Math.PI / 2 ]],
+ ],
+ Y: [
+ [ new Mesh( scaleHandleGeometry, matGreen ), [ 0, 0.5, 0 ]],
+ [ new Mesh( lineGeometry2, matGreen ) ],
+ [ new Mesh( scaleHandleGeometry, matGreen ), [ 0, - 0.5, 0 ], [ 0, 0, Math.PI ]],
+ ],
+ Z: [
+ [ new Mesh( scaleHandleGeometry, matBlue ), [ 0, 0, 0.5 ], [ Math.PI / 2, 0, 0 ]],
+ [ new Mesh( lineGeometry2, matBlue ), [ 0, 0, 0 ], [ Math.PI / 2, 0, 0 ]],
+ [ new Mesh( scaleHandleGeometry, matBlue ), [ 0, 0, - 0.5 ], [ - Math.PI / 2, 0, 0 ]]
+ ],
+ XY: [
+ [ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matBlueTransparent ), [ 0.15, 0.15, 0 ]]
+ ],
+ YZ: [
+ [ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matRedTransparent ), [ 0, 0.15, 0.15 ], [ 0, Math.PI / 2, 0 ]]
+ ],
+ XZ: [
+ [ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matGreenTransparent ), [ 0.15, 0, 0.15 ], [ - Math.PI / 2, 0, 0 ]]
+ ],
+ XYZ: [
+ [ new Mesh( new BoxGeometry( 0.1, 0.1, 0.1 ), matWhiteTransparent.clone() ) ],
+ ]
+ };
+
+ const pickerScale = {
+ X: [
+ [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0.3, 0, 0 ], [ 0, 0, - Math.PI / 2 ]],
+ [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ - 0.3, 0, 0 ], [ 0, 0, Math.PI / 2 ]]
+ ],
+ Y: [
+ [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, 0.3, 0 ]],
+ [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, - 0.3, 0 ], [ 0, 0, Math.PI ]]
+ ],
+ Z: [
+ [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, 0, 0.3 ], [ Math.PI / 2, 0, 0 ]],
+ [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, 0, - 0.3 ], [ - Math.PI / 2, 0, 0 ]]
+ ],
+ XY: [
+ [ new Mesh( new BoxGeometry( 0.2, 0.2, 0.01 ), matInvisible ), [ 0.15, 0.15, 0 ]],
+ ],
+ YZ: [
+ [ new Mesh( new BoxGeometry( 0.2, 0.2, 0.01 ), matInvisible ), [ 0, 0.15, 0.15 ], [ 0, Math.PI / 2, 0 ]],
+ ],
+ XZ: [
+ [ new Mesh( new BoxGeometry( 0.2, 0.2, 0.01 ), matInvisible ), [ 0.15, 0, 0.15 ], [ - Math.PI / 2, 0, 0 ]],
+ ],
+ XYZ: [
+ [ new Mesh( new BoxGeometry( 0.2, 0.2, 0.2 ), matInvisible ), [ 0, 0, 0 ]],
+ ]
+ };
+
+ const helperScale = {
+ X: [
+ [ new Line( lineGeometry, matHelper.clone() ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ]
+ ],
+ Y: [
+ [ new Line( lineGeometry, matHelper.clone() ), [ 0, - 1e3, 0 ], [ 0, 0, Math.PI / 2 ], [ 1e6, 1, 1 ], 'helper' ]
+ ],
+ Z: [
+ [ new Line( lineGeometry, matHelper.clone() ), [ 0, 0, - 1e3 ], [ 0, - Math.PI / 2, 0 ], [ 1e6, 1, 1 ], 'helper' ]
+ ]
+ };
+
+ // Creates an Object3D with gizmos described in custom hierarchy definition.
+
+ function setupGizmo( gizmoMap ) {
+
+ const gizmo = new Object3D();
+
+ for ( const name in gizmoMap ) {
+
+ for ( let i = gizmoMap[ name ].length; i --; ) {
+
+ const object = gizmoMap[ name ][ i ][ 0 ].clone();
+ const position = gizmoMap[ name ][ i ][ 1 ];
+ const rotation = gizmoMap[ name ][ i ][ 2 ];
+ const scale = gizmoMap[ name ][ i ][ 3 ];
+ const tag = gizmoMap[ name ][ i ][ 4 ];
+
+ // name and tag properties are essential for picking and updating logic.
+ object.name = name;
+ object.tag = tag;
+
+ if ( position ) {
+
+ object.position.set( position[ 0 ], position[ 1 ], position[ 2 ] );
+
+ }
+
+ if ( rotation ) {
+
+ object.rotation.set( rotation[ 0 ], rotation[ 1 ], rotation[ 2 ] );
+
+ }
+
+ if ( scale ) {
+
+ object.scale.set( scale[ 0 ], scale[ 1 ], scale[ 2 ] );
+
+ }
+
+ object.updateMatrix();
+
+ const tempGeometry = object.geometry.clone();
+ tempGeometry.applyMatrix4( object.matrix );
+ object.geometry = tempGeometry;
+ object.renderOrder = Infinity;
+
+ object.position.set( 0, 0, 0 );
+ object.rotation.set( 0, 0, 0 );
+ object.scale.set( 1, 1, 1 );
+
+ gizmo.add( object );
+
+ }
+
+ }
+
+ return gizmo;
+
+ }
+
+ // Gizmo creation
+
+ this.gizmo = {};
+ this.picker = {};
+ this.helper = {};
+
+ this.add( this.gizmo[ 'translate' ] = setupGizmo( gizmoTranslate ) );
+ this.add( this.gizmo[ 'rotate' ] = setupGizmo( gizmoRotate ) );
+ this.add( this.gizmo[ 'scale' ] = setupGizmo( gizmoScale ) );
+ this.add( this.picker[ 'translate' ] = setupGizmo( pickerTranslate ) );
+ this.add( this.picker[ 'rotate' ] = setupGizmo( pickerRotate ) );
+ this.add( this.picker[ 'scale' ] = setupGizmo( pickerScale ) );
+ this.add( this.helper[ 'translate' ] = setupGizmo( helperTranslate ) );
+ this.add( this.helper[ 'rotate' ] = setupGizmo( helperRotate ) );
+ this.add( this.helper[ 'scale' ] = setupGizmo( helperScale ) );
+
+ // Pickers should be hidden always
+
+ this.picker[ 'translate' ].visible = false;
+ this.picker[ 'rotate' ].visible = false;
+ this.picker[ 'scale' ].visible = false;
+
+ }
+
+ // updateMatrixWorld will update transformations and appearance of individual handles
+
+ updateMatrixWorld( force ) {
+
+ const space = ( this.mode === 'scale' ) ? 'local' : this.space; // scale always oriented to local rotation
+
+ const quaternion = ( space === 'local' ) ? this.worldQuaternion : _identityQuaternion;
+
+ // Show only gizmos for current transform mode
+
+ this.gizmo[ 'translate' ].visible = this.mode === 'translate';
+ this.gizmo[ 'rotate' ].visible = this.mode === 'rotate';
+ this.gizmo[ 'scale' ].visible = this.mode === 'scale';
+
+ this.helper[ 'translate' ].visible = this.mode === 'translate';
+ this.helper[ 'rotate' ].visible = this.mode === 'rotate';
+ this.helper[ 'scale' ].visible = this.mode === 'scale';
+
+
+ let handles = [];
+ handles = handles.concat( this.picker[ this.mode ].children );
+ handles = handles.concat( this.gizmo[ this.mode ].children );
+ handles = handles.concat( this.helper[ this.mode ].children );
+
+ for ( let i = 0; i < handles.length; i ++ ) {
+
+ const handle = handles[ i ];
+
+ // hide aligned to camera
+
+ handle.visible = true;
+ handle.rotation.set( 0, 0, 0 );
+ handle.position.copy( this.worldPosition );
+
+ let factor;
+
+ if ( this.camera.isOrthographicCamera ) {
+
+ factor = ( this.camera.top - this.camera.bottom ) / this.camera.zoom;
+
+ } else {
+
+ factor = this.worldPosition.distanceTo( this.cameraPosition ) * Math.min( 1.9 * Math.tan( Math.PI * this.camera.fov / 360 ) / this.camera.zoom, 7 );
+
+ }
+
+ handle.scale.set( 1, 1, 1 ).multiplyScalar( factor * this.size / 4 );
+
+ // TODO: simplify helpers and consider decoupling from gizmo
+
+ if ( handle.tag === 'helper' ) {
+
+ handle.visible = false;
+
+ if ( handle.name === 'AXIS' ) {
+
+ handle.position.copy( this.worldPositionStart );
+ handle.visible = !! this.axis;
+
+ if ( this.axis === 'X' ) {
+
+ _tempQuaternion.setFromEuler( _tempEuler.set( 0, 0, 0 ) );
+ handle.quaternion.copy( quaternion ).multiply( _tempQuaternion );
+
+ if ( Math.abs( _alignVector.copy( _unitX ).applyQuaternion( quaternion ).dot( this.eye ) ) > 0.9 ) {
+
+ handle.visible = false;
+
+ }
+
+ }
+
+ if ( this.axis === 'Y' ) {
+
+ _tempQuaternion.setFromEuler( _tempEuler.set( 0, 0, Math.PI / 2 ) );
+ handle.quaternion.copy( quaternion ).multiply( _tempQuaternion );
+
+ if ( Math.abs( _alignVector.copy( _unitY ).applyQuaternion( quaternion ).dot( this.eye ) ) > 0.9 ) {
+
+ handle.visible = false;
+
+ }
+
+ }
+
+ if ( this.axis === 'Z' ) {
+
+ _tempQuaternion.setFromEuler( _tempEuler.set( 0, Math.PI / 2, 0 ) );
+ handle.quaternion.copy( quaternion ).multiply( _tempQuaternion );
+
+ if ( Math.abs( _alignVector.copy( _unitZ ).applyQuaternion( quaternion ).dot( this.eye ) ) > 0.9 ) {
+
+ handle.visible = false;
+
+ }
+
+ }
+
+ if ( this.axis === 'XYZE' ) {
+
+ _tempQuaternion.setFromEuler( _tempEuler.set( 0, Math.PI / 2, 0 ) );
+ _alignVector.copy( this.rotationAxis );
+ handle.quaternion.setFromRotationMatrix( _lookAtMatrix.lookAt( _zeroVector, _alignVector, _unitY ) );
+ handle.quaternion.multiply( _tempQuaternion );
+ handle.visible = this.dragging;
+
+ }
+
+ if ( this.axis === 'E' ) {
+
+ handle.visible = false;
+
+ }
+
+
+ } else if ( handle.name === 'START' ) {
+
+ handle.position.copy( this.worldPositionStart );
+ handle.visible = this.dragging;
+
+ } else if ( handle.name === 'END' ) {
+
+ handle.position.copy( this.worldPosition );
+ handle.visible = this.dragging;
+
+ } else if ( handle.name === 'DELTA' ) {
+
+ handle.position.copy( this.worldPositionStart );
+ handle.quaternion.copy( this.worldQuaternionStart );
+ _tempVector.set( 1e-10, 1e-10, 1e-10 ).add( this.worldPositionStart ).sub( this.worldPosition ).multiplyScalar( - 1 );
+ _tempVector.applyQuaternion( this.worldQuaternionStart.clone().invert() );
+ handle.scale.copy( _tempVector );
+ handle.visible = this.dragging;
+
+ } else {
+
+ handle.quaternion.copy( quaternion );
+
+ if ( this.dragging ) {
+
+ handle.position.copy( this.worldPositionStart );
+
+ } else {
+
+ handle.position.copy( this.worldPosition );
+
+ }
+
+ if ( this.axis ) {
+
+ handle.visible = this.axis.search( handle.name ) !== - 1;
+
+ }
+
+ }
+
+ // If updating helper, skip rest of the loop
+ continue;
+
+ }
+
+ // Align handles to current local or world rotation
+
+ handle.quaternion.copy( quaternion );
+
+ if ( this.mode === 'translate' || this.mode === 'scale' ) {
+
+ // Hide translate and scale axis facing the camera
+
+ const AXIS_HIDE_TRESHOLD = 0.99;
+ const PLANE_HIDE_TRESHOLD = 0.2;
+
+ if ( handle.name === 'X' ) {
+
+ if ( Math.abs( _alignVector.copy( _unitX ).applyQuaternion( quaternion ).dot( this.eye ) ) > AXIS_HIDE_TRESHOLD ) {
+
+ handle.scale.set( 1e-10, 1e-10, 1e-10 );
+ handle.visible = false;
+
+ }
+
+ }
+
+ if ( handle.name === 'Y' ) {
+
+ if ( Math.abs( _alignVector.copy( _unitY ).applyQuaternion( quaternion ).dot( this.eye ) ) > AXIS_HIDE_TRESHOLD ) {
+
+ handle.scale.set( 1e-10, 1e-10, 1e-10 );
+ handle.visible = false;
+
+ }
+
+ }
+
+ if ( handle.name === 'Z' ) {
+
+ if ( Math.abs( _alignVector.copy( _unitZ ).applyQuaternion( quaternion ).dot( this.eye ) ) > AXIS_HIDE_TRESHOLD ) {
+
+ handle.scale.set( 1e-10, 1e-10, 1e-10 );
+ handle.visible = false;
+
+ }
+
+ }
+
+ if ( handle.name === 'XY' ) {
+
+ if ( Math.abs( _alignVector.copy( _unitZ ).applyQuaternion( quaternion ).dot( this.eye ) ) < PLANE_HIDE_TRESHOLD ) {
+
+ handle.scale.set( 1e-10, 1e-10, 1e-10 );
+ handle.visible = false;
+
+ }
+
+ }
+
+ if ( handle.name === 'YZ' ) {
+
+ if ( Math.abs( _alignVector.copy( _unitX ).applyQuaternion( quaternion ).dot( this.eye ) ) < PLANE_HIDE_TRESHOLD ) {
+
+ handle.scale.set( 1e-10, 1e-10, 1e-10 );
+ handle.visible = false;
+
+ }
+
+ }
+
+ if ( handle.name === 'XZ' ) {
+
+ if ( Math.abs( _alignVector.copy( _unitY ).applyQuaternion( quaternion ).dot( this.eye ) ) < PLANE_HIDE_TRESHOLD ) {
+
+ handle.scale.set( 1e-10, 1e-10, 1e-10 );
+ handle.visible = false;
+
+ }
+
+ }
+
+ } else if ( this.mode === 'rotate' ) {
+
+ // Align handles to current local or world rotation
+
+ _tempQuaternion2.copy( quaternion );
+ _alignVector.copy( this.eye ).applyQuaternion( _tempQuaternion.copy( quaternion ).invert() );
+
+ if ( handle.name.search( 'E' ) !== - 1 ) {
+
+ handle.quaternion.setFromRotationMatrix( _lookAtMatrix.lookAt( this.eye, _zeroVector, _unitY ) );
+
+ }
+
+ if ( handle.name === 'X' ) {
+
+ _tempQuaternion.setFromAxisAngle( _unitX, Math.atan2( - _alignVector.y, _alignVector.z ) );
+ _tempQuaternion.multiplyQuaternions( _tempQuaternion2, _tempQuaternion );
+ handle.quaternion.copy( _tempQuaternion );
+
+ }
+
+ if ( handle.name === 'Y' ) {
+
+ _tempQuaternion.setFromAxisAngle( _unitY, Math.atan2( _alignVector.x, _alignVector.z ) );
+ _tempQuaternion.multiplyQuaternions( _tempQuaternion2, _tempQuaternion );
+ handle.quaternion.copy( _tempQuaternion );
+
+ }
+
+ if ( handle.name === 'Z' ) {
+
+ _tempQuaternion.setFromAxisAngle( _unitZ, Math.atan2( _alignVector.y, _alignVector.x ) );
+ _tempQuaternion.multiplyQuaternions( _tempQuaternion2, _tempQuaternion );
+ handle.quaternion.copy( _tempQuaternion );
+
+ }
+
+ }
+
+ // Hide disabled axes
+ handle.visible = handle.visible && ( handle.name.indexOf( 'X' ) === - 1 || this.showX );
+ handle.visible = handle.visible && ( handle.name.indexOf( 'Y' ) === - 1 || this.showY );
+ handle.visible = handle.visible && ( handle.name.indexOf( 'Z' ) === - 1 || this.showZ );
+ handle.visible = handle.visible && ( handle.name.indexOf( 'E' ) === - 1 || ( this.showX && this.showY && this.showZ ) );
+
+ // highlight selected axis
+
+ handle.material._color = handle.material._color || handle.material.color.clone();
+ handle.material._opacity = handle.material._opacity || handle.material.opacity;
+
+ handle.material.color.copy( handle.material._color );
+ handle.material.opacity = handle.material._opacity;
+
+ if ( this.enabled && this.axis ) {
+
+ if ( handle.name === this.axis ) {
+
+ handle.material.color.setHex( 0xffff00 );
+ handle.material.opacity = 1.0;
+
+ } else if ( this.axis.split( '' ).some( function ( a ) {
+
+ return handle.name === a;
+
+ } ) ) {
+
+ handle.material.color.setHex( 0xffff00 );
+ handle.material.opacity = 1.0;
+
+ }
+
+ }
+
+ }
+
+ super.updateMatrixWorld( force );
+
+ }
+
+}
+
+TransformControlsGizmo.prototype.isTransformControlsGizmo = true;
+
+//
+
+class TransformControlsPlane extends Mesh {
+
+ constructor() {
+
+ super(
+ new PlaneGeometry( 100000, 100000, 2, 2 ),
+ new MeshBasicMaterial( { visible: false, wireframe: true, side: DoubleSide, transparent: true, opacity: 0.1, toneMapped: false } )
+ );
+
+ this.type = 'TransformControlsPlane';
+
+ }
+
+ updateMatrixWorld( force ) {
+
+ let space = this.space;
+
+ this.position.copy( this.worldPosition );
+
+ if ( this.mode === 'scale' ) space = 'local'; // scale always oriented to local rotation
+
+ _v1.copy( _unitX ).applyQuaternion( space === 'local' ? this.worldQuaternion : _identityQuaternion );
+ _v2.copy( _unitY ).applyQuaternion( space === 'local' ? this.worldQuaternion : _identityQuaternion );
+ _v3.copy( _unitZ ).applyQuaternion( space === 'local' ? this.worldQuaternion : _identityQuaternion );
+
+ // Align the plane for current transform mode, axis and space.
+
+ _alignVector.copy( _v2 );
+
+ switch ( this.mode ) {
+
+ case 'translate':
+ case 'scale':
+ switch ( this.axis ) {
+
+ case 'X':
+ _alignVector.copy( this.eye ).cross( _v1 );
+ _dirVector.copy( _v1 ).cross( _alignVector );
+ break;
+ case 'Y':
+ _alignVector.copy( this.eye ).cross( _v2 );
+ _dirVector.copy( _v2 ).cross( _alignVector );
+ break;
+ case 'Z':
+ _alignVector.copy( this.eye ).cross( _v3 );
+ _dirVector.copy( _v3 ).cross( _alignVector );
+ break;
+ case 'XY':
+ _dirVector.copy( _v3 );
+ break;
+ case 'YZ':
+ _dirVector.copy( _v1 );
+ break;
+ case 'XZ':
+ _alignVector.copy( _v3 );
+ _dirVector.copy( _v2 );
+ break;
+ case 'XYZ':
+ case 'E':
+ _dirVector.set( 0, 0, 0 );
+ break;
+
+ }
+
+ break;
+ case 'rotate':
+ default:
+ // special case for rotate
+ _dirVector.set( 0, 0, 0 );
+
+ }
+
+ if ( _dirVector.length() === 0 ) {
+
+ // If in rotate mode, make the plane parallel to camera
+ this.quaternion.copy( this.cameraQuaternion );
+
+ } else {
+
+ _tempMatrix.lookAt( _tempVector.set( 0, 0, 0 ), _dirVector, _alignVector );
+
+ this.quaternion.setFromRotationMatrix( _tempMatrix );
+
+ }
+
+ super.updateMatrixWorld( force );
+
+ }
+
+}
+
+TransformControlsPlane.prototype.isTransformControlsPlane = true;
+
+export { TransformControls, TransformControlsGizmo, TransformControlsPlane };
diff --git a/public/js/lib/ml/lalolib.js b/public/js/lib/ml/lalolib.js
new file mode 100644
index 0000000..a392383
--- /dev/null
+++ b/public/js/lib/ml/lalolib.js
@@ -0,0 +1,16501 @@
+/////////////////////////////////////
+/// Stand alone lalolib base functions
+////////////////////////////////////
+var printPrecision = 3; // number of digits to print
+
+var LALOLibPlotsIndex = 0;
+var LALOLibPlots = new Array();
+var LALOLABPLOTMOVING = false;
+
+//////////////////////////
+//// Cross-browser compatibility
+///////////////////////////
+
+if( typeof(console) == "undefined" ) {
+ // for Safari
+ var console = {log: function ( ) { } };
+}
+
+if( typeof(Math.sign) == "undefined" ) {
+ // for IE, Safari
+ Math.sign = function ( x ) { return ( x>=0 ? (x==0 ? 0 : 1) : -1 ) ;}
+}
+
+//////////////////////////
+//// printing
+///////////////////////////
+
+function laloprint( x , htmlId, append ) {
+ /*
+ use print(x) to print to the standard LALOLabOutput
+
+ use print(x, id) to print to another html entity
+
+ use str = print(x, true) to get the resulting string
+ */
+
+ if ( typeof(htmlId) == "undefined" )
+ var htmlId = "LALOLibOutput";
+ if ( typeof(append) == "undefined" )
+ var append = true;
+
+ return printMat(x, size(x), htmlId, append ) ;
+}
+
+function printMat(A, size, htmlId, append) {
+ if (typeof(append) === "undefined")
+ var append = false;
+ if ( typeof(htmlId) == "undefined" || htmlId === true ) {
+ // return a string as [ [ .. , .. ] , [.. , ..] ]
+ if ( type(A) == "matrix" ) {
+ var str = "[";
+ var i;
+ var j;
+ var m = size[0];
+ var n = size[1];
+
+ for (i=0;i";
+ }
+ console.log(str);
+ return str;
+ }
+ }
+ else {
+ // Produce HTML code and load it in htmlId
+
+ var html = "";
+ var i;
+ var j;
+
+ /*if (domathjax) {
+ html = tex ( A ) ;
+ }
+ else {*/
+ if ( isScalar(A) ) {
+ html += A + " " ;
+ }
+ else if (type(A) == "vector" ) {
+ var n = size[0];
+
+ // Vector (one column)
+ for (i=0;i";
+ }
+ }
+ else {
+ // Matrix
+ var m = size[0];
+ var n = size[1];
+
+ for (i=0;i";
+ }
+ }
+ //}
+ if (append)
+ document.getElementById(htmlId).innerHTML += html;
+ else
+ document.getElementById(htmlId).innerHTML = html;
+ /*
+ if ( domathjax)
+ MathJax.Hub.Queue(["Typeset",MathJax.Hub,"output"]);
+ */
+ }
+}
+
+function printNumber ( x ) {
+ switch ( typeof(x) ) {
+ case "undefined":
+ return "" + 0;// for sparse matrices
+ break;
+ case "string":
+ /*if ( domathjax )
+ return "\\verb&" + x + "&";
+ else*/
+ return x;
+ break;
+ case "boolean":
+ return x;
+ break;
+ default:
+ if ( x == Infinity )
+ return "Inf";
+ if ( x == -Infinity )
+ return "-Inf";
+ var x_int = Math.floor(x);
+ if ( Math.abs( x - x_int ) < 2.23e-16 ) {
+ return "" + x_int;
+ }
+ else
+ return x.toFixed( printPrecision );
+
+ break;
+ }
+}
+
+//// Error handling
+
+function error( msg ) {
+ throw new Error ( msg ) ;
+// postMessage( {"error": msg} );
+}
+
+
+///////////
+// Plots
+//////////
+function plot(multiargs) {
+ // plot(x,y,"style", x2,y2,"style",y3,"style",... )
+
+ // Part copied from lalolabworker.js
+
+ var data = new Array();
+ var styles = new Array();
+ var legends = new Array();
+ var minX = Infinity;
+ var maxX = -Infinity;
+ var minY = Infinity;
+ var maxY = -Infinity;
+
+ var p=0; // argument pointer
+ var x;
+ var y;
+ var style;
+ var i;
+ var n;
+ var c = 0; // index of current curve
+ while ( p < arguments.length) {
+
+ if ( type( arguments[p] ) == "vector" ) {
+
+ if ( p + 1 < arguments.length && type ( arguments[p+1] ) == "vector" ) {
+ // classic (x,y) arguments
+ x = arguments[p];
+ y = arguments[p+1];
+
+ p++;
+ }
+ else {
+ // only y provided => x = 0:n
+ y = arguments[p];
+ x = range(y.length);
+ }
+ }
+ else if ( type( arguments[p] ) == "matrix" ) {
+ // argument = [x, y]
+ if ( arguments[p].n == 1 ) {
+ y = arguments[p].val;
+ x = range(y.length);
+ }
+ else if (arguments[p].m == 1 ) {
+ y = arguments[p].val;
+ x = range(y.length);
+ }
+ else if ( arguments[p].n == 2 ) {
+ // 2 columns => [x,y]
+ x = getCols(arguments[p], [0]);
+ y = getCols(arguments[p], [1]);
+ }
+ else {
+ // more columns => trajectories as rows
+ x = range(arguments[p].n);
+ for ( var row = 0; row < arguments[p].m; row++) {
+ y = arguments[p].row(row);
+ data[c] = [new Array(x.length), new Array(x.length)];
+ for ( i=0; i < x.length; i++) {
+ data[c][0][i] = x[i];
+ data[c][1][i] = y[i];
+ if ( x[i] < minX )
+ minX = x[i];
+ if(x[i] > maxX )
+ maxX = x[i];
+ if ( y[i] > maxY )
+ maxY = y[i];
+ if ( y[i] < minY )
+ minY = y[i];
+
+ }
+
+ styles[c] = undefined;
+ legends[c] = "";
+
+ // Next curve
+ c++;
+ }
+ p++;
+ continue;
+ }
+ }
+ else {
+ return "undefined";
+ }
+
+ //Style
+ style = undefined;
+ if ( p + 1 < arguments.length && type ( arguments[p+1] ) == "string" ) {
+ style = arguments[p+1];
+ p++;
+ }
+ legend = "";
+ if ( p + 1 < arguments.length && type ( arguments[p+1] ) == "string" ) {
+ legend = arguments[p+1];
+ p++;
+ }
+
+ // Add the curve (x,y, style) to plot
+ data[c] = [new Array(x.length), new Array(x.length)];
+ for ( i=0; i < x.length; i++) {
+ data[c][0][i] = x[i];
+ data[c][1][i] = y[i];
+ if ( x[i] < minX )
+ minX = x[i];
+ if(x[i] > maxX )
+ maxX = x[i];
+ if ( y[i] > maxY )
+ maxY = y[i];
+ if ( y[i] < minY )
+ minY = y[i];
+
+ }
+ styles[c] = style;
+ legends[c] = legend;
+
+ // Next curve
+ c++;
+ p++; // from next argument
+ }
+
+ var widthX = maxX-minX;
+ var widthY = Math.max( maxY-minY, 1);
+
+ maxX += 0.1*widthX;
+ minX -= 0.1*widthX;
+ maxY += 0.1*widthY;
+ minY -= 0.1*widthY;
+
+ if ( minY > 0 )
+ minY = -0.1*maxY;
+
+ if ( maxY < 0 )
+ maxY = -0.1*minY;
+
+ var scaleY = 0.9 * (maxX-minX) / (2*maxY);
+
+ var plotinfo = {"data" : data, "minX" : minX, "maxX" : maxX, "minY" : minY, "maxY": maxY, "styles" : styles, "legend": legends };
+
+ //////// Part from laloplots.html //////////
+
+ var plotid = "LALOLibPlot" + LALOLibPlotsIndex;
+ var legendwidth = 50;
+
+ LALOLibOutput.innerHTML += " ";
+
+ // prepare legend
+ var ylegend = 20;
+
+ // do plot
+
+ LALOLibPlots[LALOLibPlotsIndex] = new Plot(plotid) ;
+
+ LALOLibPlots[LALOLibPlotsIndex].setScalePlot(plotinfo.minX, plotinfo.maxX, 200, plotinfo.scaleY);
+ if ( plotinfo.minY && plotinfo.maxY ) {
+ LALOLibPlots[LALOLibPlotsIndex].view(plotinfo.minX, plotinfo.maxX, plotinfo.minY, plotinfo.maxY);
+ }
+
+ var colors = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,0];
+
+ var p;
+ var color;
+ for (p = 0; p= 0 ) {
+ linestyle = false;
+ plotinfo.styles[p] = plotinfo.styles[p].replace(".","");
+ }
+ if ( plotinfo.styles[p].indexOf("_") >= 0 ) {
+ pointstyle = false;
+ plotinfo.styles[p] = plotinfo.styles[p].replace("_","");
+ }
+ color = parseColor(plotinfo.styles[p]);
+
+ if ( color < 0 )
+ color = colors.splice(0,1)[0]; // pick next unused color
+ else
+ colors.splice(colors.indexOf(color),1); // remove this color
+ }
+ else
+ color = color = colors.splice(0,1)[0]; // pick next unused color
+
+ if ( typeof(color) == "undefined") // pick black if no next unused color
+ color = 0;
+
+ for ( i=0; i < plotinfo.data[p][0].length; i++) {
+ if ( pointstyle )
+ LALOLibPlots[LALOLibPlotsIndex].addPoint(plotinfo.data[p][0][i],plotinfo.data[p][1][i], color);
+ if ( linestyle && i < plotinfo.data[p][0].length-1 )
+ LALOLibPlots[LALOLibPlotsIndex].plot_line(plotinfo.data[p][0][i],plotinfo.data[p][1][i], plotinfo.data[p][0][i+1],plotinfo.data[p][1][i+1], color);
+ }
+
+
+ // Legend
+ if ( plotinfo.legend[p] != "" ) {
+ var ctx = document.getElementById("legend" +LALOLibPlotsIndex).getContext("2d");
+ setcolor(ctx, color);
+ ctx.lineWidth = "3";
+ if ( pointstyle ) {
+ ctx.beginPath();
+ ctx.arc( legendwidth/2 , ylegend, 5, 0, 2 * Math.PI , true);
+ ctx.closePath();
+ ctx.fill();
+ }
+ if( linestyle) {
+ ctx.beginPath();
+ ctx.moveTo ( 0,ylegend);
+ ctx.lineTo (legendwidth, ylegend);
+ ctx.stroke();
+ }
+ ylegend += 20;
+
+ document.getElementById("legendtxt" +LALOLibPlotsIndex).innerHTML += plotinfo.legend[p] + " ";
+ }
+ }
+ for ( var pi=0; pi <= LALOLibPlotsIndex; pi++)
+ LALOLibPlots[pi].replot();
+
+ // ZOOM
+ if(window.addEventListener)
+ document.getElementById(plotid).addEventListener('DOMMouseScroll', this.mousezoom, false);//firefox
+
+ //for IE/OPERA etc
+ document.getElementById(plotid).onmousewheel = this.mousezoom;
+
+ LALOLibPlotsIndex++;
+}
+
+// Color plot
+function colorplot(multiargs) {
+ // colorplot(x,y,z) or colorplot(X) or colorplot(..., "cmapname" )
+
+ // Part copied from lalolabworker.js
+
+ var minX = Infinity;
+ var maxX = -Infinity;
+ var minY = Infinity;
+ var maxY = -Infinity;
+ var minZ = Infinity;
+ var maxZ = -Infinity;
+
+ var x;
+ var y;
+ var z;
+ var i;
+
+ var t0 = type( arguments[0] );
+ if ( t0 == "matrix" && arguments[0].n == 3 ) {
+ x = getCols(arguments[0], [0]);
+ y = getCols(arguments[0], [1]);
+ z = getCols(arguments[0], [2]);
+ }
+ else if ( t0 == "matrix" && arguments[0].n == 2 && type(arguments[1]) == "vector" ) {
+ x = getCols(arguments[0], [0]);
+ y = getCols(arguments[0], [1]);
+ z = arguments[1];
+ }
+ else if (t0 == "vector" && type(arguments[1]) == "vector" && type(arguments[2]) == "vector") {
+ x = arguments[0];
+ y = arguments[1];
+ z = arguments[2];
+ }
+ else {
+ return "undefined";
+ }
+
+ var minX = min(x);
+ var maxX = max(x);
+ var minY = min(y);
+ var maxY = max(y);
+ var minZ = min(z);
+ var maxZ = max(z);
+
+ var widthX = maxX-minX;
+ var widthY = Math.max( maxY-minY, 1);
+
+ maxX += 0.1*widthX;
+ minX -= 0.1*widthX;
+ maxY += 0.1*widthY;
+ minY -= 0.1*widthY;
+
+ if ( minY > 0 )
+ minY = -0.1*maxY;
+
+ if ( maxY < 0 )
+ maxY = -0.1*minY;
+
+ var plotinfo = {"x" : x, "y": y, "z": z, "minX" : minX, "maxX" : maxX, "minY" : minY, "maxY": maxY, "minZ" : minZ, "maxZ" : maxZ };
+
+ //////// Part from laloplots.html //////////
+
+ var plotid = "LALOLibPlot" + LALOLibPlotsIndex;
+ var legendwidth = 50;
+
+
+ LALOLibOutput.innerHTML += " ";
+
+ LALOLibPlots[LALOLibPlotsIndex] = new ColorPlot(plotid) ;
+ LALOLibPlots[LALOLibPlotsIndex].setScale(plotinfo.minX, plotinfo.maxX, plotinfo.minY, plotinfo.maxY,plotinfo.minZ, plotinfo.maxZ);
+ LALOLibPlots[LALOLibPlotsIndex].view(plotinfo.minX, plotinfo.maxX, plotinfo.minY, plotinfo.maxY);
+
+ for (var i=0; i < plotinfo.x.length; i++)
+ LALOLibPlots[LALOLibPlotsIndex].addPoint(plotinfo.x[i],plotinfo.y[i],plotinfo.z[i]);
+
+ LALOLibPlots[LALOLibPlotsIndex].replot();
+
+ var legendwidth = 50;
+// plotlegend.innerHTML += plotinfo.maxZ.toFixed(3) + " " + plotinfo.minZ.toFixed(3);
+ var ctx = document.getElementById("legend" +LALOLibPlotsIndex).getContext("2d");
+
+ var legendcanvas = document.getElementById("legend"+LALOLibPlotsIndex);
+ if ( legendcanvas )
+ var legendheight = legendcanvas.height;
+ else
+ var legendheight = 500;
+
+ var y;
+ for (var i=0; i< LALOLibPlots[LALOLibPlotsIndex].cmap.length;i++) {
+ y = Math.floor(i * legendheight / LALOLibPlots[LALOLibPlotsIndex].cmap.length);
+ ctx.fillStyle = "rgb(" + LALOLibPlots[LALOLibPlotsIndex].cmap[i][0] + "," + LALOLibPlots[LALOLibPlotsIndex].cmap[i][1] + "," + LALOLibPlots[LALOLibPlotsIndex].cmap[i][2] + ")";
+ ctx.fillRect( 0, legendheight-y, legendwidth , (legendheight / LALOLibPlots[LALOLibPlotsIndex].cmap.length) + 1) ;
+ }
+
+ document.getElementById("legendmaxZ" + LALOLibPlotsIndex).innerHTML = plotinfo.maxZ.toPrecision(3);
+ document.getElementById("legendminZ" + LALOLibPlotsIndex).innerHTML = plotinfo.minZ.toPrecision(3);
+
+ if(window.addEventListener)
+ document.getElementById(plotid).addEventListener('DOMMouseScroll', this.mousezoom, false);//firefox
+
+ //for IE/OPERA etc
+ document.getElementById(plotid).onmousewheel = this.mousezoom;
+
+ LALOLibPlotsIndex++;
+}
+
+// 3D plot
+function plot3(multiargs) {
+ // plot3(x,y,z,"style", x2,y2,z2,"style",... )
+
+ var data = new Array();
+ var styles = new Array();
+ var legends = new Array();
+
+ var p=0; // argument pointer
+ var x;
+ var y;
+ var z;
+ var style;
+ var i;
+ var n;
+ var c = 0; // index of current curve
+ while ( p < arguments.length) {
+
+ if ( type( arguments[p] ) == "vector" ) {
+
+ if ( p + 2 < arguments.length && type ( arguments[p+1] ) == "vector" && type ( arguments[p+2] ) == "vector" ) {
+ // classic (x,y,z) arguments
+ x = arguments[p];
+ y = arguments[p+1];
+ z = arguments[p+2];
+
+ p += 2;
+ }
+ else {
+ return "undefined";
+ }
+ }
+ else if ( type( arguments[p] ) == "matrix" ) {
+ // argument = [x, y, z]
+ n = arguments[p].length;
+ x = new Array(n);
+ y = new Array(n);
+ z = new Array(n);
+ for ( i=0; i < n; i++) {
+ x[i] = get(arguments[p], i, 0);
+ y[i] = get(arguments[p], i, 1);
+ z[i] = get(arguments[p], i, 2);
+ }
+ }
+ else {
+ return "undefined";
+ }
+
+ //Style
+ style = undefined;
+ if ( p + 1 < arguments.length && type ( arguments[p+1] ) == "string" ) {
+ style = arguments[p+1];
+ p++;
+ }
+ legend = "";
+ if ( p + 1 < arguments.length && type ( arguments[p+1] ) == "string" ) {
+ legend = arguments[p+1];
+ p++;
+ }
+
+ // Add the curve (x,y,z, style) to plot
+ data[c] = new Array();
+ for ( i=0; i < x.length; i++) {
+ data[c][i] = [x[i], y[i], z[i]];
+ }
+ styles[c] = style;
+ legends[c] = legend;
+
+ // Next curve
+ c++;
+ p++; // from next argument
+
+ }
+
+
+ var plotinfo = { "data" : data, "styles" : styles, "legend": legends };
+
+ //////// Part from laloplots.html //////////
+
+ var plotid = "LALOLibPlot" + LALOLibPlotsIndex;
+ var legendwidth = 50;
+
+ LALOLibOutput.innerHTML += ' ';
+
+ var ylegend = 20;
+
+ // do plot
+
+ LALOLibPlots[LALOLibPlotsIndex] = new Plot3D(plotid) ;
+
+ LALOLibPlots[LALOLibPlotsIndex].cameraDistance = 30;
+ LALOLibPlots[LALOLibPlotsIndex].angleX = Math.PI/10;
+ LALOLibPlots[LALOLibPlotsIndex].angleZ = Math.PI/10;
+
+ LALOLibPlots[LALOLibPlotsIndex].axisNameX1 = "x";
+ LALOLibPlots[LALOLibPlotsIndex].axisNameX2 = "y";
+ LALOLibPlots[LALOLibPlotsIndex].axisNameX3 = "z";
+
+
+ var colors = [1,2,3,4,5,0];
+
+ var p;
+ var color;
+
+ for (p = 0; p= 0 ) {
+ linestyle = false;
+ plotinfo.styles[p] = plotinfo.styles[p].replace(".","");
+ }
+ if ( plotinfo.styles[p].indexOf("_") >= 0 ) {
+ pointstyle = false;
+ plotinfo.styles[p] = plotinfo.styles[p].replace("_","");
+ }
+ color = parseColor(plotinfo.styles[p]);
+
+ if ( color < 0 )
+ color = colors.splice(0,1)[0]; // pick next unused color
+ else
+ colors.splice(colors.indexOf(color),1); // remove this color
+ }
+ else
+ color = color = colors.splice(0,1)[0]; // pick next unused color
+
+ if ( typeof(color) == "undefined") // pick black if no next unused color
+ color = 0;
+
+ for ( i=0; i < plotinfo.data[p].length; i++) {
+ if ( pointstyle ) {
+ LALOLibPlots[LALOLibPlotsIndex].X.push( plotinfo.data[p][i] );
+ LALOLibPlots[LALOLibPlotsIndex].Y.push( color );
+ }
+ if ( linestyle && i < plotinfo.data[p].length-1 )
+ LALOLibPlots[LALOLibPlotsIndex].plot_line(plotinfo.data[p][i], plotinfo.data[p][i+1], "", color);
+ }
+
+ // Legend
+ if ( plotinfo.legend[p] != "" ) {
+ var ctx = document.getElementById("legend" +LALOLibPlotsIndex).getContext("2d");
+ setcolor(ctx, color);
+ ctx.lineWidth = "3";
+ if ( pointstyle ) {
+ ctx.beginPath();
+ ctx.arc( legendwidth/2 , ylegend, 5, 0, 2 * Math.PI , true);
+ ctx.closePath();
+ ctx.fill();
+ }
+ if( linestyle) {
+ ctx.beginPath();
+ ctx.moveTo ( 0,ylegend);
+ ctx.lineTo (legendwidth, ylegend);
+ ctx.stroke();
+ }
+ ylegend += 20;
+
+ document.getElementById("legendtxt" +LALOLibPlotsIndex).innerHTML += plotinfo.legend[p] + " ";
+ }
+ }
+ LALOLibPlots[LALOLibPlotsIndex].computeRanges();
+ LALOLibPlots[LALOLibPlotsIndex].replot();
+
+ LALOLibPlotsIndex++;
+}
+
+// image
+function image(X, title) {
+ if (type(X) == "vector") {
+ X = mat([X]);
+ }
+
+ var style;
+ var minX = min(X);
+ var maxX = max(X);
+ var m = X.length;
+ var n = X.n;
+ var scale = (maxX - minX) ;
+
+ var i;
+ var j;
+ var k = 0;
+ var data = new Array();
+ for ( i=0; i < m; i++) {
+ var Xi = X.row(i);
+ for ( j=0; j < n; j++) { // could do for j in X[i] if colormap for 0 is white...
+ color = mul( ( Xi[j] - minX) / scale, ones(3) ) ;
+ data[k] = [i/m, j/n, color];
+ k++;
+ }
+ }
+ style = [m,n,minX,maxX];
+
+ var imagedata = { "data" : data, "style" : style, "title": title };
+
+ ////// Part from laloplots.html
+
+
+ var plotid = "LALOLibPlot" + LALOLibPlotsIndex;
+ var legendwidth = 50;
+ var pixWidth ;
+ var pixHeight ;
+
+ // prepare legend
+ var ylegend = 20;
+
+ // do plot
+
+
+ var i;
+
+ var width = 500;
+ var height = 500;
+
+ var title = imagedata.title;
+ if(title) {
+ LALOLibOutput.innerHTML += ""+title+" " + " ( " + imagedata.style[0] + " by " + imagedata.style[1] + " matrix )";
+ }
+
+ if ( imagedata.style[1] > width ) {
+ width = imagedata.style[1];
+ plotlegend.style.left = (width+60) +"px";
+ }
+ if ( imagedata.style[0] > height )
+ height = imagedata.style[0];
+
+ pixWidth = width / imagedata.style[1];
+ pixHeight = height / imagedata.style[0];
+
+ var legendwidth = 50;
+
+ LALOLibOutput.innerHTML += ' ' + imagedata.style[2].toFixed(3) + ' ' + imagedata.style[3].toFixed(3) + '
';
+
+ var x;
+ var y;
+ var color;
+
+ LALOLibPlots[LALOLibPlotsIndex] = imagedata;
+ LALOLibPlots[LALOLibPlotsIndex].canvasId = plotid;
+ var canvas = document.getElementById(plotid);
+
+ if (canvas.getContext) {
+ var ctx = canvas.getContext("2d");
+
+ for ( i=0; i < imagedata.data.length ; i++) {
+ x = canvas.width * LALOLibPlots[LALOLibPlotsIndex].data[i][1];
+ y = canvas.height * LALOLibPlots[LALOLibPlotsIndex].data[i][0] ;
+ color = LALOLibPlots[LALOLibPlotsIndex].data[i][2];
+
+ ctx.fillStyle = "rgb(" + Math.floor(255*(1-color[0])) + "," + Math.floor(255*(1-color[1])) + "," + Math.floor(255*(1-color[2])) + ")";
+ ctx.fillRect( x , y, pixWidth +1, pixHeight +1); // +1 to avoid blank lines between pixels
+
+ }
+ }
+
+ // add legend / colormap
+
+ var legend = document.getElementById("legend" +LALOLibPlotsIndex);
+ var ctx = legend.getContext("2d");
+
+ for ( i=0; i< 255;i++) {
+ y = Math.floor(i * legend.height / 255);
+ ctx.fillStyle = "rgb(" + (255-i) + "," + (255-i) + "," + (255-i) + ")";
+ ctx.fillRect( 0, y, legendwidth , (legend.height / 255) + 1) ;
+ }
+
+ // Prepare mouseposition info
+ LALOLibPlots[LALOLibPlotsIndex].pixelWidth = pixWidth;
+ LALOLibPlots[LALOLibPlotsIndex].pixelHeight = pixHeight;
+ LALOLibPlotsIndex++;
+}
+
+
+
+function parseColor( str ) {
+ if ( typeof(str) == "undefined")
+ return -1;
+
+ var color;
+ switch( str ) {
+ case "k":
+ case "black":
+ color = 0;
+ break;
+ case "blue":
+ case "b":
+ color = 1;
+ break;
+ case "r":
+ case "red":
+ color = 2;
+ break;
+ case "g":
+ case "green":
+ color = 3;
+ break;
+ case "m":
+ case "magenta":
+ color = 4;
+ break;
+ case "y":
+ case "yellow":
+ color = 5;
+ break;
+
+ default:
+ color = -1;
+ break;
+ }
+ return color;
+}
+
+function mousezoom ( e, delta , plotidx) {
+ if (!e)
+ e = window.event;
+
+ e.preventDefault();
+
+ if ( typeof(plotidx) == "undefined")
+ var plotidx = 0;
+
+ if ( typeof(delta) == "undefined") {
+ var delta = 0;
+
+ // normalize the delta
+ if (e.wheelDelta) {
+ // IE and Opera
+ delta = e.wheelDelta / 30;
+ }
+ else if (e.detail) {
+ delta = -e.detail ;
+ }
+ }
+ else {
+ if (e.button != 0 )
+ delta *= -1;
+ }
+
+ var plotcanvas = document.getElementById(LALOLibPlots[plotidx].canvasId);
+ var rect = plotcanvas.getBoundingClientRect();
+ var x = e.clientX - rect.left; // mouse coordinates relative to plot
+ var y = e.clientY - rect.top;
+ LALOLibPlots[plotidx].zoom(1+delta/30,1+delta/30, x, y);
+}
+function zoomoriginal(plotidx) {
+ LALOLibPlots[plotidx].resetzoom();
+}
+function mouseposition( e , plotidx) {
+ var plotcanvas = document.getElementById(LALOLibPlots[plotidx].canvasId);
+ var rect = plotcanvas.getBoundingClientRect();
+
+ var xmouse = e.clientX - rect.left; // mouse coordinates relative to plot
+ var ymouse = e.clientY - rect.top;
+
+ if ( LALOLABPLOTMOVING ) {
+ var dx = xmouse - LALOLABPLOTxprev ;
+ var dy = ymouse - LALOLABPLOTyprev;
+ if ( Math.abs( dx ) > 1 || Math.abs( dy ) > 1 ) {
+ LALOLibPlots[plotidx].translate(dx, dy);
+ }
+ LALOLABPLOTxprev = xmouse;
+ LALOLABPLOTyprev = ymouse;
+ }
+ else {
+ var x = xmouse / LALOLibPlots[plotidx].scaleX + LALOLibPlots[plotidx].minX;
+ var y = (plotcanvas.height - ymouse ) / LALOLibPlots[plotidx].scaleY + LALOLibPlots[plotidx].minY;
+
+ document.getElementById("lblposition" + plotidx).innerHTML = "x = " + x.toFixed(3) + ", y = " + y.toFixed(3);
+ }
+}
+
+function mousestartmove( e , plotidx) {
+ if ( e.button == 0 ) {
+ LALOLABPLOTMOVING = true;
+ var plotcanvas = document.getElementById(LALOLibPlots[plotidx].canvasId);
+ var rect = plotcanvas.getBoundingClientRect();
+ LALOLABPLOTxprev = e.clientX - rect.left; // mouse coordinates relative to plot
+ LALOLABPLOTyprev = e.clientY - rect.top;
+ }
+ else {
+ LALOLABPLOTMOVING = false;
+ }
+}
+function mousestopmove( e ) {
+ LALOLABPLOTMOVING = false;
+}
+
+function mouseimageposition( e, plotidx ) {
+ var plotcanvas = document.getElementById(LALOLibPlots[plotidx].canvasId);
+ var rect = plotcanvas.getBoundingClientRect();
+
+ var xmouse = e.clientX - rect.left; // mouse coordinates relative to plot
+ var ymouse = e.clientY - rect.top;
+
+ var n = LALOLibPlots[plotidx].style[1];
+ var minX = LALOLibPlots[plotidx].style[2];
+ var maxX = LALOLibPlots[plotidx].style[3];
+ var i = Math.floor(ymouse / LALOLibPlots[plotidx].pixelHeight);
+ var j = Math.floor(xmouse / LALOLibPlots[plotidx].pixelWidth );
+ if ( j < n ) {
+ var val = LALOLibPlots[plotidx].data[i*n + j][2][0]*(maxX - minX) + minX;
+
+ document.getElementById("lblposition" + plotidx).innerHTML = "Matrix[ " + i + " ][ " + j + " ] = " + val.toFixed(3);
+ }
+}
+/////////////////////////////////
+//// Parser
+////////////////////////////////
+
+function lalo( Command ) {
+ // Parse command line and execute in current scopes
+ var cmd = laloparse( Command );
+ var res = self.eval(cmd);
+ return res;
+}
+function laloparse( WorkerCommand ) {
+ // Parse Commands
+ var WorkerCommandList = WorkerCommand.split("\n");
+ var k;
+ var cmd = "";
+ for (k = 0; k 0 ) {
+ if ( WorkerCommandList[k].indexOf("{") >= 0 || WorkerCommandList[k].indexOf("}") >= 0) {
+ // this line includes braces => plain javascript: do not parse it!
+ cmd += WorkerCommandList[k];
+ if ( WorkerCommandList[k].indexOf("}") >= 0 ) {
+ // braces closed, we can end the line
+ cmd += " ;\n";
+ }
+ }
+ else {
+ // standard lalolab line
+ cmd += parseCommand(WorkerCommandList[k]) + " ;\n";
+ }
+ }
+ }
+ return cmd;
+}
+function parseSplittedCommand( cmd ) {
+ //console.log("parsing : " + cmd);
+ // !!! XXX should parse unary ops before all the others !!!
+
+ var ops = ["==", "!=", ">=" ,"<=", ">", "<" , "\\" ,":", "+", "-", ".*", "*", "./" , "^", "'"]; // from lowest priority to highest
+ var opsFcts = ["isEqual" , "isNotEqual", "isGreaterOrEqual", "isLowerOrEqual", "isGreater" , "isLower", "solve","range", "add", "sub", "entrywisemul", "mul" , "entrywisediv", "pow", "undefined" ];
+ var unaryOpsFcts = ["", "", "", "", "","", "","range","", "minus", "", "" , "", "", "transpose" ];
+
+ var o;
+ var i ;
+ var k;
+ var operandA;
+ var operandB;
+
+ for ( o = 0; o < ops.length; o++) {
+
+ var splitted_wrt_op = cmd.split(ops[o]);
+
+ if ( splitted_wrt_op.length > 1) {
+ if ( removeSpaces(splitted_wrt_op[0]) != "" ) {
+ // there is actually a left-hand side operand
+ if( removeSpaces(splitted_wrt_op[1]) != "" ) {
+ // and a right-hand side operand
+ operandA = parseSplittedCommand(splitted_wrt_op[0]);
+
+ for ( k = 1; k< splitted_wrt_op.length ; k++) {
+ operandB = splitted_wrt_op[k];
+ operandA = opsFcts[o] + "(" + operandA + "," + parseSplittedCommand(operandB) + ")";
+ }
+ cmd = operandA;
+ }
+ else {
+ // no right-hand side: like transpose operator
+ cmd = unaryOpsFcts[o] + "(" + parseSplittedCommand(splitted_wrt_op[0]) + ")";
+ }
+ }
+ else {
+ // no left operand: like minus something...
+
+ // Apply unary operator
+ operandA = unaryOpsFcts[o] + "(" + parseSplittedCommand(splitted_wrt_op[1]) + ")";
+
+ // and then binary operator for the remaining occurences
+ for ( k = 2; k< splitted_wrt_op.length ; k++) {
+ operandB = splitted_wrt_op[k];
+ operandA = opsFcts[o] + "(" + operandA + "," + parseSplittedCommand(operandB) + ")";
+ }
+ cmd = operandA;
+ }
+ }
+ }
+
+ return cmd;
+
+}
+
+function parseAssignment ( assignmentStr ) {
+ if ( assignmentStr.indexOf("[") < 0 ) {
+ // straightforward assignment
+ return assignmentStr;
+ }
+ else {
+ var assign = removeSpaces(assignmentStr).replace("=","").replace(",","][");
+ var middle = assign.indexOf("][");
+ var start = assign.indexOf("[");
+ var varname = assign.substr(0,start);
+ if ( middle >= 0 ) {
+ // submatrix assignment
+ var rowsrange = assign.substr( start + 1, middle-start-1);
+
+ // find last "]";
+ var end = middle+1;
+ while ( assign.indexOf("]",end+1) >= 0)
+ end = assign.indexOf("]",end+1);
+
+ var colsrange = assign.substr(middle+2, end - (middle+2)); // everything after "][" and before last "]"
+
+ // Parse colon ranges
+ var rowssplit = rowsrange.split(":");
+ if (rowssplit.length == 2 ){
+ if ( rowssplit[0] =="" && rowssplit[1] =="" )
+ rowsrange = "[]";
+ else
+ rowsrange = "range(" + rowssplit[0] + "," + rowssplit[1] + ")";
+ }
+ else if ( rowssplit.length == 3)
+ rowsrange = "range(" + rowssplit[0] + "," + rowssplit[2] + "," + rowssplit[1] + ")";
+
+ var colssplit = colsrange.split(":");
+ if (colssplit.length == 2 ) {
+ if ( colssplit[0] =="" && colssplit[1] =="" )
+ colsrange = "[]";
+ else
+ colsrange = "range(" + colssplit[0] + "," + colssplit[1] + ")";
+ }
+ else if ( colssplit.length == 3)
+ colsrange = "range(" + colssplit[0] + "," + colssplit[2] + "," + colssplit[1] + ")";
+
+ return "set( " + varname + "," + rowsrange + "," + colsrange + ", ";
+ }
+ else {
+ // subvector assignment
+
+ // find last "]";
+ var end = start;
+ while ( assign.indexOf("]",end+1) >= 0)
+ end = assign.indexOf("]",end+1);
+
+ var rowsrange = assign.substr( start + 1, end-start-1);
+
+ // Parse colon ranges
+ var rowssplit = rowsrange.split(":");
+ if (rowssplit.length == 2 ){
+ if ( rowssplit[0] =="" && rowssplit[1] =="" )
+ rowsrange = "[]";
+ else
+ rowsrange = "range(" + rowssplit[0] + "," + rowssplit[1] + ")";
+ }
+ else if ( rowssplit.length == 3)
+ rowsrange = "range(" + rowssplit[0] + "," + rowssplit[2] + "," + rowssplit[1] + ")";
+
+ return "set( " + varname + "," + rowsrange + ", ";
+ }
+ }
+}
+
+function parseBrackets( cmdString ) {
+ // Parse brackets => get matrix entries
+
+ var delimiters = ["[", "(",",",";",")", "\\", "+", "-", "*", "/", ":", "^", "'", "=", ">", "<", "!"];
+
+ cmdString = cmdString.split("][").join(","); // replace ][ by , and ] by )
+
+ var cmd = cmdString.split(""); // string to array of char
+
+ var i;
+ var j;
+ var k;
+ var l;
+ var lhs;
+
+ // For the entire string:
+ i = cmd.length - 1;
+ while ( i >= 0 ) {
+ // Search for the right-most opening bracket:
+ while ( i >= 0 && cmd[i] != "[" )
+ i--;
+
+ if ( i >= 0 ) {
+ // found a bracket, find its corresponding closing bracket
+ j = i+1;
+ while ( j < cmd.length && cmd[j] != "]" )
+ j++;
+
+ if ( j < cmd.length ) {
+
+ // then determine its left-hand side operand:
+ l = 0;
+ k = 0;
+ while ( k < i ) {
+ if ( delimiters.indexOf(cmd[k]) >= 0)
+ l = k+1;
+ k++;
+ }
+ lhs = cmd.slice(l,i).join(""); // should be LHS as string or "" if l >= i
+
+ if ( removeSpaces(lhs) == "" ) {
+ // if the LHS operand is empty, leave the brackets untouched
+ cmd[i] = "#"; // (replace by # and $ re-replace at the end by a matrix creation)
+
+ // look for semicolon within brackets:
+ k = i+1;
+ var rowwise = false;
+ var colwise = false;
+ while ( k < j ) {
+ if( cmd[k] == "," ) {
+ //colwise = true;
+ }
+
+ if ( cmd[k] == ";" ) {
+ rowwise = true; // mark for rowwise mat
+
+ if ( colwise ) {
+ cmd.splice(k,1, ["@", ","] ); // end previous row vector, replace by comma
+ colwise = false;
+ }
+ else {
+ cmd[k] = ","; // simply replace by comma
+ }
+ }
+
+
+ k++;
+ }
+
+ if ( rowwise )
+ cmd[j] = "$";
+ else
+ cmd[j] = "@";
+
+ }
+ else {
+ // if not empty, implement a GET
+ cmd[l]="get(" + lhs ;
+ for ( k = l+1; k < i; k++)
+ cmd[k] = "";
+ cmd[i] = ",";
+ cmd[j] = ")";
+ }
+ }
+ else {
+ return undefined; // error no ending bracket;
+ }
+ }
+ i--;
+ }
+
+ var cmdparsed = cmd.join("").split("#").join("mat([").split("$").join("], true)").split("@").join("])");
+ //console.log(cmdparsed);
+ return cmdparsed;
+}
+
+function parseCommand( cmdString ) {
+
+ // Remove comments at the end of the line
+ var idxComments = cmdString.indexOf("//");
+ if ( idxComments >= 0 )
+ cmdString = cmdString.substr(0,idxComments);
+
+
+ // Parse "=" sign to divide between assignement String and computeString
+ var idxEqual = cmdString.split("==")[0].split("!=")[0].split(">=")[0].split("<=")[0].indexOf("=");
+ if ( idxEqual > 0 ) {
+ var assignmentStr = parseAssignment( cmdString.substr(0,idxEqual + 1) );
+ var computeStr = cmdString.substr(idxEqual+1);
+
+ // Check for simple assignments like A = B to force copy
+ if ( assignmentStr.indexOf("set(") < 0 && typeof(self[removeSpaces(computeStr)]) != "undefined" ) { //self.hasOwnProperty( removeSpaces(computeStr) ) ) { // self.hasOwnProperty does not work in Safari workers....
+
+ // computeStr is a varaible name
+ if ( !isScalar(self[ removeSpaces(computeStr) ] ) ) {
+ // the variable is a vector or matrix
+ var FinalCommand = assignmentStr + "matrixCopy(" + computeStr + ")";
+ console.log(FinalCommand);
+ return FinalCommand;
+ }
+ }
+ }
+ else {
+ var assignmentStr = "";
+ var computeStr = cmdString;
+ }
+
+ // parse brackets:
+ var cmd = parseBrackets( computeStr ).split(""); // and convert string to Array
+
+ // Parse delimiters
+ var startdelimiters = ["(","[",",",";"];
+ var enddelimiters = [")","]",",",";"];
+ var i;
+ var j;
+ var k;
+ var parsedContent = "";
+ var parsedCommand = new Array(cmd.length);
+
+ var map = new Array(cmd.length ) ;
+ for ( k=0;k= 0 ) {
+ // Find the most right starting delimiter
+ while ( i >= 0 && startdelimiters.indexOf(cmd[i]) < 0 )
+ i--;
+ if ( i >= 0 ) {
+ // found a delimiter, search for the closest ending delimiter
+ j = i+1;
+ while ( j < cmd.length && enddelimiters.indexOf(cmd[j] ) < 0 ) {
+ j++;
+ }
+ if ( j < cmd.length ) {
+ // starting delimiter is at cmd[i] and ending one at cmd[j]
+
+ // parse content within delimiters
+ parsedContent = parseSplittedCommand( parsedCommand.slice(map[i]+1,map[j]).join("") ) ;
+ // and replace the corresponding content in the parsed command
+ parsedCommand.splice (map[i]+1, map[j]-map[i]-1, parsedContent ) ;
+
+ // remove delimiters from string to be parsed
+ if ( cmd[i] != "," )
+ cmd[i] = " "; // except for commas that serve twice (once as start once as end)
+ cmd[j] = " ";
+
+ // map position in the original cmd to positions in the parsedCommand to track brackets
+ for ( k=i+1; k < j;k++)
+ map[k] = map[i]+1;
+ var deltamap = map[j] - map[i] - 1;
+ for ( k=j; k < cmd.length;k++)
+ map[k] += 1 - deltamap;
+
+ /*console.log(parsedCommand);
+ console.log(cmd.join(""));
+ console.log(map);
+ console.log(i + " : " + j);*/
+ }
+ else {
+ return "undefined";
+ }
+ }
+ i--;
+ }
+ var FinalCommand = assignmentStr + parseSplittedCommand(parsedCommand.join(""));
+
+ // Parse brackets => get matrix entries
+ //cmdString = cmdString.split("][").join(",").split("]").join(")"); // replace ][ by , and ] by )
+ // consider [ as a left-hand unary operator
+// cmd = "get(" + parseSplittedCommand(splitted_wrt_op[0]) + ")";
+
+
+
+ if ( assignmentStr.substr(0,4) == "set(" )
+ FinalCommand += " )";
+
+ FinalCommand = parseRangeRange( FinalCommand );
+
+ console.log(FinalCommand);
+ return FinalCommand;
+}
+
+function parseRangeRange( cmd ) {
+ // parse complex ranges like 0:0.1:4
+ var elems = cmd.split("range(range(");
+ var i;
+ var j;
+ var tmp;
+ var args;
+ var incargs;
+ var endargs;
+ for ( i = 0; i< elems.length - 1 ; i++) {
+
+// elems[i+1] = elems[i+1].replace(")","");
+
+ // ivert second and third arguments to get range(start, end, inc) from start:inc:end
+ args = elems[i+1].split(",");
+ tmp = args[2].split(")"); // keep only the content of the range and not the remaining commands
+ endargs = tmp[0];
+ j = 0; // deal with cases like end="minus(4)" where the first closing bracket is not at the complete end
+ while ( tmp[j].indexOf("(") >= 0 ) {
+ endargs = endargs + ")" + tmp[j+1];
+ j++;
+ }
+
+ incargs = args[1].substr(0,args[1].length-1); // remove ")"
+ args[1] = endargs;
+ //endargs[0] = incargs;
+ args[2] = incargs + ")" + tmp.slice(j+1).join(")");
+ elems[i+1] = args.join(",");
+ }
+ return elems.join("range(");//replace range(range( by range(
+}
+
+function removeSpaces( str ) {
+ return str.split(" ").join("");
+}
+
+////////////////////////////
+/// Lab
+////////////////////////////
+function MLlab ( id , path ) {
+ var that = new Lalolab ( id, true, path);
+ return that;
+}
+function Lalolab ( id, mllab , path ) {
+ // constructor for a Lab with independent scope running in a worker
+ this.id = id;
+
+ this.callbacks = new Array();
+
+ // Create worker with a Blob to avoid distributing lalolibworker.js
+ // => does not work due to importScripts with relative path to the Blob unresolved (or cross-origin)
+
+ if ( typeof(path) == "undefined" )
+ var path = "http://mlweb.loria.fr/";
+ else {
+ if (path.length > 0 && path[path.length-1] != "/" )
+ path = [path,"/"].join("");
+ }
+
+ if ( typeof(mllab) != "undefined" && mllab ) {
+ this.worker = new Worker(path+"mlworker.js"); // need mlworker.js in same directory as web page
+ this.labtype = "ml";
+ /* Using a Blob to avoid distributing mlworker.js:
+ does not work because of importScripts from cross origin...
+ var workerscript = "importScripts(\"ml.js\");\n onmessage = function ( WorkerEvent ) {\n var WorkerCommand = WorkerEvent.data.cmd;var mustparse = WorkerEvent.data.parse; \n if ( mustparse )\n var res = lalo(WorkerCommand);\n else {\n if ( WorkerCommand == \"load_mat\" ) {\n if ( type(WorkerEvent.data.data) == \"matrix\" )\n var res = new Matrix(WorkerEvent.data.data.m,WorkerEvent.data.data.n,WorkerEvent.data.data.val, true);\nelse\n var res = mat(WorkerEvent.data.data, true);\n eval(WorkerEvent.data.varname + \"=res\");\n}\n else\n var res = self.eval( WorkerCommand ) ;\n}\n try {\n postMessage( { \"cmd\" : WorkerCommand, \"output\" : res } );\n} catch ( e ) {\n try {\n postMessage( { \"cmd\" : WorkerCommand, \"output\" : res.info() } );\n } catch(e2) { \n postMessage( { \"cmd\" : WorkerCommand, \"output\" : undefined } );\n}\n}\n}";
+ var blob = new Blob([workerscript], { "type" : "text/javascript" });
+ var blobURL = window.URL.createObjectURL(blob);
+ console.log(blobURL);
+ this.worker = new Worker(blobURL);*/
+ }
+ else {
+ this.worker = new Worker(path+"lalolibworker.js"); // need lalolibworker.js in same directory as web page
+ this.labtype = "lalo";
+ }
+ this.worker.onmessage = this.onresult;
+ this.worker.parent = this;
+}
+Lalolab.prototype.close = function ( ) {
+ this.worker.terminate();
+ this.worker.parent = null;// delete circular reference
+}
+Lalolab.prototype.onprogress = function ( ratio ) {
+ // do nothing by default;
+ // user must set lab.onprogress = function (ratio) { ... } to do something
+}
+Lalolab.prototype.onresult = function ( WorkerEvent ) {
+// console.log(WorkerEvent, ""+ this.parent.callbacks);
+ if ( typeof(WorkerEvent.data.progress) != "undefined" ) {
+ this.parent.onprogress( WorkerEvent.data.progress ) ;
+ }
+ else {
+ var cb = this.parent.callbacks.splice(0,1)[0] ; // take first callback from the list
+ if ( typeof(cb) == "function" ) {
+ var WorkerCommand = WorkerEvent.data.cmd;
+ var WorkerResult = WorkerEvent.data.output;
+ cb( WorkerResult, WorkerCommand, this.parent.id ); // call the callback if present
+ }
+ }
+}
+Lalolab.prototype.do = function ( cmd , callback ) {
+ // prepare callback, parse cmd and execute in worker
+ this.callbacks.push( callback ) ;
+ this.worker.postMessage( {cmd: cmd, parse: true} );
+}
+Lalolab.prototype.exec = function ( cmd , callback ) {
+ // prepare callback, and execute cmd in worker
+ this.callbacks.push( callback );
+ this.worker.postMessage( {cmd: cmd, parse: false} );
+}
+Lalolab.prototype.parse = function ( cmd , callback ) {
+ // prepare callback, parse cmd and execute in worker
+ this.callbacks.push( callback );
+ this.worker.postMessage( {cmd: cmd, parse: false} );
+}
+Lalolab.prototype.load = function ( data , varname, callback ) {
+ // load data in varname
+ this.callbacks.push( callback ) ;
+ if ( typeof(data) == "string" ){
+ this.worker.postMessage( {"cmd" : varname + "= load_data (\"" + data + "\")", parse: false} );
+ }
+ else {
+ this.worker.postMessage( {"cmd" : "load_mat", data: data, varname: varname, parse: false} );
+ }
+}
+Lalolab.prototype.import = function ( script, callback ) {
+ // load a script in lalolib language
+ this.do('importLaloScript("' + script + '")', callback);
+}
+function importLaloScript ( script ) {
+ // load a script in lalolib language in the current Lab worker
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', script, false);
+ xhr.send();
+ var cmd = xhr.responseText;
+ return lalo(cmd);
+}
+Lalolab.prototype.importjs = function ( script, callback ) {
+ // load a script in javascript
+ this.exec("importScripts('" + script + "');", callback);
+}
+Lalolab.prototype.getObject = function ( varname, callback ) {
+ this.exec("getObjectWithoutFunc(" + varname +")", function (res) {callback(renewObject(res));} );
+}
+
+function getObjectWithoutFunc( obj ) {
+ // Functions and Objects with function members cannot be sent
+ // from one worker to another...
+
+ if ( typeof(obj) != "object" )
+ return obj;
+ else {
+ var res = {};
+
+ for (var p in obj ) {
+ switch( type(obj[p]) ) {
+ case "vector":
+ res[p] = {type: "vector", data: [].slice.call(obj[p])};
+ break;
+ case "matrix":
+ res[p] = obj[p];
+ res[p].val = [].slice.call(obj[p].val);
+ break;
+ case "spvector":
+ res[p] = obj[p];
+ res[p].val = [].slice.call(obj[p].val);
+ res[p].ind = [].slice.call(obj[p].ind);
+ break;
+ case "spmatrix":
+ res[p] = obj[p];
+ res[p].val = [].slice.call(obj[p].val);
+ res[p].cols = [].slice.call(obj[p].cols);
+ res[p].rows = [].slice.call(obj[p].rows);
+ break;
+ case "undefined":
+ res[p] = obj[p];
+ break;
+ case "function":
+ break;
+ case "Array":
+ res[p] = getObjectWithoutFunc( obj[p] );
+ res[p].type = "Array";
+ res[p].length = obj[p].length;
+ break;
+ default:
+ res[p] = getObjectWithoutFunc( obj[p] );
+ break;
+ }
+ }
+ return res;
+ }
+}
+function renewObject( obj ) {
+ // Recreate full object with member functions
+ // from an object created by getObjectWithoutFunc()
+
+ var to = type(obj);
+ switch( to ) {
+ case "number":
+ case "boolean":
+ case "string":
+ case "undefined":
+ return obj;
+ break;
+ case "vector":
+ return new Float64Array(obj.data);
+ break;
+ case "matrix":
+ return new Matrix(obj.m, obj.n, obj.val);
+ break;
+ case "spvector":
+ return new spVector(obj.length,obj.val,obj.ind);
+ break;
+ case "spmatrix":
+ return new spMatrix(obj.m, obj.n, obj.val, obj.cols, obj.rows);
+ break;
+ case "object":
+ // Object without type property and thus without Class
+ var newobj = {};
+ for ( var p in obj )
+ newobj[p] = renewObject(obj[p]);
+ return newobj;
+ break;
+ case "Array":
+ var newobj = new Array(obj.length);
+ for ( var p in obj )
+ newobj[p] = renewObject(obj[p]);
+ return newobj;
+ default:
+ // Structured Object like Classifier etc...
+ // type = Class:subclass
+ var typearray = obj.type.split(":");
+ var Class = eval(typearray[0]);
+ if ( typearray.length == 1 )
+ var newobj = new Class();
+ else
+ var newobj = new Class(typearray[1]);
+ for ( var p in obj )
+ newobj[p] = renewObject(obj[p]);
+
+ // deal with particular cases:
+ // Rebuild kernelFunc
+ if (typearray[1] == "SVM" || typearray[1] == "SVR" ) {
+ newobj["kernelFunc"] = kernelFunction(newobj["kernel"], newobj["kernelpar"], type(newobj["SV"]) == "spmatrix"?"spvector":"vector");
+ }
+ if (typearray[1] == "KernelRidgeRegression" ) {
+ newobj["kernelFunc"] = kernelFunction(newobj["kernel"], newobj["kernelpar"], type(newobj["X"]) == "spmatrix"?"spvector":"vector");
+ }
+
+ return newobj;
+ break;
+ }
+}
+
+function load_data ( datastring ) {
+
+ // convert a string into a matrix data
+ var i;
+ var cmd = "mat( [ ";
+ var row;
+ var rows = datastring.split("\n");
+ var ri ;
+ for ( i=0; i< rows.length - 1; i++) {
+ ri = removeFirstSpaces(rows[i]);
+ if ( ri != "" ) {
+ row = ri.replace(/,/g," ").replace(/ +/g,",");
+ cmd += "new Float64Array([" + row + "]) ,";
+ }
+ }
+ ri = removeFirstSpaces(rows[rows.length-1]);
+ if ( ri != "" ) {
+ row = ri.replace(/,/g," ").replace(/ +/g,",");
+ cmd += "new Float64Array([" + row + "]) ] , true) ";
+ }
+ else {
+ cmd = cmd.substr(0,cmd.length-1); // remove last comma
+ cmd += "] , true) ";
+ }
+
+ return eval(cmd);
+
+}
+
+function removeFirstSpaces( str ) {
+ //remove spaces at begining of string
+ var i = 0;
+ while ( i < str.length && str[i] == " " )
+ i++;
+ if ( i submatrix of M
+ get ( M, rows ) => subset of rows from M (equiv to rows(M,rows) )
+ get ( M, [], cols ) => subset of cols (equiv to cols(M, cols) )
+ get ( M, i, j) => M[i][j] converted to dense format (0 instead of undefined)
+ get ( M ) => M in dense format (with 0 instead of undefined)
+
+ For VECTORS:
+
+ get ( v, rows ) => subvector from v (equiv to rows(v,rows) )
+ get ( v, i ) => v[i] converted to dense format (0 instead of undefined)
+ get ( v ) => v in dense format (with 0 instead of undefined)
+
+*/
+function get ( A , rowsrange, colsrange) {
+
+ var typerows = typeof(rowsrange);
+ var typecols = typeof(colsrange);
+
+ if (arguments.length == 1 )
+ return matrixCopy(A);
+
+ var typeA = type ( A );
+ if ( typeA == "vector" ) {
+
+ if ( typerows == "number" ) {
+ if (rowsrange >= 0 && rowsrange < A.length)
+ return A[rowsrange]; // get v[i]
+ else {
+ error("Error in a[i] = get(a,i): Index i="+rowsrange+" out of bounds [0,"+(A.length-1)+"]");
+ return undefined;
+ }
+ }
+ else {
+ return getSubVector(A, rowsrange);
+ }
+ }
+ else if ( typeA == "matrix") {
+
+ if ( typerows == "number" )
+ rowsrange = [rowsrange];
+
+ if ( typecols == "number" )
+ colsrange = [colsrange];
+
+ if ( rowsrange.length == 1 && colsrange.length == 1 )
+ return A.val[rowsrange[0] * A.n + colsrange[0]]; // get ( A, i, j)
+
+ if ( rowsrange.length == 0 )
+ return getCols(A,colsrange);// get(A,[],4) <=> cols(A,4)
+
+ if (colsrange.length == 0 )
+ return getRows(A, rowsrange);// get(A,3,[]) <=> rows(A,3)
+
+ // otherwise:
+ return getSubMatrix(A, rowsrange, colsrange);
+
+ }
+ else if ( typeA == "Array" ) {
+ if ( typerows == "number" )
+ return A[rowsrange];
+ else
+ return getSubArray(A, rowsrange);
+ }
+ else if ( typeA == "spmatrix") {
+
+ if ( typerows == "number" )
+ rowsrange = [rowsrange];
+
+ if ( typecols == "number" )
+ colsrange = [colsrange];
+
+ if ( rowsrange.length == 1 && colsrange.length == 1 )
+ return A.get(rowsrange[0], colsrange[0]); // get ( A, i, j)
+
+ if ( rowsrange.length == 1 && A.rowmajor )
+ return A.row(rowsrange[0]);
+ if ( colsrange.length == 1 && !A.rowmajor )
+ return A.col(colsrange[0]);
+
+ if (colsrange.length == 0 )
+ return spgetRows(A, rowsrange);
+ if ( rowsrange.length == 0 )
+ return spgetCols(A,colsrange);
+
+ // TODO
+ }
+ else if ( typeA == "spvector" ) {
+
+ if ( typerows == "number" )
+ return A.get( rowsrange ); // get v[i]
+ else
+ return getSubspVector(A, rowsrange);//TODO
+ }
+ else if ( typeA == "ComplexVector") {
+ if ( typerows == "number" )
+ return A.get( rowsrange ); // get v[i]
+ else
+ return A.getSubVector(rowsrange);
+ }
+ else if ( typeA == "ComplexMatrix") {
+
+ if ( typerows == "number" )
+ rowsrange = [rowsrange];
+
+ if ( typecols == "number" )
+ colsrange = [colsrange];
+
+ if ( rowsrange.length == 1 && colsrange.length == 1 )
+ return A.get(i,j);
+
+ if ( rowsrange.length == 0 )
+ return A.getCols(colsrange);// get(A,[],4) <=> cols(A,4)
+
+ if (colsrange.length == 0 )
+ return A.getRows(rowsrange);// get(A,3,[]) <=> rows(A,3)
+
+ // otherwise:
+ return A.getSubMatrix(rowsrange, colsrange);
+ }
+ return undefined;
+}
+function getSubMatrix(A, rowsrange, colsrange) {
+ var n = colsrange.length;
+ var i;
+ var j;
+ var res;
+ if ( n == 1 ) {
+ res = new Float64Array(rowsrange.length);
+ for (i= 0; i< rowsrange.length ; i++) {
+ res[i] = A.val[rowsrange[i] * A.n + colsrange[0]];
+ }
+ }
+ else {
+ res = new Matrix(rowsrange.length, n);
+ var r = 0;
+
+ for (i= 0; i< rowsrange.length ; i++) {
+ var rA = rowsrange[i]*A.n;
+ for ( j=0; j < n; j++) {
+ res.val[r+j] = A.val[rA + colsrange[j]];
+ }
+ r += n;
+ }
+ }
+ return res;
+}
+
+function getRows(A, rowsrange) {
+ var n = rowsrange.length;
+ if ( n > 1 ) {
+ var res = new Matrix(n, A.n);
+ var r=0;
+ for ( var i = 0; i < n; i++) {
+ for (var j=0; j < A.n; j++)
+ res.val[r + j] = A.val[rowsrange[i]*A.n + j];
+ r += A.n;
+ }
+ return res;
+ }
+ else
+ return vectorCopy(A.val.subarray( rowsrange[0]*A.n, rowsrange[0]*A.n + A.n));
+}
+function getCols(A, colsrange) {
+ var m = A.m;
+ var n = colsrange.length;
+ if( n > 1 ) {
+ var res = new Matrix(m, n);
+ var r = 0;
+ var rA = 0;
+ for ( var i = 0; i < m; i++) {
+ for ( var j = 0; j < n; j++)
+ res.val[r + j] = A.val[rA + colsrange[j]];
+
+ r += n;
+ rA += A.n;
+ }
+ return res;
+ }
+ else {
+ var res = new Float64Array(m);
+ var r = 0;
+ for ( var i = 0; i < m; i++) {
+ res[i] = A.val[r + colsrange[0]];
+ r += A.n;
+ }
+ return res;
+ }
+}
+/**
+ * @param {Float64Array}
+ * @param {Array}
+ * @return {Float64Array}
+ */
+function getSubVector(a, rowsrange) {
+ const n = rowsrange.length;
+ var res= new Float64Array( n );
+ for (var i = 0; i< n; i++) {
+ res[i] = a[rowsrange[i]];
+ }
+ return res;
+}
+
+/**
+ * @param {Array}
+ * @param {Array}
+ * @return {Array}
+ */
+function getSubArray(a, rowsrange) {
+ const n = rowsrange.length;
+ var res= new Array( n );
+ for (var i = 0; i< n; i++) {
+ res[i] = a[rowsrange[i]];
+ }
+ return res;
+}
+
+
+function getrowref(A, i) {
+ // return a pointer-like object on a row in a matrix, not a copy!
+ return A.val.subarray(i*A.n, (i+1)*A.n);
+}
+
+/*
+ SET function : set values in a subset of entries of a matrix or vector
+
+ For MATRICES:
+
+ set ( M, rows, cols, A ) => submatrix of M = A
+ set ( M, rows, A ) => subset of rows from M = A
+ set ( M, [], cols, A ) => subset of cols from M = A
+ set ( M, i, [], A ) => fill row M[i] with vector A (transposed)
+ set ( M, i, j, A) => M[i][j] = A
+
+ For VECTORS:
+
+ set ( v, rows, a ) => subvector from v = a
+ set ( v, i , a) => v[i] = a
+
+*/
+function set ( A , rowsrange, colsrange, B) {
+ var i;
+ var j;
+ var k;
+ var l;
+ var n;
+
+ var typerows = typeof(rowsrange);
+ var typecols = typeof(colsrange);
+
+ if (arguments.length == 1 )
+ return undefined;
+
+ var typeA = type ( A );
+ if ( typeA == "vector" ) {
+ B = colsrange;
+ if ( typerows == "number" ) {
+ A[rowsrange] = B;
+ return B;
+ }
+ else if ( rowsrange.length == 0 )
+ rowsrange = range(A.length);
+
+ if ( size(B,1) == 1 ) {
+ setVectorScalar (A, rowsrange, B);
+ }
+ else {
+ setVectorVector (A, rowsrange, B);
+ }
+ return B;
+ }
+ else if ( typeA == "matrix") {
+
+ if ( typerows == "number" )
+ rowsrange = [rowsrange];
+ if ( typecols == "number" )
+ colsrange = [colsrange];
+
+ if ( rowsrange.length == 1 && colsrange.length == 1 ) {
+ A.val[rowsrange[0]*A.n + colsrange[0]] = B;
+ return B;
+ }
+
+ if ( rowsrange.length == 0 ) {
+ setCols(A, colsrange, B);
+ return B;
+ }
+
+ if (colsrange.length == 0 ) {
+ setRows( A, rowsrange, B);
+ return B;
+ }
+
+ // Set a submatrix
+ var sB = size(B);
+ var tB = type(B);
+ if ( sB[0] == 1 && sB[1] == 1 ) {
+ if ( tB == "number" )
+ setMatrixScalar(A, rowsrange, colsrange, B);
+ else if ( tB == "vector" )
+ setMatrixScalar(A, rowsrange, colsrange, B[0]);
+ else
+ setMatrixScalar(A, rowsrange, colsrange, B.val[0]);
+ }
+ else {
+ if ( colsrange.length == 1 )
+ setMatrixColVector(A, rowsrange, colsrange[0], B);
+ else if ( rowsrange.length == 1 ) {
+ if ( tB == "vector" )
+ setMatrixRowVector(A, rowsrange[0], colsrange, B);
+ else
+ setMatrixRowVector(A, rowsrange[0], colsrange, B.val);
+ }
+ else
+ setMatrixMatrix(A, rowsrange, colsrange, B);
+ }
+ return B;
+ }
+ else if ( typeA == "ComplexVector" ) {
+ B = colsrange;
+ if ( typerows == "number" ) {
+ A.set(rowsrange, B);
+ return B;
+ }
+ else if ( rowsrange.length == 0 )
+ rowsrange = range(A.length);
+
+ if ( size(B,1) == 1 ) {
+ A.setVectorScalar (rowsrange, B);
+ }
+ else {
+ A.setVectorVector (rowsrange, B);
+ }
+ return B;
+ }
+}
+
+function setVectorScalar(A, rowsrange, B) {
+ var i;
+ for (i = 0; i< rowsrange.length; i++)
+ A[rowsrange[i]] = B;
+}
+function setVectorVector(A, rowsrange, B) {
+ var i;
+ for (i = 0; i< rowsrange.length; i++)
+ A[rowsrange[i]] = B[i];
+}
+
+function setMatrixScalar(A, rowsrange, colsrange, B) {
+ var i;
+ var j;
+ var m = rowsrange.length;
+ var n = colsrange.length;
+ for (i = 0; i< m; i++)
+ for(j=0; j < n; j++)
+ A.val[rowsrange[i]*A.n + colsrange[j]] = B;
+}
+function setMatrixMatrix(A, rowsrange, colsrange, B) {
+ var i;
+ var j;
+ var m = rowsrange.length;
+ var n = colsrange.length;
+ for (i = 0; i< m; i++)
+ for(j=0; j < n; j++)
+ A.val[rowsrange[i]*A.n + colsrange[j]] = B.val[i*B.n +j];
+}
+function setMatrixColVector(A, rowsrange, col, B) {
+ var i;
+ var m = rowsrange.length;
+ for (i = 0; i< m; i++)
+ A.val[rowsrange[i]*A.n + col] = B[i];
+}
+function setMatrixRowVector(A, row, colsrange, B) {
+ var j;
+ var n = colsrange.length;
+ for(j=0; j < n; j++)
+ A.val[row*A.n + colsrange[j]] = B[j];
+}
+function setRows(A, rowsrange, B ) {
+ var i;
+ var j;
+ var m = rowsrange.length;
+ var rA;
+ switch( type(B) ) {
+ case "vector":
+ for ( i=0; i end ) {
+ if ( inc > 0)
+ inc *= -1;
+ var r = new Array( Math.floor ( ( start - end ) / Math.abs(inc) ) );
+ var k = 0;
+ for ( var i = start; i> end; i+=inc) {
+ r[k] = i;
+ k++;
+ }
+ }
+ else {
+ var r = new Array( Math.floor ( ( end - start ) / inc ) );
+ var k = 0;
+ for ( var i = start; i< end; i+=inc) {
+ r[k] = i;
+ k++;
+ }
+ }
+ return r;
+}
+
+// Swaping
+/**
+ * @param {Matrix}
+ */
+function swaprows ( A , i, j ) {
+ if ( i != j ) {
+ var ri = i*A.n;
+ var rj = j*A.n;
+ var tmp = vectorCopy(A.val.subarray(ri, ri+A.n));
+ A.val.set(vectorCopy(A.val.subarray(rj, rj+A.n)), ri);
+ A.val.set(tmp, rj);
+ }
+}
+/**
+ * @param {Matrix}
+ */
+function swapcols ( A , j, k ) {
+ if ( j != k ) {
+ var tmp = getCols ( A, [j]);
+ setCols ( A, [j] , getCols ( A, [k]) );
+ setCols ( A, [k], tmp);
+ }
+}
+
+//////////////////////////
+// Random numbers
+////////////////////////////
+
+// Gaussian random number (mean = 0, variance = 1;
+// Gaussian noise with the polar form of the Box-Muller transformation
+function randnScalar() {
+
+ var x1;
+ var x2;
+ var w;
+ var y1;
+ var y2;
+ do {
+ x1 = 2.0 * Math.random() - 1.0;
+ x2 = 2.0 * Math.random() - 1.0;
+ w = x1 * x1 + x2 * x2;
+ } while ( w >= 1.0 );
+
+ w = Math.sqrt( (-2.0 * Math.log( w ) ) / w );
+ y1 = x1 * w;
+ y2 = x2 * w;
+
+ return y1;
+}
+function randn( dim1, dim2 ) {
+ var res;
+
+ if ( typeof ( dim1 ) == "undefined" || (dim1 == 1 && typeof(dim2)=="undefined") || (dim1 == 1 && dim2==1)) {
+ return randnScalar();
+ }
+ else if (typeof(dim2) == "undefined" || dim2 == 1 ) {
+ res = new Float64Array(dim1);
+ for (var i=0; i< dim1; i++)
+ res[i] = randnScalar();
+
+ return res;
+ }
+ else {
+ res = zeros(dim1, dim2);
+ for (var i=0; i< dim1*dim2; i++) {
+ res.val[i] = randnScalar();
+ }
+ return res;
+ }
+}
+
+// Uniform random numbers
+/*
+ * @param{number}
+ * @return{Float64Array}
+ */
+function randVector(dim1) {
+ var res = new Float64Array(dim1);
+ for (var i=0; i< dim1; i++) {
+ res[i] = Math.random();
+ }
+ return res;
+}
+/*
+ * @param{number}
+ * @param{number}
+ * @return{Matrix}
+ */
+function randMatrix(dim1,dim2) {
+ const n = dim1*dim2;
+ var res = new Float64Array(n);
+ for (var i=0; i< n; i++) {
+ res[i] = Math.random();
+ }
+ return new Matrix(dim1,dim2,res,true);
+}
+function rand( dim1, dim2 ) {
+ var res;
+ if ( typeof ( dim1 ) == "undefined" || (dim1 == 1 && typeof(dim2)=="undefined") || (dim1 == 1 && dim2==1)) {
+ return Math.random();
+ }
+ else if (typeof(dim2) == "undefined" || dim2 == 1) {
+ return randVector(dim1);
+ }
+ else {
+ return randMatrix(dim1,dim2);
+ }
+}
+
+function randnsparse(NZratio, dim1, dim2) {
+ // Generates a sparse random matrix with NZratio * dim1*dim2 (or NZ if NZratio > 1 ) nonzeros
+ var NZ;
+ if ( NZratio > 1 )
+ NZ = NZratio;
+ else
+ NZ = Math.floor(NZratio *dim1*dim2);
+
+ var indexes;
+ var i;
+ var j;
+ var k;
+ var res;
+
+ if ( typeof ( dim1 ) == "undefined" ) {
+ return randn();
+ }
+ else if (typeof(dim2) == "undefined" || dim2 == 1) {
+
+ indexes = randperm( dim1 );
+
+ res = zeros(dim1);
+ for (i=0; i< NZ; i++) {
+ res[indexes[i]] = randn();
+ }
+ return res;
+ }
+ else {
+ res = zeros(dim1, dim2);
+ indexes = randperm( dim1*dim2 );
+ for (k=0; k< NZ; k++) {
+ i = Math.floor(indexes[k] / dim2);
+ j = indexes[k] - i * dim2;
+ res.val[i*dim2+j] = randn();
+ }
+ return res;
+ }
+}
+function randsparse(NZratio, dim1, dim2) {
+ // Generates a sparse random matrix with NZratio * dim1*dim2 (or NZ if NZratio > 1 ) nonzeros
+ if (typeof(dim2) == "undefined")
+ var dim2 = 1;
+
+ var NZ;
+ if ( NZratio > 1 )
+ NZ = NZratio;
+ else
+ NZ = Math.floor(NZratio *dim1*dim2);
+
+ var indexes;
+ var i;
+ var j;
+ var k;
+ var res;
+
+ if ( typeof ( dim1 ) == "undefined" ) {
+ return randn();
+ }
+ else if (dim2 == 1) {
+
+ indexes = randperm( dim1 );
+
+ res = zeros(dim1);
+ for (i=0; i< NZ; i++) {
+ res[indexes[i]] = Math.random();
+ }
+ return res;
+ }
+ else {
+ res = zeros(dim1, dim2);
+ indexes = randperm( dim1*dim2 );
+
+ for (k=0; k< NZ; k++) {
+ i = Math.floor(indexes[k] / dim2);
+ j = indexes[k] - i * dim2;
+ res.val[i*dim2+j] = Math.random();
+ }
+ return res;
+ }
+}
+
+function randperm( x ) {
+ // return a random permutation of x (or of range(x) if x is a number)
+
+ if ( typeof( x ) == "number" ) {
+ var perm = range(x);
+ }
+ else {
+ var perm = new Float64Array(x);
+ }
+ var i;
+ var j;
+ var k;
+
+ // shuffle
+ for(i=perm.length - 1 ; i > 1; i--) {
+ j = Math.floor(Math.random() * i);
+ k = perm[j];
+ perm[j] = perm[i];
+ perm[i] = k;
+ }
+ return perm;
+}
+///////////////////////////////
+/// Basic Math function: give access to Math.* JS functions
+/// and vectorize them
+///////////////////////////////
+
+
+// automatically generate (vectorized) wrappers for Math functions
+var MathFunctions = Object.getOwnPropertyNames(Math);
+for ( var mf in MathFunctions ) {
+ if ( eval( "typeof(Math." + MathFunctions[mf] + ")") == "function") {
+ if ( eval( "Math." + MathFunctions[mf] + ".length") == 1 ) {
+ // this is a function of a scalar
+ // make generic function:
+ eval( MathFunctions[mf] + " = function (x) { return apply(Math."+ MathFunctions[mf] + " , x );};");
+ // make vectorized version:
+ eval( MathFunctions[mf] + "Vector = function (x) { return applyVector(Math."+ MathFunctions[mf] + " , x );};");
+ // make matrixized version:
+ eval( MathFunctions[mf] + "Matrix = function (x) { return applyMatrix(Math."+ MathFunctions[mf] + " , x );};");
+ }
+ }
+ else if ( eval( "typeof(Math." + MathFunctions[mf] + ")") == "number") {
+ // Math constant:
+ eval( MathFunctions[mf] + " = Math."+ MathFunctions[mf] ) ;
+ }
+}
+
+function apply( f, x ) {
+ // Generic wrapper to apply scalar functions
+ // element-wise to vectors and matrices
+ if ( typeof(f) != "function")
+ return undefined;
+ switch ( type( x ) ) {
+ case "number":
+ return f(x);
+ break;
+ case "Complex":
+ var ComplexFunctions = ["exp", "abs"];
+ var fc = ComplexFunctions.indexOf(f.name);
+ if ( fc >= 0 )
+ return eval(ComplexFunctions[fc] + "Complex(x);");
+ else {
+ error("This function has no Complex counterpart (yet).");
+ return undefined;
+ }
+ break;
+ case "vector":
+ return applyVector(f, x);
+ break;
+ case "spvector":
+ return applyspVector(f, x);
+ break;
+ case "ComplexVector":
+ if ( f.name == "abs" )
+ return absComplex(x);
+ else
+ return applyComplexVector(f, x);
+ break;
+ case "matrix":
+ return applyMatrix(f, x);
+ break;
+ case "spmatrix":
+ return applyspMatrix(f, x);
+ break;
+ case "ComplexMatrix":
+ if ( f.name == "abs" )
+ return absComplex(x);
+ else
+ return applyComplexMatrix(f, x);
+ break;
+ default:
+ return "undefined";
+ }
+}
+function applyVector( f, x ) {
+ const nv = x.length;
+ var res = new Float64Array(nv);
+ for (var i=0; i< nv; i++)
+ res[i] = f(x[i]);
+ return res;
+}
+function applyComplexVector( f, x ) {
+ const nv = x.length;
+ var res = new ComplexVector(nv);
+ for (var i=0; i< nv; i++)
+ res.set(i, f(x.get(i) ) );
+ return res;
+}
+function applyComplexMatrix( f, x ) {
+ const m = x.m;
+ const n = x.n;
+ var res = new ComplexMatrix(m, n);
+ for (var i=0; i< m; i++)
+ for ( var j =0; j < n; j++)
+ res.set(i, j, f(x.get(i,j) ) );
+ return res;
+}
+function applyMatrix(f, x) {
+ return new Matrix(x.m, x.n, applyVector(f, x.val), true);
+}
+///////////////////////////////
+/// Operators
+///////////////////////////////
+
+function mul(a,b) {
+ var sa = size(a);
+ var sb = size(b);
+ if ( !isScalar(a) && sa[0] == 1 && sa[1] == 1 )
+ a = get(a, 0, 0);
+ if ( !isScalar(b) && sb[0] == 1 && sb[1] == 1 )
+ b = get(b, 0, 0);
+
+ switch( type(a) ) {
+ case "number":
+ switch( type(b) ) {
+ case "number":
+ return a*b;
+ break;
+ case "Complex":
+ return mulComplexReal(b,a);
+ break;
+ case "vector":
+ return mulScalarVector(a,b);
+ break;
+ case "spvector":
+ return mulScalarspVector(a,b);
+ break;
+ case "ComplexVector":
+ return mulScalarComplexVector(a,b);
+ break;
+ case "matrix":
+ return mulScalarMatrix(a,b);
+ break;
+ case "spmatrix":
+ return mulScalarspMatrix(a,b);
+ break;
+ case "ComplexMatrix":
+ return mulScalarComplexMatrix(a,b);
+ break;
+ default:
+ return undefined;
+ break;
+ }
+ break;
+ case "Complex":
+ switch( type(b) ) {
+ case "number":
+ return mulComplexReal(a,b);
+ break;
+ case "Complex":
+ return mulComplex(a,b);
+ break;
+ case "vector":
+ return mulComplexVector(a,b);
+ break;
+ case "ComplexVector":
+ return mulComplexComplexVector(a,b);
+ break;
+ case "spvector":
+ return mulComplexspVector(a,b);
+ break;
+ case "matrix":
+ return mulComplexMatrix(a,b);
+ break;
+ case "ComplexMatrix":
+ return mulComplexComplexMatrix(a,b);
+ break;
+ case "spmatrix":
+ return mulComplexspMatrix(a,b);
+ break;
+ default:
+ return undefined;
+ break;
+ }
+ break;
+ case "vector":
+ switch( type(b) ) {
+ case "number":
+ return mulScalarVector(b,a);
+ break;
+ case "Complex":
+ return mulComplexVector(b,a);
+ break;
+ case "vector":
+ if ( a.length != b.length ) {
+ error("Error in mul(a,b) (dot product): a.length = " + a.length + " != " + b.length + " = b.length.");
+ return undefined;
+ }
+ return dot(a,b);
+ break;
+ case "spvector":
+ if ( a.length != b.length ) {
+ error("Error in mul(a,b) (dot product): a.length = " + a.length + " != " + b.length + " = b.length.");
+ return undefined;
+ }
+ return dotspVectorVector(b,a);
+ break;
+ case "ComplexVector":
+ if ( a.length != b.length ) {
+ error("Error in mul(a,b) (dot product): a.length = " + a.length + " != " + b.length + " = b.length.");
+ return undefined;
+ }
+ return dotComplexVectorVector(b,a);
+ break;
+ case "matrix":
+ if ( b.m == 1)
+ return outerprodVectors(a , b.val );
+ else {
+ error("Inconsistent dimensions in mul(a,B): size(a) = [" + sa[0] + "," + sa[1] + "], size(B) = [" + sb[0] + "," + sb[1] + "]");
+ return undefined;
+ }
+ break;
+ case "spmatrix":
+ if ( b.m == 1)
+ return outerprodVectors(a , fullMatrix(b).val );
+ else {
+ error("Inconsistent dimensions in mul(a,B): size(a) = [" + sa[0] + "," + sa[1] + "], size(B) = [" + sb[0] + "," + sb[1] + "]");
+ return undefined;
+ }
+ break;
+ case "ComplexMatrix":
+ if ( b.m == 1)
+ return transpose(outerprodComplexVectorVector(new ComplexVector(b.re,b.im,true), a , b.val ));
+ else {
+ error("Inconsistent dimensions in mul(a,B): size(a) = [" + sa[0] + "," + sa[1] + "], size(B) = [" + sb[0] + "," + sb[1] + "]");
+ return undefined;
+ }
+ break;
+ default:
+ return undefined;
+ break;
+ }
+ break;
+ case "spvector":
+ switch( type(b) ) {
+ case "number":
+ return mulScalarspVector(b,a);
+ break;
+ case "vector":
+ if ( a.length != b.length ) {
+ error("Error in mul(a,b) (dot product): a.length = " + a.length + " != " + b.length + " = b.length.");
+ return undefined;
+ }
+ return dotspVectorVector(a,b);
+ break;
+ case "spvector":
+ if ( a.length != b.length ) {
+ error("Error in mul(a,b) (dot product): a.length = " + a.length + " != " + b.length + " = b.length.");
+ return undefined;
+ }
+ return spdot(b,a);
+ break;
+ case "matrix":
+ if ( b.m == 1)
+ return outerprodspVectorVector(a , b.val );
+ else {
+ error("Inconsistent dimensions in mul(a,B): size(a) = [" + sa[0] + "," + sa[1] + "], size(B) = [" + sb[0] + "," + sb[1] + "]");
+ return undefined;
+ }
+ break;
+ case "spmatrix":
+ if ( b.m == 1)
+ return outerprodspVectorVector(a, fullMatrix(b).val);
+ else {
+ error("Inconsistent dimensions in mul(a,B): size(a) = [" + sa[0] + "," + sa[1] + "], size(B) = [" + sb[0] + "," + sb[1] + "]");
+ return undefined;
+ }
+ break;
+ default:
+ return undefined;
+ break;
+ }
+ break;
+ case "ComplexVector":
+ switch( type(b) ) {
+ case "number":
+ return mulScalarComplexVector(b,a);
+ break;
+ case "Complex":
+ return mulComplexComplexVector(b,a);
+ break;
+ case "vector":
+ if ( a.length != b.length ) {
+ error("Error in mul(a,b) (dot product): a.length = " + a.length + " != " + b.length + " = b.length.");
+ return undefined;
+ }
+ return dotComplexVectorVector(a,b);
+ break;
+ case "spvector":
+ if ( a.length != b.length ) {
+ error("Error in mul(a,b) (dot product): a.length = " + a.length + " != " + b.length + " = b.length.");
+ return undefined;
+ }
+ return dotComplexVectorspVector(a,b);
+ break;
+ case "matrix":
+ if ( b.m == 1)
+ return outerprodComplexVectorVector(a , b.val );
+ else {
+ error("Inconsistent dimensions in mul(a,B): size(a) = [" + sa[0] + "," + sa[1] + "], size(B) = [" + sb[0] + "," + sb[1] + "]");
+ return undefined;
+ }
+ break;
+ case "spmatrix":
+ if ( b.m == 1)
+ return outerprodComplexVectorVector(a , fullMatrix(b).val );
+ else {
+ error("Inconsistent dimensions in mul(a,B): size(a) = [" + sa[0] + "," + sa[1] + "], size(B) = [" + sb[0] + "," + sb[1] + "]");
+ return undefined;
+ }
+ break;
+ case "ComplexMatrix":
+ if ( b.m == 1)
+ return outerprodComplexVectors(a , new ComplexVector(b.re,b.im, true) );
+ else {
+ error("Inconsistent dimensions in mul(a,B): size(a) = [" + sa[0] + "," + sa[1] + "], size(B) = [" + sb[0] + "," + sb[1] + "]");
+ return undefined;
+ }
+ break;
+ default:
+ return undefined;
+ break;
+ }
+ break;
+
+ case "matrix":
+ switch( type(b) ) {
+ case "number":
+ return mulScalarMatrix(b,a);
+ break;
+ case "Complex":
+ return mulComplexMatrix(b,a);
+ break;
+ case "vector":
+ if ( a.m == 1 ) {
+ // dot product with explicit transpose
+ if ( a.val.length != b.length ) {
+ error("Error in mul(a',b): a.length = " + a.val.length + " != " + b.length + " = b.length.");
+ return undefined;
+ }
+ return dot(a.val, b);
+ }
+ else {
+ if ( a.n != b.length ) {
+ error("Error in mul(A,b): A.n = " + a.n + " != " + b.length + " = b.length.");
+ return undefined;
+ }
+ return mulMatrixVector(a,b);
+ }
+ break;
+ case "spvector":
+ if ( a.m == 1 ) {
+ // dot product with explicit transpose
+ if ( a.val.length != b.length ) {
+ error("Error in mul(a',b): a.length = " + a.val.length + " != " + b.length + " = b.length.");
+ return undefined;
+ }
+ return dotspVectorVector(b, a.val);
+ }
+ else {
+ if ( a.n != b.length ) {
+ error("Error in mul(A,b): A.n = " + a.n + " != " + b.length + " = b.length.");
+ return undefined;
+ }
+ return mulMatrixspVector(a,b);
+ }
+ break;
+ case "ComplexVector":
+ if ( a.m == 1 ) {
+ // dot product with explicit transpose
+ if ( a.val.length != b.length ) {
+ error("Error in mul(a',b): a.length = " + a.val.length + " != " + b.length + " = b.length.");
+ return undefined;
+ }
+ return dotComplexVectorVector(b, a.val);
+ }
+ else {
+ if ( a.n != b.length ) {
+ error("Error in mul(A,b): A.n = " + a.n + " != " + b.length + " = b.length.");
+ return undefined;
+ }
+ return mulMatrixComplexVector(a,b);
+ }
+ break;
+ case "matrix":
+ if ( a.n != b.m ) {
+ error("Error in mul(A,B): A.n = " + a.n + " != " + b.m + " = B.m.");
+ return undefined;
+ }
+ return mulMatrixMatrix(a,b);
+ break;
+ case "spmatrix":
+ if ( a.n != b.m ) {
+ error("Error in mul(A,B): A.n = " + a.n + " != " + b.m + " = B.m.");
+ return undefined;
+ }
+ return mulMatrixspMatrix(a,b);
+ break;
+ case "ComplexMatrix":
+ if ( a.n != b.m ) {
+ error("Error in mul(A,B): A.n = " + a.n + " != " + b.m + " = B.m.");
+ return undefined;
+ }
+ return transpose(mulComplexMatrixMatrix(transpose(b),transpose(a)));
+ break;
+ default:
+ return undefined;
+ break;
+ }
+ break;
+ case "spmatrix":
+ switch( type(b) ) {
+ case "number":
+ return mulScalarspMatrix(b,a);
+ break;
+ case "vector":
+ if ( a.m == 1 ) {
+ // dot product with explicit transpose
+ if ( a.n != b.length ) {
+ error("Error in mul(a',b): a.length = " + a.val.length + " != " + b.length + " = b.length.");
+ return undefined;
+ }
+ return dot(fullMatrix(a).val, b);
+ }
+ else {
+ if ( a.n != b.length ) {
+ error("Error in mul(A,b): A.n = " + a.n + " != " + b.length + " = b.length.");
+ return undefined;
+ }
+ return mulspMatrixVector(a,b);
+ }
+ break;
+ case "spvector":
+ if ( a.m == 1 ) {
+ // dot product with explicit transpose
+ if ( a.n != b.length ) {
+ error("Error in mul(a',b): a.length = " + a.val.length + " != " + b.length + " = b.length.");
+ return undefined;
+ }
+ return dotspVectorVector(b, fullMatrix(a).val);
+ }
+ else {
+ if ( a.n != b.length ) {
+ error("Error in mul(A,b): A.n = " + a.n + " != " + b.length + " = b.length.");
+ return undefined;
+ }
+ return mulspMatrixspVector(a,b);
+ }
+ break;
+ case "matrix":
+ if ( a.n != b.m ) {
+ error("Error in mul(A,B): A.n = " + a.n + " != " + b.m + " = B.m.");
+ return undefined;
+ }
+ return mulspMatrixMatrix(a,b);
+ break;
+ case "spmatrix":
+ if ( a.n != b.m ) {
+ error("Error in mul(A,B): A.n = " + a.n + " != " + b.m + " = B.m.");
+ return undefined;
+ }
+ return mulspMatrixspMatrix(a,b);
+ break;
+ default:
+ return undefined;
+ break;
+ }
+ break;
+ case "ComplexMatrix":
+ switch( type(b) ) {
+ case "number":
+ return mulScalarComplexMatrix(b,a);
+ break;
+ case "Complex":
+ return mulComplexComplexMatrix(b,a);
+ break;
+ case "vector":
+ if ( a.m == 1 ) {
+ // dot product with explicit transpose
+ if ( a.val.length != b.length ) {
+ error("Error in mul(a',b): a.length = " + a.val.length + " != " + b.length + " = b.length.");
+ return undefined;
+ }
+ return dotComplexVectorVector(new ComplexVector(a.re,a.im,true), b);
+ }
+ else {
+ if ( a.n != b.length ) {
+ error("Error in mul(A,b): A.n = " + a.n + " != " + b.length + " = b.length.");
+ return undefined;
+ }
+ return mulComplexMatrixVector(a,b);
+ }
+ break;
+ case "spvector":
+ if ( a.m == 1 ) {
+ // dot product with explicit transpose
+ if ( a.val.length != b.length ) {
+ error("Error in mul(a',b): a.length = " + a.val.length + " != " + b.length + " = b.length.");
+ return undefined;
+ }
+ return dotComplexVectorspVector(new ComplexVector(a.re,a.im,true), b);
+ }
+ else {
+ if ( a.n != b.length ) {
+ error("Error in mul(A,b): A.n = " + a.n + " != " + b.length + " = b.length.");
+ return undefined;
+ }
+ return mulComplexMatrixspVector(a,b);
+ }
+ break;
+ case "ComplexVector":
+ if ( a.m == 1 ) {
+ // dot product with explicit transpose
+ if ( a.val.length != b.length ) {
+ error("Error in mul(a',b): a.length = " + a.val.length + " != " + b.length + " = b.length.");
+ return undefined;
+ }
+ return dotComplexVectors(new ComplexVector(a.re,a.im,true), b);
+ }
+ else {
+ if ( a.n != b.length ) {
+ error("Error in mul(A,b): A.n = " + a.n + " != " + b.length + " = b.length.");
+ return undefined;
+ }
+ return mulComplexMatrixComplexVector(a,b);
+ }
+ break;
+ case "matrix":
+ if ( a.n != b.m ) {
+ error("Error in mul(A,B): A.n = " + a.n + " != " + b.m + " = B.m.");
+ return undefined;
+ }
+ return mulComplexMatrixMatrix(a,b);
+ break;
+ case "spmatrix":
+ if ( a.n != b.m ) {
+ error("Error in mul(A,B): A.n = " + a.n + " != " + b.m + " = B.m.");
+ return undefined;
+ }
+ return mulComplexMatrixspMatrix(a,b);
+ break;
+ case "ComplexMatrix":
+ if ( a.n != b.m ) {
+ error("Error in mul(A,B): A.n = " + a.n + " != " + b.m + " = B.m.");
+ return undefined;
+ }
+ return mulComplexMatrices(a,b);
+ break;
+ default:
+ return undefined;
+ break;
+ }
+ break;
+ default:
+ return undefined;
+ break;
+ }
+}
+
+/**
+ * @param {number}
+ * @param {Float64Array}
+ * @return {Float64Array}
+ */
+function mulScalarVector( scalar, vec ) {
+ var i;
+ const n = vec.length;
+ var res = new Float64Array(vec);
+ for ( i=0; i < n; i++)
+ res[i] *= scalar ;
+ return res;
+}
+/**
+ * @param {number}
+ * @param {Matrix}
+ * @return {Matrix}
+ */
+function mulScalarMatrix( scalar, A ) {
+ var res = new Matrix(A.m,A.n, mulScalarVector(scalar, A.val), true );
+
+ return res;
+}
+
+/**
+ * @param {Float64Array}
+ * @param {Float64Array}
+ * @return {number}
+ */
+function dot(a, b) {
+ const n = a.length;
+ var i;
+ var res = 0;
+ for ( i=0; i< n; i++)
+ res += a[i]*b[i];
+ return res;
+}
+
+/**
+ * @param {Matrix}
+ * @param {Float64Array}
+ * @return {Float64Array}
+ */
+function mulMatrixVector( A, b ) {
+ const m = A.length;
+ var c = new Float64Array(m);
+ var r = 0;
+ for (var i=0; i < m; i++) {
+ c[i] = dot(A.val.subarray(r, r+A.n), b);
+ r += A.n;
+ }
+
+ return c;
+}
+/**
+ * @param {Matrix}
+ * @param {Float64Array}
+ * @return {Float64Array}
+ */
+function mulMatrixTransVector( A, b ) {
+ const m = A.length;
+ const n = A.n;
+ var c = new Float64Array(n);
+ var rj = 0;
+ for (var j=0; j < m; j++) {
+ var bj = b[j];
+ for (var i=0; i < n; i++) {
+ c[i] += A.val[rj + i] * bj;
+ }
+ rj += A.n;
+ }
+ return c;
+}
+/**
+ * @param {Matrix}
+ * @param {Matrix}
+ * @return {Matrix}
+ */
+function mulMatrixMatrix(A, B) {
+ const m = A.length;
+ const n = B.n;
+ const n2 = B.length;
+
+ var Av = A.val;
+ var Bv = B.val;
+
+ var C = new Float64Array(m*n);
+ var aik;
+ var Aik = 0;
+ var Ci = 0;
+ for (var i=0;i < m ; i++) {
+ var bj = 0;
+ for (var k=0; k < n2; k++ ) {
+ aik = Av[Aik];
+ for (var j =0; j < n; j++) {
+ C[Ci + j] += aik * Bv[bj];
+ bj++;
+ }
+ Aik++;
+ }
+ Ci += n;
+ }
+ return new Matrix(m,n,C, true);
+}
+/**
+ * @param {Float64Array}
+ * @param {Float64Array}
+ * @return {Float64Array}
+ */
+function entrywisemulVector( a, b) {
+ var i;
+ const n = a.length;
+ var res = new Float64Array(n);
+ for ( i=0; i < n; i++)
+ res[i] = a[i] * b[i];
+ return res;
+}
+/**
+ * @param {Matrix}
+ * @param {Matrix}
+ * @return {Matrix}
+ */
+function entrywisemulMatrix( A, B) {
+ var res = new Matrix(A.m,A.n, entrywisemulVector(A.val, B.val), true );
+ return res;
+}
+
+
+function entrywisemul(a,b) {
+ var sa = size(a);
+ var sb = size(b);
+ if (typeof(a) != "number" && sa[0] == 1 && sa[1] == 1 )
+ a = get(a, 0, 0);
+ if (typeof(b) != "number" && sb[0] == 1 && sb[1] == 1 )
+ b = get(b, 0, 0);
+
+ switch( type(a) ) {
+ case "number":
+ switch( type(b) ) {
+ case "number":
+ return a*b;
+ break;
+ case "Complex":
+ return mulComplexReal(b,a);
+ break;
+ case "vector":
+ return mulScalarVector(a,b);
+ break;
+ case "spvector":
+ return mulScalarspVector(a,b);
+ break;
+ case "ComplexVector":
+ return mulScalarComplexVector(b,a);
+ break;
+ case "matrix":
+ return mulScalarMatrix(a,b);
+ break;
+ case "spmatrix":
+ return mulScalarspMatrix(a,b);
+ break;
+ case "ComplexMatrix":
+ return mulScalarComplexMatrix(b,a);
+ break;
+ default:
+ return undefined;
+ break;
+ }
+ break;
+ case "vector":
+ switch( type(b) ) {
+ case "number":
+ return mulScalarVector(b,a);
+ break;
+ case "Complex":
+ return mulComplexVector(b,a);
+ break;
+ case "vector":
+ if ( a.length != b.length ) {
+ error("Error in entrywisemul(a,b): a.length = " + a.length + " != " + b.length + " = b.length.");
+ return undefined;
+ }
+ return entrywisemulVector(a,b);
+ break;
+ case "ComplexVector":
+ if ( a.length != b.length ) {
+ error("Error in entrywisemul(a,b): a.length = " + a.length + " != " + b.length + " = b.length.");
+ return undefined;
+ }
+ return entrywisemulComplexVectorVector(b,a);
+ break;
+ case "spvector":
+ if ( a.length != b.length ) {
+ error("Error in entrywisemul(a,b): a.length = " + a.length + " != " + b.length + " = b.length.");
+ return undefined;
+ }
+ return entrywisemulspVectorVector(b,a);
+ break;
+ case "matrix":
+ case "spmatrix":
+ case "ComplexMatrix":
+ error("Error in entrywisemul(a,B): a is a vector and B is a matrix.");
+ return undefined;
+ break;
+ default:
+ return undefined;
+ break;
+ }
+ break;
+ case "spvector":
+ switch( type(b) ) {
+ case "number":
+ return mulScalarspVector(b,a);
+ break;
+ case "vector":
+ if ( a.length != b.length ) {
+ error("Error in entrywisemul(a,b): a.length = " + a.length + " != " + b.length + " = b.length.");
+ return undefined;
+ }
+ return entrywisemulspVectorVector(a,b);
+ break;
+ case "spvector":
+ if ( a.length != b.length ) {
+ error("Error in entrywisemul(a,b): a.length = " + a.length + " != " + b.length + " = b.length.");
+ return undefined;
+ }
+ return entrywisemulspVectors(a,b);
+ break;
+ case "matrix":
+ error("Error in entrywisemul(a,B): a is a vector and B is a Matrix.");
+ return undefined;
+ break;
+ case "spmatrix":
+ error("Error in entrywisemul(a,B): a is a vector and B is a Matrix.");
+ return undefined;
+ break;
+ default:
+ return undefined;
+ break;
+ }
+ break;
+ case "matrix":
+ switch( type(b) ) {
+ case "number":
+ return mulScalarMatrix(b,a);
+ break;
+ case "Complex":
+ return mulComplexMatrix(b,a);
+ break;
+ case "vector":
+ case "spvector":
+ case "ComplexVector":
+ error("Error in entrywisemul(A,b): A is a Matrix and b is a vector.");
+ return undefined;
+ break;
+ case "matrix":
+ if ( a.m != b.m || a.n != b.n ) {
+ error("Error in entrywisemul(A,B): size(A) = [" + a.m + "," + a.n + "] != [" + b.m + "," + b.n + "] = size(B).");
+ return undefined;
+ }
+ return entrywisemulMatrix(a,b);
+ break;
+ case "spmatrix":
+ if ( a.m != b.m || a.n != b.n ) {
+ error("Error in entrywisemul(A,B): size(A) = [" + a.m + "," + a.n + "] != [" + b.m + "," + b.n + "] = size(B).");
+ return undefined;
+ }
+ return entrywisemulspMatrixMatrix(b,a);
+ break;
+ case "ComplexMatrix":
+ if ( a.m != b.m || a.n != b.n ) {
+ error("Error in entrywisemul(A,B): size(A) = [" + a.m + "," + a.n + "] != [" + b.m + "," + b.n + "] = size(B).");
+ return undefined;
+ }
+ return entrywisemulComplexMatrixMatrix(b,a);
+ break;
+ default:
+ return undefined;
+ break;
+ }
+ break;
+ case "spmatrix":
+ switch( type(b) ) {
+ case "number":
+ return mulScalarspMatrix(b,a);
+ break;
+ case "vector":
+ error("Error in entrywisemul(A,b): A is a Matrix and b is a vector.");
+ return undefined;
+ break;
+ case "spvector":
+ error("Error in entrywisemul(A,b): A is a Matrix and b is a vector.");
+ return undefined;
+ break;
+ case "matrix":
+ if ( a.m != b.m || a.n != b.n ) {
+ error("Error in entrywisemul(A,B): size(A) = [" + a.m + "," + a.n + "] != [" + b.m + "," + b.n + "] = size(B).");
+ return undefined;
+ }
+ return entrywisemulspMatrixMatrix(a,b);
+ break;
+ case "spmatrix":
+ if ( a.m != b.m || a.n != b.n ) {
+ error("Error in entrywisemul(A,B): size(A) = [" + a.m + "," + a.n + "] != [" + b.m + "," + b.n + "] = size(B).");
+ return undefined;
+ }
+ return entrywisemulspMatrices(a,b);
+ break;
+ default:
+ return undefined;
+ break;
+ }
+ break;
+ case "ComplexVector":
+ switch( type(b) ) {
+ case "number":
+ return mulScalarComplexVector(b,a);
+ break;
+ case "Complex":
+ return mulComplexComplexVector(b,a);
+ break;
+ case "vector":
+ if ( a.length != b.length ) {
+ error("Error in entrywisemul(a,b): a.length = " + a.length + " != " + b.length + " = b.length.");
+ return undefined;
+ }
+ return entrywisemulComplexVectorVector(a,b);
+ break;
+ case "ComplexVector":
+ if ( a.length != b.length ) {
+ error("Error in entrywisemul(a,b): a.length = " + a.length + " != " + b.length + " = b.length.");
+ return undefined;
+ }
+ return entrywisemulComplexVectors(a,b);
+ break;
+ case "spvector":
+ if ( a.length != b.length ) {
+ error("Error in entrywisemul(a,b): a.length = " + a.length + " != " + b.length + " = b.length.");
+ return undefined;
+ }
+ return entrywisemulComplexVectorspVector(a,b);
+ break;
+ case "matrix":
+ case "spmatrix":
+ case "ComplexMatrix":
+ error("Error in entrywisemul(a,B): a is a vector and B is a matrix.");
+ return undefined;
+ break;
+ default:
+ return undefined;
+ break;
+ }
+ break;
+ case "ComplexMatrix":
+ switch( type(b) ) {
+ case "number":
+ return mulScalarComplexMatrix(b,a);
+ break;
+ case "Complex":
+ return mulComplexComplexMatrix(b,a);
+ break;
+ case "vector":
+ case "spvector":
+ case "ComplexVector":
+ error("Error in entrywisemul(A,b): A is a Matrix and b is a vector.");
+ return undefined;
+ break;
+ case "matrix":
+ if ( a.m != b.m || a.n != b.n ) {
+ error("Error in entrywisemul(A,B): size(A) = [" + a.m + "," + a.n + "] != [" + b.m + "," + b.n + "] = size(B).");
+ return undefined;
+ }
+ return entrywisemulComplexMatrixMatrix(a,b);
+ break;
+ case "spmatrix":
+ if ( a.m != b.m || a.n != b.n ) {
+ error("Error in entrywisemul(A,B): size(A) = [" + a.m + "," + a.n + "] != [" + b.m + "," + b.n + "] = size(B).");
+ return undefined;
+ }
+ return entrywisemulComplexMatrixspMatrix(a,b);
+ break;
+ case "ComplexMatrix":
+ if ( a.m != b.m || a.n != b.n ) {
+ error("Error in entrywisemul(A,B): size(A) = [" + a.m + "," + a.n + "] != [" + b.m + "," + b.n + "] = size(B).");
+ return undefined;
+ }
+ return entrywisemulComplexMatrices(a,b);
+ break;
+ default:
+ return undefined;
+ break;
+ }
+ break;
+ default:
+ return undefined;
+ break;
+ }
+}
+
+
+/** SAXPY : y = y + ax
+ * @param {number}
+ * @param {Float64Array}
+ * @param {Float64Array}
+ */
+function saxpy ( a, x, y) {
+ const n = y.length;
+ for ( var i=0; i < n; i++)
+ y[i] += a*x[i];
+}
+/** GAXPY : y = y + Ax
+ * @param {Matrix}
+ * @param {Float64Array}
+ * @param {Float64Array}
+ */
+function gaxpy ( A, x, y) {
+ const m = A.m;
+ const n = A.n;
+ var r = 0;
+ for ( var i=0; i < m; i++) {
+ y[i] += dot(A.val.subarray(r, r + n),x);
+ r += n;
+ }
+}
+
+/**
+ * @param {Float64Array}
+ * @param {number}
+ * @return {Float64Array}
+ */
+function divVectorScalar( a, b) {
+ var i;
+ const n = a.length;
+ var res = new Float64Array(a);
+ for ( i=0; i < n; i++)
+ res[i] /= b;
+ return res;
+}
+/**
+ * @param {number}
+ * @param {Float64Array}
+ * @return {Float64Array}
+ */
+function divScalarVector ( a, b) {
+ var i;
+ const n = b.length;
+ var res = new Float64Array(n);
+ for ( i=0; i < n; i++)
+ res[i] = a / b[i];
+ return res;
+}
+/**
+ * @param {Float64Array}
+ * @param {Float64Array}
+ * @return {Float64Array}
+ */
+function divVectors( a, b) {
+ var i;
+ const n = a.length;
+ var res = new Float64Array(a);
+ for ( i=0; i < n; i++)
+ res[i] /= b[i];
+ return res;
+}
+/**
+ * @param {Matrix}
+ * @param {number}
+ * @return {Matrix}
+ */
+function divMatrixScalar( A, b) {
+ var res = new Matrix(A.m, A.n, divVectorScalar(A.val , b ), true);
+ return res;
+}
+/**
+ * @param {number}
+ * @param {Matrix}
+ * @return {Matrix}
+ */
+function divScalarMatrix( a, B) {
+ var res = new Matrix(B.m, B.n, divScalarVector(a, B.val ), true);
+ return res;
+}
+/**
+ * @param {Matrix}
+ * @param {Matrix}
+ * @return {Matrix}
+ */
+function divMatrices( A, B) {
+ var res = new Matrix(A.m, A.n, divVectors(A.val, B.val ), true);
+ return res;
+}
+
+function entrywisediv(a,b) {
+ var ta = type(a);
+ var tb = type(b);
+
+ switch(ta) {
+ case "number":
+ switch(tb) {
+ case "number":
+ return a/b;
+ break;
+ case "vector":
+ return divScalarVector(a,b);
+ break;
+ case "matrix":
+ return divScalarMatrix(a,b);
+ break;
+ case "spvector":
+ return divScalarspVector(a,b);
+ break;
+ case "spmatrix":
+ return divScalarspMatrix(a,b);
+ break;
+ default:
+ error("Error in entrywisediv(a,b): b must be a number, a vector or a matrix.");
+ return undefined;
+ }
+ break;
+ case "vector":
+ switch(tb) {
+ case "number":
+ return divVectorScalar(a,b);
+ break;
+ case "vector":
+ if ( a.length != b.length ) {
+ error("Error in entrywisediv(a,b): a.length = " + a.length + " != " + b.length + " = b.length.");
+ return undefined;
+ }
+ return divVectors(a,b);
+ break;
+ case "spvector":
+ error("Error in entrywisediv(a,b): b is a sparse vector with zeros.");
+ break;
+ default:
+ error("Error in entrywisediv(a,B): a is a vector and B is a " + tb + ".");
+ return undefined;
+ }
+ break;
+ case "spvector":
+ switch(tb) {
+ case "number":
+ return mulScalarspVector(1/b, a);
+ break;
+ case "vector":
+ if ( a.length != b.length ) {
+ error("Error in entrywisediv(a,b): a.length = " + a.length + " != " + b.length + " = b.length.");
+ return undefined;
+ }
+ return divVectorspVector(a,b);
+ break;
+ case "spvector":
+ error("Error in entrywisediv(a,b): b is a sparse vector with zeros.");
+ return undefined;
+ break;
+ default:
+ error("Error in entrywisediv(a,B): a is a vector and B is a " + tb + ".");
+ return undefined;
+ }
+ break;
+ case "matrix":
+ switch(tb) {
+ case "number":
+ return divMatrixScalar(a,b);
+ break;
+ case "matrix":
+ if ( a.m != b.m || a.n != b.n ) {
+ error("Error in entrywisediv(A,B): size(A) = [" + a.m + "," + a.n + "] != [" + b.m + "," + b.n + "] = size(B).");
+ return undefined;
+ }
+ return divMatrices(a,b);
+ break;
+ case "spmatrix":
+ error("Error in entrywisediv(A,B): B is a sparse matrix with zeros.");
+ return undefined;
+ break;
+ default:
+ error("Error in entrywisediv(A,b): a is a matrix and B is a " + tb + ".");
+ return undefined;
+ }
+ case "spmatrix":
+ switch(tb) {
+ case "number":
+ return mulScalarspMatrix(1/b,a);
+ break;
+ case "matrix":
+ if ( a.m != b.m || a.n != b.n ) {
+ error("Error in entrywisediv(A,B): size(A) = [" + a.m + "," + a.n + "] != [" + b.m + "," + b.n + "] = size(B).");
+ return undefined;
+ }
+ return divMatrixspMatrix(a,b);
+ break;
+ case "spmatrix":
+ error("Error in entrywisediv(A,B): B is a sparse matrix with zeros.");
+ return undefined;
+ break;
+ default:
+ error("Error in entrywisediv(A,b): a is a matrix and B is a " + tb + ".");
+ return undefined;
+ }
+ break;
+ default:
+ error("Error in entrywisediv(a,b): a must be a number, a vector or a matrix.");
+ return undefined;
+ break;
+ }
+}
+
+function outerprodVectors(a, b, scalar) {
+ var i;
+ var j;
+ var ui;
+ const m = a.length;
+ const n = b.length;
+ var res = new Matrix(m,n);
+ if( arguments.length == 3 ) {
+ for (i=0; i< m; i++)
+ res.val.set( mulScalarVector(scalar*a[i], b), i*n);
+ }
+ else {
+ for (i=0; i< m; i++)
+ res.val.set( mulScalarVector(a[i], b), i*n);
+ }
+ return res;
+}
+function outerprod( u , v, scalar ) {
+ // outer product of two vectors : res = scalar * u * v^T
+
+ if (typeof(u) == "number" ) {
+ if ( typeof(v) == "number" ) {
+ if ( arguments.length == 2 )
+ return u*v;
+ else
+ return u*v*scalar;
+ }
+ else {
+ if ( arguments.length == 2 )
+ return new Matrix(1,v.length, mulScalarVector(u, v), true );
+ else
+ return new Matrix(1,v.length, mulScalarVector(u*scalar, v), true );
+ }
+ }
+ if ( u.length == 1 ) {
+ if ( typeof(v) == "number" ) {
+ if ( arguments.length == 2 )
+ return u[0]*v;
+ else
+ return u[0]*v*scalar;
+ }
+ else {
+ if ( arguments.length == 2 )
+ return new Matrix(1,v.length, mulScalarVector(u[0], v) , true);
+ else
+ return new Matrix(1,v.length, mulScalarVector(u[0]*scalar, v), true );
+ }
+ }
+ if (typeof(v) == "number" ) {
+ if (arguments.length == 2 )
+ return mulScalarVector(v, u);
+ else
+ return mulScalarVector( scalar * v , u);
+ }
+ if ( v.length == 1) {
+ if ( arguments.length == 2 )
+ return mulScalarVector(v[0], u);
+ else
+ return mulScalarVector( scalar * v[0] , u);
+ }
+
+ if ( arguments.length == 2 )
+ return outerprodVectors(u,v);
+ else
+ return outerprodVectors(u,v, scalar);
+}
+/**
+ * @param {number}
+ * @param {Float64Array}
+ * @return {Float64Array}
+ */
+function addScalarVector ( scalar, vec ) {
+ const n = vec.length;
+ var res = new Float64Array(vec);
+ for (var i = 0 ; i< n; i++)
+ res[i] += scalar ;
+
+ return res;
+}
+/**
+ * @param {number}
+ * @param {Matrix}
+ * @return {Matrix}
+ */
+function addScalarMatrix(a, B ) {
+ return new Matrix(B.m, B.n, addScalarVector(a, B.val), true );
+}
+/**
+ * @param {Float64Array}
+ * @param {Float64Array}
+ * @return {Float64Array}
+ */
+function addVectors(a,b) {
+ const n = a.length;
+ var c = new Float64Array(a);
+ for (var i=0; i < n; i++)
+ c[i] += b[i];
+ return c;
+}
+/**
+ * @param {Matrix}
+ * @param {Matrix}
+ * @return {Matrix}
+ */
+function addMatrices(A,B) {
+ return new Matrix(A.m, A.n, addVectors(A.val, B.val) , true);
+}
+function add(a,b) {
+
+ const ta = type(a);
+ const tb = type(b);
+ if ( ta == "number" && tb == "number" || ta == "string" || tb == "string")
+ return a + b;
+ else if ( ta == "number") {
+ switch(tb) {
+ case "Complex":
+ return addComplexReal(b,a);
+ break;
+ case "vector":
+ return addScalarVector(a,b);
+ break;
+ case "matrix":
+ return addScalarMatrix(a,b);
+ break;
+ case "spvector":
+ return addScalarspVector(a,b);
+ break;
+ case "spmatrix":
+ return addScalarspMatrix(a,b);
+ break;
+ case "ComplexVector":
+ return addScalarComplexVector(a,b);
+ break;
+ case "ComplexMatrix":
+ return addScalarComplexMatrix(a,b);
+ break;
+ default:
+ return undefined;
+ break;
+ }
+ }
+ else if ( tb == "number" ) {
+ switch(ta) {
+ case "Complex":
+ return addComplexReal(a,b);
+ break;
+ case "vector":
+ return addScalarVector(b,a);
+ break;
+ case "matrix":
+ return addScalarMatrix(b,a);
+ break;
+ case "spvector":
+ return addScalarspVector(b,a);
+ break;
+ case "spmatrix":
+ return addScalarspMatrix(b,a);
+ break;
+ case "ComplexVector":
+ return addScalarComplexVector(b,a);
+ break;
+ case "ComplexMatrix":
+ return addScalarComplexMatrix(b,a);
+ break;
+ default:
+ return undefined;
+ break;
+ }
+ }
+ else if ( ta == "vector" ) {
+ switch(tb) {
+ case "vector":
+ // vector addition
+ if ( a.length != b.length ) {
+ error("Error in add(a,b): a.length = " + a.length + " != " + b.length + " = b.length.");
+ return undefined;
+ }
+ return addVectors(a,b);
+ break;
+ case "spvector":
+ if ( a.length != b.length ) {
+ error("Error in add(a,b): a.length = " + a.length + " != " + b.length + " = b.length.");
+ return undefined;
+ }
+ return addVectorspVector(a,b);
+ break;
+ case "ComplexVector":
+ if ( a.length != b.length ) {
+ error("Error in add(a,b): a.length = " + a.length + " != " + b.length + " = b.length.");
+ return undefined;
+ }
+ return addComplexVectorVector(b,a);
+ break;
+ case "matrix":
+ case "spmatrix":
+ default:
+ error("Error in add(a,B): a is a vector and B is a " + tb + ".");
+ return undefined;
+ break;
+ }
+ }
+ else if ( ta == "matrix" ) {
+ switch(tb) {
+ case "matrix":
+ // Matrix addition
+ if ( a.m != b.m || a.n != b.n ) {
+ error("Error in add(A,B): size(A) = [" + a.m + "," + a.n + "] != [" + b.m + "," + b.n + "] = size(B).");
+ return undefined;
+ }
+ return addMatrices(a,b);
+ break;
+ case "spmatrix":
+ // Matrix addition
+ if ( a.m != b.m || a.n != b.n ) {
+ error("Error in add(A,B): size(A) = [" + a.m + "," + a.n + "] != [" + b.m + "," + b.n + "] = size(B).");
+ return undefined;
+ }
+ return addMatrixspMatrix(a,b);
+ break;
+ case "ComplexMatrix":
+ // Matrix addition
+ if ( a.m != b.m || a.n != b.n ) {
+ error("Error in add(A,B): size(A) = [" + a.m + "," + a.n + "] != [" + b.m + "," + b.n + "] = size(B).");
+ return undefined;
+ }
+ return addComplexMatrixMatrix(b,a);
+ break;
+ case "vector":
+ case "spvector":
+ default:
+ error("Error in add(A,b): a is a matrix and B is a " + tb + ".");
+ return undefined;
+ break;
+ }
+ }
+ else if ( ta == "spvector" ) {
+ switch(tb) {
+ case "vector":
+ // vector addition
+ if ( a.length != b.length ) {
+ error("Error in add(a,b): a.length = " + a.length + " != " + b.length + " = b.length.");
+ return undefined;
+ }
+ return addVectorspVector(b,a);
+ break;
+ case "spvector":
+ if ( a.length != b.length ) {
+ error("Error in add(a,b): a.length = " + a.length + " != " + b.length + " = b.length.");
+ return undefined;
+ }
+ return addspVectors(a,b);
+ break;
+ case "matrix":
+ case "spmatrix":
+ default:
+ error("Error in add(a,B): a is a sparse vector and B is a " + tb + ".");
+ return undefined;
+ break;
+ }
+ }
+ else if ( ta == "spmatrix" ) {
+ switch(tb) {
+ case "matrix":
+ // Matrix addition
+ if ( a.m != b.m || a.n != b.n ) {
+ error("Error in add(A,B): size(A) = [" + a.m + "," + a.n + "] != [" + b.m + "," + b.n + "] = size(B).");
+ return undefined;
+ }
+ return addMatrixspMatrix(b,a);
+ break;
+ case "spmatrix":
+ // Matrix addition
+ if ( a.m != b.m || a.n != b.n ) {
+ error("Error in add(A,B): size(A) = [" + a.m + "," + a.n + "] != [" + b.m + "," + b.n + "] = size(B).");
+ return undefined;
+ }
+ return addspMatrices(a,b);
+ break;
+ case "vector":
+ case "spvector":
+ default:
+ error("Error in add(A,b): a is a sparse matrix and B is a " + tb + ".");
+ return undefined;
+ break;
+ }
+ }
+ else if ( ta == "ComplexVector" ) {
+ switch(tb) {
+ case "vector":
+ // vector addition
+ if ( a.length != b.length ) {
+ error("Error in add(a,b): a.length = " + a.length + " != " + b.length + " = b.length.");
+ return undefined;
+ }
+ return addComplexVectorVector(a,b);
+ break;
+ case "spvector":
+ if ( a.length != b.length ) {
+ error("Error in add(a,b): a.length = " + a.length + " != " + b.length + " = b.length.");
+ return undefined;
+ }
+ return addComplexVectorspVector(a,b);
+ break;
+ case "ComplexVector":
+ if ( a.length != b.length ) {
+ error("Error in add(a,b): a.length = " + a.length + " != " + b.length + " = b.length.");
+ return undefined;
+ }
+ return addComplexVectors(b,a);
+ break;
+ case "matrix":
+ case "spmatrix":
+ default:
+ error("Error in add(a,B): a is a vector and B is a " + tb + ".");
+ return undefined;
+ break;
+ }
+ }
+ else if ( ta == "ComplexMatrix" ) {
+ switch(tb) {
+ case "matrix":
+ // Matrix addition
+ if ( a.m != b.m || a.n != b.n ) {
+ error("Error in add(A,B): size(A) = [" + a.m + "," + a.n + "] != [" + b.m + "," + b.n + "] = size(B).");
+ return undefined;
+ }
+ return addComplexMatrixMatrix(a,b);
+ break;
+ case "spmatrix":
+ // Matrix addition
+ if ( a.m != b.m || a.n != b.n ) {
+ error("Error in add(A,B): size(A) = [" + a.m + "," + a.n + "] != [" + b.m + "," + b.n + "] = size(B).");
+ return undefined;
+ }
+ return addComplexMatrixspMatrix(a,b);
+ break;
+ case "ComplexMatrix":
+ // Matrix addition
+ if ( a.m != b.m || a.n != b.n ) {
+ error("Error in add(A,B): size(A) = [" + a.m + "," + a.n + "] != [" + b.m + "," + b.n + "] = size(B).");
+ return undefined;
+ }
+ return addComplexMatrices(a,b);
+ break;
+ case "vector":
+ case "spvector":
+ default:
+ error("Error in add(A,b): a is a matrix and B is a " + tb + ".");
+ return undefined;
+ break;
+ }
+ }
+ else
+ return undefined;
+}
+/**
+ * @param {number}
+ * @param {Float64Array}
+ * @return {Float64Array}
+ */
+function subScalarVector ( scalar, vec ) {
+ const n = vec.length;
+ var res = new Float64Array(n);
+ for (var i = 0 ; i< n; i++)
+ res[i] = scalar - vec[i];
+
+ return res;
+}
+/**
+ * @param {Float64Array}
+ * @param {number}
+ * @return {Float64Array}
+ */
+function subVectorScalar ( vec, scalar ) {
+ const n = vec.length;
+ var res = new Float64Array(vec);
+ for (var i = 0 ; i< n; i++)
+ res[i] -= scalar;
+
+ return res;
+}
+/**
+ * @param {number}
+ * @param {Matrix}
+ * @return {Matrix}
+ */
+function subScalarMatrix(a, B ) {
+ return new Matrix(B.m, B.n, subScalarVector(a, B.val), true );
+}
+/**
+ * @param {Matrix}
+ * @param {number}
+ * @return {Matrix}
+ */
+function subMatrixScalar(B, a ) {
+ return new Matrix(B.m, B.n, subVectorScalar(B.val, a) , true);
+}
+/**
+ * @param {Float64Array}
+ * @param {Float64Array}
+ * @return {Float64Array}
+ */
+function subVectors(a,b) {
+ const n = a.length;
+ var c = new Float64Array(a);
+ for (var i=0; i < n; i++)
+ c[i] -= b[i];
+ return c;
+}
+/**
+ * @param {Matrix}
+ * @param {Matrix}
+ * @return {Matrix}
+ */
+function subMatrices(A,B) {
+ return new Matrix(A.m, A.n, subVectors(A.val, B.val), true );
+}
+function sub(a,b) {
+
+ const ta = type(a);
+ const tb = type(b);
+ if ( ta == "number" && tb == "number" )
+ return a - b;
+ else if ( ta == "number") {
+ switch(tb) {
+ case "Complex":
+ return addComplexReal(minusComplex(b),a);
+ break;
+ case "vector":
+ return subScalarVector(a,b);
+ break;
+ case "matrix":
+ return subScalarMatrix(a,b);
+ break;
+ case "spvector":
+ return subScalarspVector(a,b);
+ break;
+ case "spmatrix":
+ return subScalarspMatrix(a,b);
+ break;
+ default:
+ return undefined;
+ break;
+ }
+ }
+ else if ( tb == "number" ) {
+ switch(ta) {
+ case "Complex":
+ return addComplexReal(b,-a);
+ break;
+ case "vector":
+ return subVectorScalar (a, b);
+ break;
+ case "matrix":
+ return subMatrixScalar(a,b);
+ break;
+ case "spvector":
+ return addScalarspVector(-b,a);
+ break;
+ case "spmatrix":
+ return addScalarspMatrix(-b,a);
+ break;
+ default:
+ return undefined;
+ break;
+ }
+ }
+ else if ( ta == "vector" ) {
+ switch(tb) {
+ case "vector":
+ // vector substraction
+ if ( a.length != b.length ) {
+ error("Error in sub(a,b): a.length = " + a.length + " != " + b.length + " = b.length.");
+ return undefined;
+ }
+ return subVectors(a,b);
+ break;
+ case "spvector":
+ // vector substraction
+ if ( a.length != b.length ) {
+ error("Error in sub(a,b): a.length = " + a.length + " != " + b.length + " = b.length.");
+ return undefined;
+ }
+ return subVectorspVector(a,b);
+ break;
+ case "matrix":
+ case "spmatrix":
+ default:
+ error("Error in sub(a,B): a is a vector and B is a " + tb + ".");
+ return undefined;
+ break;
+ }
+ }
+ else if ( ta == "matrix" ) {
+ switch(tb) {
+ case "matrix":
+ // Matrix sub
+ if ( a.m != b.m || a.n != b.n ) {
+ error("Error in sub(A,B): size(A) = [" + a.m + "," + a.n + "] != [" + b.m + "," + b.n + "] = size(B).");
+ return undefined;
+ }
+ return subMatrices(a,b);
+ break;
+ case "spmatrix":
+ // Matrix addition
+ if ( a.m != b.m || a.n != b.n ) {
+ error("Error in sub(A,B): size(A) = [" + a.m + "," + a.n + "] != [" + b.m + "," + b.n + "] = size(B).");
+ return undefined;
+ }
+ return subMatrixspMatrix(a,b);
+ break;
+ case "vector":
+ case "spvector":
+ default:
+ error("Error in sub(A,b): A is a matrix and b is a " + tb + ".");
+ return undefined;
+ break;
+ }
+ }
+ else if ( ta == "spvector" ) {
+ switch(tb) {
+ case "vector":
+ if ( a.length != b.length ) {
+ error("Error in sub(a,b): a.length = " + a.length + " != " + b.length + " = b.length.");
+ return undefined;
+ }
+ return subspVectorVector(a,b);
+ break;
+ case "spvector":
+ if ( a.length != b.length ) {
+ error("Error in sub(a,b): a.length = " + a.length + " != " + b.length + " = b.length.");
+ return undefined;
+ }
+ return subspVectors(a,b);
+ break;
+ case "matrix":
+ case "spmatrix":
+ default:
+ error("Error in sub(a,B): a is a sparse vector and B is a " + tb + ".");
+ return undefined;
+ break;
+ }
+ }
+ else if ( ta == "spmatrix" ) {
+ switch(tb) {
+ case "matrix":
+ if ( a.m != b.m || a.n != b.n ) {
+ error("Error in sub(A,B): size(A) = [" + a.m + "," + a.n + "] != [" + b.m + "," + b.n + "] = size(B).");
+ return undefined;
+ }
+ return subspMatrixMatrix(a,b);
+ break;
+ case "spmatrix":
+ if ( a.m != b.m || a.n != b.n ) {
+ error("Error in sub(A,B): size(A) = [" + a.m + "," + a.n + "] != [" + b.m + "," + b.n + "] = size(B).");
+ return undefined;
+ }
+ return subspMatrices(a,b);
+ break;
+ case "vector":
+ case "spvector":
+ default:
+ error("Error in sub(A,b): a is a sparse matrix and B is a " + tb + ".");
+ return undefined;
+ break;
+ }
+ }
+ else
+ return undefined;
+}
+
+function pow(a,b) {
+ var i;
+ const ta = type(a);
+ const tb = type(b);
+
+ if ( ta == "number" && tb == "number" )
+ return Math.pow(a, b);
+ else if ( ta == "number") {
+ if ( tb == "vector" ) {
+ var c = zeros(b.length);
+ if ( !isZero(a) ) {
+ for (i=0;i 0 && a.val.length < a.length )
+ return 0;
+ else
+ return m;
+ break;
+ case "matrix":
+ return minMatrix(a);
+ break;
+ case "spmatrix":
+ var m = minVector(a.val);
+ if ( m > 0 && a.val.length < a.m * a.n )
+ return 0;
+ else
+ return m;
+ break;
+ default:
+ return a;
+ break;
+ }
+ }
+
+ var tb = type(b);
+ if (ta == "spvector" ) {
+ a = fullVector(a);
+ ta = "vector";
+ }
+ if (ta == "spmatrix" ) {
+ a = fullMatrix(a);
+ ta = "matrix";
+ }
+ if (tb == "spvector" ) {
+ b = fullVector(b);
+ tb = "vector";
+ }
+ if (tb == "spmatrix" ) {
+ b = fullMatrix(b);
+ tb = "matrix";
+ }
+
+ if ( ta == "number" && tb == "number" )
+ return Math.min(a,b);
+ else if ( ta == "number") {
+ if ( tb == "vector" )
+ return minVectorScalar(b, a ) ;
+ else
+ return minMatrixScalar(b, a ) ;
+ }
+ else if ( tb == "number" ) {
+ if ( ta == "vector" )
+ return minVectorScalar(a, b);
+ else {
+ // MAtrix , scalar
+ if ( b == 1)
+ return minMatrixRows(a); // return row vector of min of columns
+ else if ( b == 2 )
+ return minMatrixCols(a); // return column vector of min of rows
+ else
+ return minMatrixScalar(a, b);
+ }
+ }
+ else if ( ta == "vector" ) {
+ if ( tb == "vector" )
+ return minVectorVector(a,b);
+ else
+ return "undefined";
+ }
+ else {
+ if ( tb == "matrix" )
+ return minMatrixMatrix(a,b);
+ else
+ return "undefined";
+ }
+}
+
+// maximum
+/**
+ * @param {Float64Array}
+ * @return {number}
+ */
+function maxVector( a ) {
+ const n = a.length;
+ var res = a[0];
+ for (var i = 1; i < n ; i++) {
+ if ( a[i] > res)
+ res = a[i];
+ }
+ return res;
+}
+/**
+ * @param {Matrix}
+ * @return {number}
+ */
+function maxMatrix( A ) {
+ return maxVector(A.val);
+}
+/**
+ * @param {Float64Array}
+ * @param {number}
+ * @return {Float64Array}
+ */
+function maxVectorScalar(vec, scalar ) {
+ const n = vec.length;
+ var res = new Float64Array(vec);
+ for (var i = 0; i < n ; i++) {
+ if ( scalar > vec[i])
+ res[i] = scalar;
+ }
+ return res;
+}
+/**
+ * @param {Matrix}
+ * @param {number}
+ * @return {Matrix}
+ */
+function maxMatrixScalar(A, scalar ) {
+ return maxVectorScalar(A.val, scalar);
+}
+/**
+ * @param {Matrix}
+ * @return {Matrix}
+ */
+function maxMatrixRows( A ) {
+ const m = A.m;
+ const n = A.n;
+ var res = new Float64Array(A.val.subarray(0,n) );
+ var j;
+ var r = n;
+ for ( var i=1; i < m; i++) {
+ for ( j = 0; j < n; j++)
+ if( A.val[r + j] > res[j])
+ res[j] = A.val[r + j];
+ r += n;
+ }
+ return new Matrix(1,n,res,true);
+}
+/**
+ * @param {Matrix}
+ * @return {Float64Array}
+ */
+function maxMatrixCols( A ) {
+ const m = A.m;
+ var res = new Float64Array(m);
+ var r = 0;
+ for ( var i=0; i < m; i++) {
+ res[i] = maxVector(A.val.subarray(r, r+A.n) );
+ r += A.n;
+ }
+ return res;
+}
+/**
+ * @param {Float64Array}
+ * @param {Float64Array}
+ * @return {Float64Array}
+ */
+function maxVectorVector(a, b) {
+ var n = a.length;
+ var res = new Float64Array(a);
+ for (var i = 0; i < n ; i++) {
+ if ( b[i] > a[i])
+ res[i] = b[i];
+ }
+ return res;
+}
+/**
+ * @param {Matrix}
+ * @param {Matrix}
+ * @return {Matrix}
+ */
+function maxMatrixMatrix( A, B ) {
+ return new Matrix(A.m, A.n, maxVectorVector(A.val, B.val), true);
+}
+function max(a,b) {
+ var ta = type(a);
+
+ if ( arguments.length == 1 ) {
+ switch( ta ) {
+ case "vector":
+ return maxVector(a);
+ break;
+ case "spvector":
+ var m = maxVector(a.val);
+ if ( m < 0 && a.val.length < a.length )
+ return 0;
+ else
+ return m;
+ break;
+ case "matrix":
+ return maxMatrix(a);
+ break;
+ case "spmatrix":
+ var m = maxVector(a.val);
+ if ( m < 0 && a.val.length < a.m * a.n )
+ return 0;
+ else
+ return m;
+ break;
+ default:
+ return a;
+ break;
+ }
+ }
+
+ var tb = type(b);
+ if (ta == "spvector" ) {
+ a = fullVector(a);
+ ta = "vector";
+ }
+ if (ta == "spmatrix" ) {
+ a = fullMatrix(a);
+ ta = "matrix";
+ }
+ if (tb == "spvector" ) {
+ b = fullVector(b);
+ tb = "vector";
+ }
+ if (tb == "spmatrix" ) {
+ b = fullMatrix(b);
+ tb = "matrix";
+ }
+
+ if ( ta == "number" && tb == "number" )
+ return Math.max(a,b);
+ else if ( ta == "number") {
+ if ( tb == "vector" )
+ return maxVectorScalar(b, a ) ;
+ else
+ return maxMatrixScalar(b, a ) ;
+ }
+ else if ( tb == "number" ) {
+ if ( ta == "vector" )
+ return maxVectorScalar(a, b);
+ else {
+ // MAtrix , scalar
+ if ( b == 1)
+ return maxMatrixRows(a); // return row vector of max of columns
+ else if ( b == 2 )
+ return maxMatrixCols(a); // return column vector of max of rows
+ else
+ return maxMatrixScalar(a, b);
+ }
+ }
+ else if ( ta == "vector" ) {
+ if ( tb == "vector" )
+ return maxVectorVector(a,b);
+ else
+ return "undefined";
+ }
+ else {
+ if ( tb == "matrix" )
+ return maxMatrixMatrix(a,b);
+ else
+ return "undefined";
+ }
+}
+/**
+ * @param {Matrix}
+ */
+function transposeMatrix ( A ) {
+ var i;
+ var j;
+ const m = A.m;
+ const n = A.n;
+ if ( m > 1 ) {
+ var res = zeros( n,m);
+ var Aj = 0;
+ for ( j=0; j< m;j++) {
+ var ri = 0;
+ for ( i=0; i < n ; i++) {
+ res.val[ri + j] = A.val[Aj + i];
+ ri += m;
+ }
+ Aj += n;
+ }
+ return res;
+ }
+ else {
+ return A.val;
+ }
+}
+/**
+ * @param {Float64Array}
+ * @return {Matrix}
+ */
+function transposeVector ( a ) {
+ return new Matrix(1,a.length, a);
+}
+function transpose( A ) {
+ var i;
+ var j;
+ switch( type( A ) ) {
+ case "number":
+ return A;
+ break;
+ case "vector":
+ var res = new Matrix(1,A.length, A);
+ return res; // matrix with a single row
+ break;
+ case "spvector":
+ return transposespVector(A);
+ break;
+ case "ComplexVector":
+ var res = new ComplexMatrix(1,A.length, conj(A));
+ return res; // matrix with a single row
+ break;
+ case "matrix":
+ return transposeMatrix(A);
+ break;
+ case "spmatrix":
+ return transposespMatrix(A);
+ break;
+ case "ComplexMatrix":
+ return transposeComplexMatrix(A);
+ break;
+ default:
+ return undefined;
+ break;
+ }
+}
+
+/**
+ * @param {Matrix}
+ * @return {number}
+ */
+function det( A ) {
+ const n = A.n;
+ if ( A.m != n || typeof(A.m) =="undefined")
+ return undefined;
+
+ if ( n == 2 ) {
+ return A.val[0]*A.val[3] - A.val[1]*A.val[2];
+ }
+ else {
+ var detA = 0;
+ var i,j;
+ for ( i=0; i < n; i++ ) {
+ var proddiag = 1;
+ for ( j=0; j < n ; j++)
+ proddiag *= A.val[( (i+j)%n ) * n + j];
+
+ detA += proddiag;
+ }
+ for ( i=0; i < n; i++ ) {
+ var proddiag = 1;
+ for ( j=0; j < n ; j++)
+ proddiag *= A.val[( (i+n-1-j)%n ) * n + j];
+
+ detA -= proddiag;
+ }
+ }
+ return detA;
+}
+function trace ( A ) {
+ if ( type(A) == "matrix") {
+ var n = A.length;
+ if ( A.m != n )
+ return "undefined";
+ var res = 0;
+ for ( var i =0; i< n;i++)
+ res += A.val[i*n + i];
+ return res;
+ }
+ else {
+ return undefined;
+ }
+}
+/**
+ * @param {Matrix}
+ * @return {Matrix}
+ */
+function triu ( A ) {
+ // return the upper triangular part of A
+ var i;
+ var j;
+ const n = A.n;
+ const m = A.m;
+ var res = zeros(m, n);
+ var im = m;
+ if ( n < m )
+ im = n;
+ var r = 0;
+ for (i=0; i < im; i++) {
+ for ( j=i; j < n; j++)
+ res.val[r + j] = A.val[r + j];
+ r += n;
+ }
+ return res;
+}
+/**
+ * @param {Matrix}
+ * @return {Matrix}
+ */
+function tril ( A ) {
+ // return the lower triangular part of A
+ var i;
+ var j;
+ const n = A.n;
+ const m = A.m;
+ var res = zeros(m, n);
+ var im = m;
+ if ( n < m )
+ im = n;
+ var r = 0;
+ for (i=0; i < im; i++) {
+ for ( j=0; j <= i; j++)
+ res.val[r + j] = A.val[r + j];
+ r += n;
+ }
+ if ( m > im ) {
+ for (i=im; i < m; i++) {
+ for ( j=0; j < n; j++)
+ res.val[r + j] = A.val[r + j];
+ r += n;
+ }
+ }
+ return res;
+}
+
+/**
+ * @param {Matrix}
+ * @return {boolean}
+ */
+function issymmetric ( A ) {
+ const m = A.m;
+ const n= A.n;
+ if ( m != n )
+ return false;
+
+ for (var i=0;i < m; i++)
+ for ( var j=0; j < n; j++)
+ if ( A.val[i*n+j] != A.val[j*n+i] )
+ return false;
+
+ return true;
+}
+
+/** Concatenate matrices/vectors
+ * @param {Array}
+ * @param {boolean}
+ * @return {Matrix}
+ */
+function mat( elems, rowwise ) {
+ var k;
+ var concatWithNumbers = false;
+ var elemtypes = new Array(elems.length);
+ for ( k=0; k < elems.length; k++) {
+ elemtypes[k] = type(elems[k]);
+ if ( elemtypes[k] == "number" )
+ concatWithNumbers = true;
+ }
+
+
+ if (typeof(rowwise ) == "undefined") {
+ // check if vector of numbers
+ if ( type(elems) == "vector" )
+ return new Float64Array(elems);
+
+ // check if 2D Array => toMatrix rowwise
+ var rowwise = true;
+ for (k=0; k < elems.length; k++) {
+ if ( !Array.isArray(elems[k] ) || elemtypes[k] == "vector" ) {
+ rowwise = false;
+ if ( elemtypes[k] == "string" )
+ return elems; // received vector of strings => return it directly
+ }
+ }
+ }
+
+ if ( elems.length == 0 ) {
+ return [];
+ }
+
+ var m = 0;
+ var n = 0;
+ var i;
+ var j;
+ if ( rowwise ) {
+ var res = new Array( ) ;
+
+ for ( k= 0; k return Array2D
+ return elems;
+ break;
+ }
+ }
+ if ( n == 1) {
+ var M = new Float64Array(res);
+ return M;
+ }
+ var M = new Matrix( m , n ) ;
+ var p = 0;
+ for (k=0; k < res.length ; k++) {
+ if(res[k].buffer) {
+ M.val.set( res[k], p);
+ p += res[k].length;
+ }
+ else {
+ for ( j=0; j < res[k].length; j++)
+ M.val[p+j] = res[k][j];
+ p += res[k].length;
+ }
+ }
+ return M;
+ }
+ else {
+ // compute size
+ m = size(elems[0], 1);
+ for ( k= 0; k EPS )
+ res[i] = 1;
+ }
+ return res;
+ break;
+ case "matrix":
+ res = new Matrix(a.m, a.n, isGreater(a.val, b), true );
+ return res;
+ break;
+ default:
+ return (a>b?1:0);
+ }
+ }
+ else if (ta == tb) {
+
+ switch( ta ) {
+ case "number":
+ return (a>b?1:0);
+ break;
+ case "vector":
+ res = new Float64Array(a.length);
+ for ( i=0; i EPS )
+ res[i] = 1;
+ }
+ return res;
+ break;
+ case "matrix":
+ res = new Matrix(a.m, a.n, isGreater(a.val, b.val), true );
+ return res;
+ break;
+ default:
+ return (a>b?1:0);
+ }
+ }
+ else
+ return "undefined";
+}
+function isGreaterOrEqual( a, b) {
+ var i;
+ var j;
+ var res;
+ var ta = type(a);
+ var tb = type(b);
+
+ if ( ta == "number" && tb != "number" )
+ return isGreaterOrEqual(b,a);
+
+ if( ta != "number" && tb == "number" ) {
+ // vector/matrix + scalar
+ switch( ta ) {
+ case "vector":
+ res = new Float64Array(a.length);
+ for ( i=0; i -EPS )
+ res[i] = 1;
+ }
+ return res;
+ break;
+ case "matrix":
+ res = new Matrix(a.m, a.n, isGreaterOrEqual(a.val, b), true );
+ return res;
+ break;
+ default:
+ return (a>=b?1:0);
+ }
+ }
+ else if ( ta == tb ) {
+
+ switch( ta ) {
+ case "number":
+ return (a>=b);
+ break;
+ case "vector":
+ res = new Float64Array(a.length);
+ for ( i=0; i -EPS )
+ res[i] = 1;
+ }
+ return res;
+ break;
+ case "matrix":
+ res = new Matrix(a.m, a.n, isGreaterOrEqual(a.val, b.val), true );
+ return res;
+ break;
+ default:
+ return (a>=b?1:0);
+ }
+ }
+ else
+ return "undefined";
+}
+
+function isLower( a, b) {
+ var i;
+ var j;
+ var res;
+ var ta = type(a);
+ var tb = type(b);
+
+ if ( ta == "number" && tb != "number" )
+ return isLower(b,a);
+
+ if( ta != "number" && tb == "number" ) {
+ // vector/matrix + scalar
+ switch( ta ) {
+ case "vector":
+ res = new Float64Array(a.length);
+ for ( i=0; i EPS )
+ res[i] = 1;
+ }
+ return res;
+ break;
+ case "matrix":
+ res = new Matrix(a.m, a.n, isLower(a.val, b), true );
+ return res;
+ break;
+ default:
+ return (a EPS )
+ res[i] = 1;
+ }
+ return res;
+ break;
+ case "matrix":
+ res = new Matrix(a.m, a.n, isLower(a.val, b.val), true );
+ return res;
+ break;
+ default:
+ return (a -EPS )
+ res[i] = 1;
+ }
+ return res;
+ break;
+ case "matrix":
+ res = new Matrix(a.m, a.n, isLowerOrEqual(a.val, b), true );
+ return res;
+ break;
+ default:
+ return (a<=b?1:0);
+ }
+ }
+ else if ( ta == tb ) {
+
+ switch( ta ) {
+ case "number":
+ return (a<=b?1:0);
+ break;
+ case "vector":
+ res = new Float64Array(a.length);
+ for ( i=0; i -EPS )
+ res[i] = 1;
+ }
+ return res;
+ break;
+ case "matrix":
+ res = new Matrix(a.m, a.n, isLowerOrEqual(a.val, b.val) , true);
+ return res;
+ break;
+ default:
+ return (a<=b?1:0);
+ }
+ }
+ else
+ return "undefined";
+}
+
+
+function find( b ) {
+ // b is a boolean vector of 0 and 1.
+ // return the indexes of the 1's.
+ var i;
+ var n = b.length;
+ var res = new Array();
+ for ( i=0; i < n; i++) {
+ if ( b[i] != 0 )
+ res.push(i);
+ }
+ return res;
+}
+argmax = findmax;
+function findmax( x ) {
+ // return the index of the maximum in x
+ var i;
+
+ switch ( type(x)) {
+ case "number":
+ return 0;
+ break;
+ case "vector":
+ var idx = 0;
+ var maxi = x[0];
+ for ( i= 1; i< x.length; i++) {
+ if ( x[i] > maxi ) {
+ maxi = x[i];
+ idx = i;
+ }
+ }
+ return idx;
+ break;
+ case "spvector":
+ var maxi = x.val[0];
+ var idx = x.ind[0];
+
+ for ( i= 1; i< x.val.length; i++) {
+ if ( x.val[i] > maxi ) {
+ maxi = x.val[i];
+ idx = x.ind[i];
+ }
+ }
+ if ( maxi < 0 && x.val.length < x.length ) {
+ idx = 0;
+ while ( x.ind.indexOf(idx) >= 0 && idx < x.length)
+ idx++;
+ }
+ return idx;
+ break;
+ default:
+ return "undefined";
+ }
+
+}
+argmin = findmin;
+function findmin( x ) {
+ // return the index of the minimum in x
+ var i;
+
+ switch ( type(x)) {
+ case "number":
+ return 0;
+ break;
+ case "vector":
+ var idx = 0;
+ var mini = x[0];
+ for ( i= 1; i< x.length; i++) {
+ if ( x[i] < mini ) {
+ mini = x[i];
+ idx = i;
+ }
+ }
+ return idx;
+ break;
+ case "spvector":
+ var mini = x.val[0];
+ var idx = x.ind[0];
+
+ for ( i= 1; i< x.val.length; i++) {
+ if ( x.val[i] < mini ) {
+ mini = x.val[i];
+ idx = x.ind[i];
+ }
+ }
+ if ( mini > 0 && x.val.length < x.length ) {
+ idx = 0;
+ while ( x.ind.indexOf(idx) >= 0 && idx < x.length)
+ idx++;
+ }
+ return idx;
+ break;
+ default:
+ return "undefined";
+ }
+
+}
+
+/**
+ * @param {Float64Array}
+ * @param {boolean}
+ * @param {boolean}
+ * @return {Float64Array|Array}
+ */
+function sort( x, decreasingOrder , returnIndexes) {
+ // if returnIndexes = true : replace x with its sorted version
+ // otherwise return a sorted copy without altering x
+
+ if ( typeof(decreasingOrder) == "undefined")
+ var decreasingOrder = false;
+ if ( typeof(returnIndexes) == "undefined")
+ var returnIndexes = false;
+
+ var i;
+ var j;
+ var tmp;
+
+ const n = x.length;
+ if ( returnIndexes ) {
+ var indexes = range(n);
+ for ( i=0; i < n - 1; i++) {
+ if ( decreasingOrder )
+ j = findmax( get ( x, range(i,n) ) ) + i ;
+ else
+ j = findmin( get ( x, range(i,n) ) ) + i;
+
+ if ( i!=j) {
+ tmp = x[i];
+ x[i] = x[j];
+ x[j] = tmp;
+
+ tmp = indexes[i];
+ indexes[i] = indexes[j];
+ indexes[j] = tmp;
+ }
+ }
+ return indexes;
+ }
+ else {
+ var xs = vectorCopy(x);
+ for ( i=0; i < n - 1; i++) {
+ if ( decreasingOrder )
+ j = findmax( get ( xs, range(i,n) ) ) + i;
+ else
+ j = findmin( get ( xs, range(i,n) ) ) + i;
+
+ if ( i!=j) {
+ tmp = xs[i];
+ xs[i] = xs[j];
+ xs[j] = tmp;
+ }
+ }
+ return xs;
+ }
+}
+
+/// Stats
+/**
+ * @param {Float64Array}
+ * @return {number}
+ */
+function sumVector ( a ) {
+ var i;
+ const n = a.length;
+ var res = a[0];
+ for ( i=1; i< n; i++)
+ res += a[i];
+ return res;
+}
+/**
+ * @param {Matrix}
+ * @return {number}
+ */
+function sumMatrix ( A ) {
+ return sumVector(A.val);
+}
+/**
+ * @param {Matrix}
+ * @return {Matrix}
+ */
+function sumMatrixRows( A ) {
+ var i;
+ var j;
+ const m = A.m;
+ const n = A.n;
+ var res = new Float64Array(n);
+ var r = 0;
+ for ( i=0; i< m; i++) {
+ for (j=0; j < n; j++)
+ res[j] += A.val[r + j];
+ r += n;
+ }
+ return new Matrix(1,n,res, true); // return row vector
+}
+/**
+ * @param {Matrix}
+ * @return {Float64Array}
+ */
+function sumMatrixCols( A ) {
+ const m = A.m;
+ var res = new Float64Array(m);
+ var r = 0;
+ for ( var i=0; i < m; i++) {
+ for (var j=0; j < A.n; j++)
+ res[i] += A.val[r + j];
+ r += A.n;
+ }
+ return res;
+}
+function sum( A , sumalongdimension ) {
+
+ switch ( type( A ) ) {
+ case "vector":
+ if ( arguments.length == 1 || sumalongdimension == 1 ) {
+ return sumVector(A);
+ }
+ else {
+ return vectorCopy(A);
+ }
+ break;
+ case "spvector":
+ if ( arguments.length == 1 || sumalongdimension == 1 )
+ return sumVector(A.val);
+ else
+ return A.copy();
+ break;
+
+ case "matrix":
+ if( arguments.length == 1 ) {
+ return sumMatrix( A ) ;
+ }
+ else if ( sumalongdimension == 1 ) {
+ return sumMatrixRows( A );
+ }
+ else if ( sumalongdimension == 2 ) {
+ return sumMatrixCols( A );
+ }
+ else
+ return undefined;
+ break;
+ case "spmatrix":
+ if( arguments.length == 1 ) {
+ return sumVector( A.val ) ;
+ }
+ else if ( sumalongdimension == 1 ) {
+ return sumspMatrixRows( A );
+ }
+ else if ( sumalongdimension == 2 ) {
+ return sumspMatrixCols( A );
+ }
+ else
+ return undefined;
+ break;
+ default:
+ return A;
+ break;
+ }
+}
+/**
+ * @param {Float64Array}
+ * @return {number}
+ */
+function prodVector ( a ) {
+ var i;
+ const n = a.length;
+ var res = a[0];
+ for ( i=1; i< n; i++)
+ res *= a[i];
+ return res;
+}
+/**
+ * @param {Matrix}
+ * @return {number}
+ */
+function prodMatrix ( A ) {
+ return prodVector(A.val);
+}
+/**
+ * @param {Matrix}
+ * @return {Matrix}
+ */
+function prodMatrixRows( A ) {
+ var i;
+ var j;
+ const m = A.m;
+ const n = A.n;
+ var res = new Float64Array(A.row(0));
+ var r = n;
+ for ( i=1; i< m; i++) {
+ for (j=0; j < n; j++)
+ res[j] *= A.val[r + j];
+ r += A.n;
+ }
+ return new Matrix(1,n,res, true); // return row vector
+}
+/**
+ * @param {Matrix}
+ * @return {Float64Array}
+ */
+function prodMatrixCols( A ) {
+ const m = A.m;
+ var res = new Float64Array(m);
+ var r = 0;
+ for ( var i=0; i < m; i++) {
+ res[i] = A.val[r];
+ for (var j=1; j < A.n; j++)
+ res[i] *= A.val[r + j];
+ r += A.n;
+ }
+ return res;
+}
+function prod( A , prodalongdimension ) {
+
+ switch ( type( A ) ) {
+ case "vector":
+ if ( arguments.length == 1 || prodalongdimension == 1 )
+ return prodVector(A);
+ else
+ return vectorCopy(A);
+ break;
+ case "spvector":
+ if ( arguments.length == 1 || prodalongdimension == 1 ) {
+ if ( A.val.length < A.length )
+ return 0;
+ else
+ return prodVector(A.val);
+ }
+ else
+ return A.copy();
+ break;
+ case "matrix":
+ if( arguments.length == 1 ) {
+ return prodMatrix( A ) ;
+ }
+ else if ( prodalongdimension == 1 ) {
+ return prodMatrixRows( A );
+ }
+ else if ( prodalongdimension == 2 ) {
+ return prodMatrixCols( A );
+ }
+ else
+ return undefined;
+ break;
+ case "spmatrix":
+ if( arguments.length == 1 ) {
+ if ( A.val.length < A.m * A.n )
+ return 0;
+ else
+ return prodVector( A.val ) ;
+ }
+ else if ( prodalongdimension == 1 ) {
+ return prodspMatrixRows( A );
+ }
+ else if ( prodalongdimension == 2 ) {
+ return prodspMatrixCols( A );
+ }
+ else
+ return undefined;
+ break;
+ default:
+ return A;
+ break;
+ }
+}
+
+function mean( A , sumalongdimension ) {
+
+ switch ( type( A ) ) {
+ case "vector":
+ if ( arguments.length == 1 || sumalongdimension == 1 ) {
+ return sumVector(A) / A.length;
+ }
+ else {
+ return vectorCopy(A);
+ }
+ break;
+ case "spvector":
+ if ( arguments.length == 1 || sumalongdimension == 1 )
+ return sumVector(A.val) / A.length;
+ else
+ return A.copy();
+ break;
+
+ case "matrix":
+ if( arguments.length == 1 ) {
+ return sumMatrix( A ) / ( A.m * A.n);
+ }
+ else if ( sumalongdimension == 1 ) {
+ return mulScalarMatrix( 1/A.m, sumMatrixRows( A ));
+ }
+ else if ( sumalongdimension == 2 ) {
+ return mulScalarVector( 1/A.n, sumMatrixCols( A )) ;
+ }
+ else
+ return undefined;
+ break;
+ case "spmatrix":
+ if( arguments.length == 1 ) {
+ return sumVector( A.val ) / ( A.m * A.n);
+ }
+ else if ( sumalongdimension == 1 ) {
+ return mulScalarMatrix(1/A.m, sumspMatrixRows(A));
+ }
+ else if ( sumalongdimension == 2 ) {
+ return mulScalarVector(1/A.n, sumspMatrixCols(A));
+ }
+ else
+ return undefined;
+ break;
+ default:
+ return A;
+ break;
+ }
+}
+
+function variance(A, alongdimension ) {
+ // variance = sum(A^2)/n - mean(A)^2
+ if ( arguments.length > 1 )
+ var meanA = mean(A, alongdimension);
+ else
+ var meanA = mean(A);
+
+ switch ( type( A ) ) {
+ case "number":
+ return 0;
+ break;
+ case "vector":
+ if ( arguments.length == 1 || alongdimension == 1 ) {
+ var res = ( dot(A,A) / A.length ) - meanA*meanA;
+ return res ;
+ }
+ else {
+ return zeros(A.length);
+ }
+ break;
+ case "spvector":
+ if ( arguments.length == 1 || alongdimension == 1 ) {
+ var res = ( dot(A.val,A.val) / A.length ) - meanA*meanA;
+ return res ;
+ }
+ else
+ return zeros(A.length);
+
+ break;
+
+ case "matrix":
+ case "spmatrix":
+ if( typeof(alongdimension) == "undefined" ) {
+ var res = (sum(entrywisemul(A,A)) / (A.m * A.n ) ) - meanA*meanA;
+ return res;
+ }
+ else if ( alongdimension == 1 ) {
+ // var of columns
+ var res = sub( entrywisediv(sum(entrywisemul(A,A),1) , A.length ) , entrywisemul(meanA,meanA) );
+ return res;
+ }
+ else if ( alongdimension == 2 ) {
+ // sum all columns, result is column vector
+ res = sub( entrywisediv(sum(entrywisemul(A,A),2) , A.n ) , entrywisemul(meanA,meanA) );
+ return res;
+ }
+ else
+ return undefined;
+ break;
+ default:
+ return undefined;
+ }
+}
+
+function std(A, alongdimension) {
+ if ( arguments.length > 1 )
+ return sqrt(variance(A,alongdimension));
+ else
+ return sqrt(variance(A));
+}
+
+/**
+ * Covariance matrix C = X'*X ./ X.m
+ * @param {Matrix|Float64Array|spVector}
+ * @return {Matrix|number}
+ */
+function cov( X ) {
+ switch ( type( X ) ) {
+ case "number":
+ return 0;
+ break;
+ case "vector":
+ var mu = mean(X);
+ return ( dot(X,X) / X.length - mu*mu);
+ break;
+ case "spvector":
+ var mu = mean(X);
+ return ( dot(X.val,X.val) / X.length - mu*mu);
+ break;
+ case "matrix":
+ var mu = mean(X,1).row(0);
+ return divMatrixScalar(xtx( subMatrices(X, outerprod(ones(X.m), mu ) ) ), X.m);
+ break;
+ case "spmatrix":
+ var mu = mean(X,1).row(0);
+ return divMatrixScalar(xtx( subspMatrixMatrix(X, outerprod(ones(X.m), mu ) ) ), X.m);
+ break;
+ default:
+ return undefined;
+ }
+}
+/**
+ * Compute X'*X
+ * @param {Matrix}
+ * @return {Matrix}
+ */
+function xtx( X ) {
+ const N = X.m;
+ const d = X.n;
+
+ var C = new Matrix(d,d);
+ for (var i=0; i < N; i++) {
+ var xi= X.row(i);
+ for(var k = 0; k < d; k++) {
+ var xik = xi[k];
+ for (var j=k; j < d; j++) {
+ C.val[k*d + j] += xik * xi[j];
+ }
+ }
+ }
+ // Symmetric lower triangular part:
+ for(var k = 0; k < d; k++) {
+ var kd = k*d;
+ for (var j=k; j < d; j++)
+ C.val[j*d+k] = C.val[kd+j];
+ }
+ return C;
+}
+
+function norm( A , sumalongdimension ) {
+ // l2-norm (Euclidean norm) of vectors or Frobenius norm of matrix
+ var i;
+ var j;
+ switch ( type( A ) ) {
+ case "number":
+ return Math.abs(A);
+ break;
+ case "vector":
+ if ( arguments.length == 1 || sumalongdimension == 1 ) {
+ return Math.sqrt(dot(A,A));
+ }
+ else
+ return abs(A);
+ break;
+ case "spvector":
+ if ( arguments.length == 1 || sumalongdimension == 1 ) {
+ return Math.sqrt(dot(A.val,A.val));
+ }
+ else
+ return abs(A);
+ break;
+ case "matrix":
+ if( arguments.length == 1 ) {
+ return Math.sqrt(dot(A.val,A.val));
+ }
+ else if ( sumalongdimension == 1 ) {
+ // norm of columns, result is row vector
+ const n = A.n;
+ var res = zeros(1, n);
+ var r = 0;
+ for (i=0; i< A.m; i++) {
+ for(j=0; j 0, consider values < epsilon as 0
+
+ var epsilon = EPS;
+ if ( arguments.length == 3 )
+ epsilon = epsilonarg;
+
+ var i;
+ var j;
+ switch ( type( A ) ) {
+ case "number":
+ return (Math.abs(A) > epsilon);
+ break;
+ case "vector":
+ if ( arguments.length == 1 || sumalongdimension == 1 ) {
+ return norm0Vector(A, epsilon);
+ }
+ else
+ return isGreater(abs(a), epsilon);
+ break;
+ case "spvector":
+ if ( arguments.length == 1 || sumalongdimension == 1 ) {
+ return norm0Vector(A.val, epsilon);
+ }
+ else
+ return isGreater(abs(a), epsilon);
+ break;
+ case "matrix":
+ if( arguments.length == 1 ) {
+ return norm0Vector(A.val, epsilon);
+ }
+ else if ( sumalongdimension == 1 ) {
+ // norm of columns, result is row vector
+ var res = zeros(1, A.n);
+ for (i=0; i< A.m; i++) {
+ for(j = 0; j < A.n; j++)
+ if ( Math.abs(A[i*A.n + j]) > epsilon )
+ res.val[j]++;
+ }
+ return res;
+ }
+ else if ( sumalongdimension == 2 ) {
+ // norm of rows, result is column vector
+ var res = zeros(A.m);
+ for (i=0; i< A.m; i++) {
+ for(j = 0; j < A.n; j++)
+ if ( Math.abs(A[i*A.n + j]) > epsilon )
+ res[i]++;
+ }
+ return res;
+ }
+ else
+ return undefined;
+ break;
+ case "spmatrix":
+ if( arguments.length == 1 ) {
+ return norm0Vector(A.val, epsilon);
+ }
+ else if ( sumalongdimension == 1 ) {
+ // norm of columns, result is row vector
+ var res = zeros(1, A.n);
+ if ( A.rowmajor ) {
+ for ( var k=0; k < A.val.length; k++)
+ if (Math.abs(A.val[k]) > epsilon)
+ res.val[A.cols[k]] ++;
+ }
+ else {
+ for ( var i=0; i epsilon)
+ res[A.rows[k]]++;
+ }
+ return res;
+ }
+ else
+ return undefined;
+ break;
+ default:
+ return undefined;
+ }
+}
+/**
+ * @param {Float64Array}
+ * @param {number}
+ * @return {number}
+ */
+function norm0Vector( x, epsilon ) {
+ const n = x.length;
+ var res = 0;
+ for (var i=0; i < n; i++)
+ if ( Math.abs(x[i]) > epsilon )
+ res++;
+ return res;
+}
+
+///////////////////////////////////////////:
+// Linear systems of equations
+///////////////////////////////////////
+
+function solve( A, b ) {
+ /* Solve the linear system Ax = b */
+
+ var tA = type(A);
+
+ if ( tA == "vector" || tA == "spvector" || (tA == "matrix" && A.m == 1) ) {
+ // One-dimensional least squares problem:
+ var AtA = mul(transpose(A),A);
+ var Atb = mul(transpose(A), b);
+ return Atb / AtA;
+ }
+
+ if ( tA == "spmatrix" ) {
+ /*if ( A.m == A.n )
+ return spsolvecg(A, b); // assume A is positive definite
+ else*/
+ return spcgnr(A, b);
+ }
+
+ if( type(b) == "vector" ) {
+ if ( A.m == A.n )
+ return solveGaussianElimination(A, b) ;
+ else
+ return solveWithQRcolumnpivoting(A, b) ;
+ }
+ else
+ return solveWithQRcolumnpivotingMultipleRHS(A, b) ; // b is a matrix
+}
+/**
+ * Solve the linear system Ax = b given the Cholesky factor L of A
+ * @param {Matrix}
+ * @param {Float64Array}
+ * @return {Float64Array}
+ */
+function cholsolve ( L, b ) {
+ var z = forwardsubstitution(L, b);
+ var x = backsubstitution(transposeMatrix(L), z);
+ return x;
+}
+
+/**
+ * @param {Matrix}
+ * @param {Float64Array}
+ * @return {Float64Array}
+ */
+function solveWithQRfactorization ( A, b ) {
+ const m = A.length;
+ const n = A.n;
+ var QRfact = qr(A);
+ var R = QRfact.R;
+ var beta = QRfact.beta;
+
+ var btmp = vectorCopy(b);
+ var j;
+ var i;
+ var k;
+ var v;
+
+ var smallb;
+
+ for (j=0;jn
+ if ( m > n ) {
+ j = n-1;
+
+ v = get(R, range(j,m), j) ; // get Householder vectors
+ v[0] = 1;
+ // b(j:m) = (I - beta v v^T ) * b(j:m)
+ smallb = get(btmp, range(j,m) );
+ set ( btmp, range(j,m), sub ( smallb , mul( beta[j] * mul( v, smallb) , v ) ) );
+
+ }
+
+ // Solve R x = b with backsubstitution (R is upper triangular, well it is not really here because we use the lower part to store the vectors v):
+ return backsubstitution ( R , get ( btmp, range(n)) );
+
+
+// return backsubstitution ( get ( R, range(n), range(n) ) , rows ( btmp, range(1,n)) );
+// we can spare the get and copy of R : backsubstitution will only use this part anyway
+}
+
+/**
+ * @param {Matrix}
+ * @param {Float64Array}
+ * @return {Float64Array}
+ */
+function backsubstitution ( U, b ) {
+ // backsubstitution to solve a linear system U x = b with upper triangular U
+
+ const n = b.length;
+ var j = n-1;
+ var x = zeros(n);
+
+ if ( ! isZero(U.val[j*n+j]) )
+ x[j] = b[j] / U.val[j*n+j];
+
+ j = n-2;
+ if ( !isZero(U.val[j*n+j]) )
+ x[j] = ( b[j] - U.val[j*n+n-1] * x[n-1] ) / U.val[j*n+j];
+
+ for ( j=n-3; j >= 0 ; j-- ) {
+ if ( ! isZero(U.val[j*n+j]) )
+ x[j] = ( b[j] - dot( U.row(j).subarray(j+1,n) , x.subarray(j+1,n) ) ) / U.val[j*n+j];
+ }
+
+ // solution
+ return x;
+}
+/**
+ * @param {Matrix}
+ * @param {Float64Array}
+ * @return {Float64Array}
+ */
+function forwardsubstitution ( L, b ) {
+ // forward substitution to solve a linear system L x = b with lower triangular L
+
+ const n = b.length;
+ var j;
+ var x = zeros(n);
+
+ if ( !isZero(L.val[0]) )
+ x[0] = b[0] / L.val[0];
+
+ if ( ! isZero(L.val[n+1]) )
+ x[1] = ( b[1] - L.val[n] * x[0] ) / L.val[n+1];
+
+ for ( j=2; j < n ; j++ ) {
+ if ( ! isZero(L.val[j*n+j]) )
+ x[j] = ( b[j] - dot( L.row(j).subarray(0,j) , x.subarray(0,j) ) ) / L.val[j*n+j];
+ }
+
+ // solution
+ return x;
+}
+/**
+ * @param {Matrix}
+ * @param {Float64Array}
+ * @return {Float64Array}
+ */
+function solveWithQRcolumnpivoting ( A, b ) {
+
+ var m;
+ var n;
+ var R;
+ var V;
+ var beta;
+ var r;
+ var piv;
+ if ( type( A ) == "matrix" ) {
+ // Compute the QR factorization
+ m = A.m;
+ n = A.n;
+ var QRfact = qr(A);
+ R = QRfact.R;
+ V = QRfact.V;
+ beta = QRfact.beta;
+ r = QRfact.rank;
+ piv = QRfact.piv;
+ }
+ else {
+ // we get the QR factorization in A
+ R = A.R;
+ r = A.rank;
+ V = A.V;
+ beta = A.beta;
+ piv = A.piv;
+ m = R.m;
+ n = R.n;
+ }
+
+ var btmp = vectorCopy(b);
+ var j;
+ var i;
+ var k;
+
+ var smallb;
+ // b = Q' * b
+ for (j=0;j < r; j++) {
+
+ // b(j:m) = (I - beta v v^T ) * b(j:m)
+ smallb = get(btmp, range(j,m) );
+
+ set ( btmp, range(j,m), sub ( smallb , mul( beta[j] * mul( V[j], smallb) , V[j] ) ) );
+ }
+ // Solve R x = b with backsubstitution
+ var x = zeros(n);
+
+ if ( r > 1 ) {
+ set ( x, range(0,r), backsubstitution ( R , get ( btmp, range(r)) ) );
+ // note: if m < n, backsubstitution only uses n columns of R.
+ }
+ else {
+ x[0] = btmp[0] / R.val[0];
+ }
+
+ // and apply permutations
+ for ( j=r-1; j>=0; j--) {
+ if ( piv[j] != j ) {
+ var tmp = x[j] ;
+ x[j] = x[piv[j]];
+ x[piv[j]] = tmp;
+ }
+ }
+ return x;
+
+}
+/**
+ * @param {Matrix}
+ * @param {Matrix}
+ * @return {Matrix}
+ */
+function solveWithQRcolumnpivotingMultipleRHS ( A, B ) {
+
+ var m;
+ var n;
+ var R;
+ var V;
+ var beta;
+ var r;
+ var piv;
+ if ( type( A ) == "matrix" ) {
+ // Compute the QR factorization
+ m = A.m;
+ n = A.n;
+ var QRfact = qr(A);
+ R = QRfact.R;
+ V = QRfact.V;
+ beta = QRfact.beta;
+ r = QRfact.rank;
+ piv = QRfact.piv;
+ }
+ else {
+ // we get the QR factorization in A
+ R = A.R;
+ r = A.rank;
+ V = A.V;
+ beta = A.beta;
+ piv = A.piv;
+ m = R.m;
+ n = R.n;
+ }
+
+ var btmp = matrixCopy(B);
+ var j;
+ var i;
+ var k;
+
+ var smallb;
+ // B = Q' * B
+ for (j=0;j < r; j++) {
+
+ // b(j:m) = (I - beta v v^T ) * b(j:m)
+ smallb = get(btmp, range(j,m), [] );
+
+ set ( btmp, range(j,m), [], sub ( smallb , mul(mul( beta[j], V[j]), mul( transpose(V[j]), smallb) ) ) );
+ }
+ // Solve R X = B with backsubstitution
+ var X = zeros(n,m);
+
+ if ( r > 1 ) {
+ for ( j=0; j < m; j++)
+ set ( X, range(0,r), j, backsubstitution ( R , get ( btmp, range(r), j) ) );
+ // note: if m < n, backsubstitution only uses n columns of R.
+ }
+ else {
+ set(X, 0, [], entrywisediv(get(btmp, 0, []) , R.val[0]) );
+ }
+
+ // and apply permutations
+ for ( j=r-1; j>=0; j--) {
+ if ( piv[j] != j ) {
+ swaprows(X, j, piv[j]);
+ }
+ }
+ return X;
+
+}
+
+function solveGaussianElimination(Aorig, borig) {
+
+ // Solve square linear system Ax = b with Gaussian elimination
+
+ var i;
+ var j;
+ var k;
+
+ var A = matrixCopy( Aorig ).toArrayOfFloat64Array(); // useful to quickly switch rows
+ var b = vectorCopy( borig );
+
+ const m = Aorig.m;
+ const n = Aorig.n;
+ if ( m != n)
+ return undefined;
+
+ // Set to zero small values... ??
+
+ for (k=0; k < m ; k++) {
+
+ // Find imax = argmax_i=k...m |A_i,k|
+ var imax = k;
+ var Aimaxk = Math.abs(A[imax][k]);
+ for (i=k+1; i Aimaxk ) {
+ imax = i;
+ Aimaxk = Aik;
+ }
+ }
+ if ( isZero( Aimaxk ) ) {
+ console.log("** Warning in solve(A,b), A is square but singular, switching from Gaussian elimination to QR method.");
+ return solveWithQRcolumnpivoting(A,b);
+ }
+
+ if ( imax != k ) {
+ // Permute the rows
+ var a = A[k];
+ A[k] = A[imax];
+ A[imax] = a;
+ var tmpb = b[k];
+ b[k] = b[imax];
+ b[imax] = tmpb;
+ }
+ var Ak = A[k];
+
+ // Normalize row k
+ var Akk = Ak[k];
+ b[k] /= Akk;
+
+ //Ak[k] = 1; // not used afterwards
+ for ( j=k+1; j < n; j++)
+ Ak[j] /= Akk;
+
+ if ( Math.abs(Akk) < 1e-8 ) {
+ console.log("** Warning in solveGaussianElimination: " + Akk + " " + k + ":" + m );
+ }
+
+ // Substract the kth row from others to get 0s in kth column
+ var Aik ;
+ var bk = b[k];
+ for ( i=0; i< m; i++) {
+ if ( i != k ) {
+ var Ai = A[i];
+ Aik = Ai[k];
+ for ( j=k+1; j < n; j++) { // Aij = 0 with j < k and Aik = 0 after this operation but is never used
+ Ai[j] -= Aik * Ak[j];
+ }
+ b[i] -= Aik * bk;
+ }
+ }
+ }
+
+ // Solution:
+ return b;
+}
+
+function inv( M ) {
+ if ( typeof(M) == "number" )
+ return 1/M;
+
+ // inverse matrix with Gaussian elimination
+
+ var i;
+ var j;
+ var k;
+ const m = M.length;
+ const n = M.n;
+ if ( m != n)
+ return "undefined";
+
+ // Make extended linear system:
+ var A = matrixCopy(M) ;
+ var B = eye(n);
+
+ for (k=0; k < m ; k++) {
+ var kn = k*n;
+
+ // Find imax = argmax_i=k...m |A_i,k|
+ var imax = k;
+ var Aimaxk = Math.abs(A.val[imax*n + k]);
+ for (i=k+1; i Aimaxk ) {
+ imax = i;
+ Aimaxk = Math.abs(A.val[i * n + k]);
+ }
+ }
+ if ( Math.abs( Aimaxk ) < 1e-12 ) {
+ return "singular";
+ }
+
+ if ( imax != k ) {
+ // Permute the rows
+ swaprows(A, k, imax);
+ swaprows(B,k, imax);
+ }
+
+ // Normalize row k
+ var Akk = A.val[kn + k];
+ for ( j=0; j < n; j++) {
+ A.val[kn + j] /= Akk;
+ B.val[kn + j] /= Akk;
+ }
+
+ if ( Math.abs(Akk) < 1e-8 )
+ console.log("!! Warning in inv(): " + Akk + " " + k + ":" + m );
+
+ // Substract the kth row from others to get 0s in kth column
+ var Aik ;
+ for ( i=0; i< m; i++) {
+ if ( i != k ) {
+ var ri = i*n;
+ Aik = A.val[ri+k];
+ if ( ! isZero(Aik) ) {
+ for ( j=0; j < n; j++) {
+ A.val[ri + j] -= Aik * A.val[kn+j];
+ B.val[ri + j] -= Aik * B.val[kn+j] ;
+ }
+ }
+ }
+ }
+ }
+
+ // Solution:
+ return B;
+}
+
+function chol( A ) {
+ // Compute the Cholesky factorization A = L L^T with L lower triangular
+ // for a positive definite and symmetric A
+ // returns L or undefined if A is not positive definite
+ const n = A.m;
+ if ( A.n != n) {
+ error("Cannot compute the cholesky factorization: the matrix is not square.");
+ return undefined;
+ }
+ const n2= n*n;
+ const Aval = A.val;
+ var L = new Float64Array(n2);
+
+ var i,j;
+ // first column = A(:,0) / sqrt(L(0,0)
+ var sqrtLjj = Math.sqrt(Aval[0]);
+ for ( i=0; i < n2 ; i+=n) { // i = i*n = ptr to row i
+ L[i] = Aval[i] / sqrtLjj;
+ }
+ // other colums
+ j = 1;
+ var jn = n;
+ while ( j < n && !isNaN(sqrtLjj)) {
+ for ( i = jn; i < n2; i+=n ) { // i = i*n
+ var Lij = Aval[i+j];
+ for ( var k=0; k < j; k++) {
+ Lij -= L[jn + k] * L[i + k];
+ }
+ if (i == jn)
+ sqrtLjj = Math.sqrt(Lij);
+
+ L[i +j] = Lij / sqrtLjj;
+ }
+ j++;
+ jn += n;
+ }
+ if ( isNaN(sqrtLjj) )
+ return undefined; // not positive definite
+ else
+ return new Matrix(n,n,L,true);
+}
+
+function ldlsymmetricpivoting ( Aorig ) {
+ // LDL factorization for symmetric matrices
+ var A = matrixCopy( Aorig );
+ var n = A.length;
+ if ( A.m != n ) {
+ error("Error in ldl(): the matrix is not square.");
+ return undefined;
+ }
+ var k;
+ var piv = zeros(n);
+ var alpha;
+ var v;
+
+ for ( k=0; k < n-1; k++) {
+
+ piv[k] = findmax(get(diag(A ), range(k,n) ));
+ swaprows(A, k, piv[k] );
+ swapcols(A, k, piv[k] );
+ alpha = A.val[k*n + k];
+ v = getCols ( A, [k]).subarray(k+1,n);
+
+ for ( var i=k+1;i < n; i++)
+ A.val[i*n + k] /= alpha;
+
+ set( A, range(k+1,n),range(k+1,n), sub (get(A,range(k+1,n), range(k+1,n)), outerprod(v,v, 1/alpha)));
+
+ }
+
+ // Make it lower triangular
+ for (var j=0; j < n-1; j++) {
+ for (var k=j+1; k < n ; k++)
+ A.val[j*n + k] = 0;
+ }
+ return {L: A, piv: piv};
+}
+/**
+ * @param {Float64Array}
+ * @return {{v: Float64Array, beta: number}}
+ */
+function house ( x ) {
+ // Compute Houselholder vector v such that
+ // P = (I - beta v v') is orthogonal and Px = ||x|| e_1
+
+ const n = x.length;
+ var i;
+ var mu;
+ var beta;
+ var v = zeros(n);
+ var v0;
+ var sigma ;
+
+ var x0 = x[0];
+ var xx = dot(x,x);
+
+ // sigma = x(2:n)^T x(2:n)
+ sigma = xx -x0*x0;
+
+ if ( isZero( sigma ) ) {
+ // x(2:n) is zero => v=[1,0...0], beta = 0
+ beta = 0;
+ v[0] = 1;
+ }
+ else {
+ mu = Math.sqrt(xx); // norm(x) ; //Math.sqrt( x0*x0 + sigma );
+ if ( x0 < EPS ) {
+ v0 = x0 - mu;
+ }
+ else {
+ v0 = -sigma / (x0 + mu);
+ }
+
+ beta = 2 * v0 * v0 / (sigma + v0 * v0 );
+
+ // v = [v0,x(2:n)] / v0
+ v[0] = 1;
+ for ( i=1; i< n; i++)
+ v[i] = x[i] / v0;
+ }
+
+ return { "v" : v , "beta" : beta};
+}
+/**
+ * @param {Matrix}
+ * @return {{Q: (Matrix|undefined), R: Matrix, beta: Float64Array}
+ */
+function qroriginal( A, compute_Q ) {
+ // QR factorization based on Householder reflections WITHOUT column pivoting
+ // A with m rows and n cols; m >= n
+
+ // test with A = [[12,-51,4],[6,167,-68],[-4,24,-41]]
+ // then R = [ [14 -21 -14 ], [ -3, 175, -70], [2, -0.75, 35]]
+
+ var m = A.length;
+ var n = A.n;
+ if ( n > m)
+ return "QR factorization unavailable for n > m.";
+
+ var i;
+ var j;
+ var k;
+ var householder;
+ var R = matrixCopy(A);
+ var beta = zeros(n);
+ var outer;
+ var smallR;
+ var Q;
+ var V = new Array(); // store householder vectors
+
+
+ for ( j=0; j < n - 1 ; j++) {
+ householder = house( get( R, range(j,m), j) );
+ // R(j:m,j:n) = ( I - beta v v' ) * R(j:m,j:n) = R - (beta v) (v'R)
+ smallR = get(R, range(j,m), range(j,n) );
+ set ( R, range(j,m), range(j,n) , subMatrices ( smallR , outerprodVectors( householder.v, mulMatrixVector( transposeMatrix(smallR), householder.v) , householder.beta ) ) ) ;
+
+ V[j] = householder.v;
+ beta[j] = householder.beta;
+
+ }
+ // Last iteration only if m > n: if m=n, (I - beta v v' ) = 1 => R(n,n) is unchanged
+ if ( m > n ) {
+ j = n-1;
+ smallR = get( R, range(j,m), j)
+ householder = house( smallR );
+ // R(j:m,n) = ( I - beta v v' ) * R(j:m, n) = R(j:m,n) - (beta v) (v'R(j:m,n) ) = Rn - ( beta *(v' * Rn) )* v
+ set ( R, range(j,m), n-1 , subVectors ( smallR , mulScalarVector( dot( householder.v, smallR ) * householder.beta, householder.v ) ) ) ;
+
+ V[j] = vectorCopy(householder.v);
+ beta[j] = householder.beta;
+
+ }
+
+ if ( compute_Q ) {
+ var r;
+ if ( typeof( compute_Q ) == "number") {
+ // compute only first r columns of Q
+ r = compute_Q;
+ Q = eye(m,r);
+ }
+ else {
+ Q = eye(m);
+ r = m;
+ }
+ var smallQ;
+ var nmax = n-1;
+ if ( m<=n)
+ nmax = n-2;
+ if ( nmax >= r )
+ nmax = r-1;
+
+ for ( j=nmax; j >=0; j--) {
+ smallQ = get(Q, range(j,m), range(j,r) );
+
+ if ( r > 1 ) {
+ if ( j == r-1)
+ set ( Q, range(j,m), [j] , subVectors ( smallQ , mulScalarVector( dot( smallQ, V[j]) * beta[j], V[j] ) ) );
+ else
+ set ( Q, range(j,m), range(j,r), sub ( smallQ , outerprod( V[j], mul( transpose( smallQ), V[j]), beta[j] ) ) );
+ }
+ else
+ Q = subVectors ( smallQ , mulScalarVector( dot( smallQ, V[j]) * beta[j], V[j] ) );
+ }
+ }
+
+ return {"Q" : Q, "R" : R, "beta" : beta };
+}
+
+/**
+ * @param {Matrix}
+ * @return {{Q: (Matrix|undefined), R: Matrix, V: Array, beta: Float64Array, piv: Float64Array, rank: number}
+ */
+function qr( A, compute_Q ) {
+ // QR factorization with column pivoting AP = QR based on Householder reflections
+ // A with m rows and n cols; m >= n (well, it also works with m < n)
+ // piv = vector of permutations : P = P_rank with P_j = identity with swaprows ( j, piv(j) )
+
+ // Implemented with R transposed for faster computations on rows instead of columns
+
+ /* TEST
+ A = [[12,-51,4],[6,167,-68],[-4,24,-41]]
+ QR = qr(A)
+ QR.R
+
+
+ */
+ const m = A.m;
+ const n = A.n;
+
+ /*
+ if ( n > m)
+ return "QR factorization unavailable for n > m.";
+ */
+
+ var i;
+ var j;
+
+ var householder;
+ var R = transpose(A);// transposed for faster implementation
+ var Q;
+
+ var V = new Array(); // store householder vectors in this list (not a matrix)
+ var beta = zeros(n);
+ var piv = zeros(n);
+
+ var smallR;
+
+ var r = -1; // rank estimate -1
+
+ var normA = norm(A);
+ var normR22 = normA;
+ var Rij;
+
+ const TOL = 1e-5;
+ var TOLnormR22square = TOL * normA;
+ TOLnormR22square *= TOLnormR22square;
+
+ var tau = 0;
+ var k = 0;
+ var c = zeros (n);
+ for ( j=0; j < n ; j++) {
+ var Rj = R.val.subarray(j*R.n,j*R.n + R.n);
+ c[j] = dot(Rj,Rj);
+ if ( c[j] > tau ) {
+ tau = c[j];
+ k = j;
+ }
+ }
+
+ var updateR = function (r, v, beta) {
+ // set ( R, range(r,n), range(r,m) , subMatrices ( smallR , outerprodVectors( mulMatrixVector( smallR, householder.v), householder.v, householder.beta ) ) ) ;
+ // most of the time is spent here...
+ var i,j,l;
+ var m_r = m-r;
+ for ( i=r; i < n; i++) {
+ var smallRiv = 0;
+ var Ri = i*m + r; // = i * R.n + r
+ var Rval = R.val.subarray(Ri,Ri+m_r);
+ for ( l = 0 ; l < m_r ; l ++)
+ smallRiv += Rval[l] * v[l]; //smallRiv += R.val[Ri + l] * v[l];
+ smallRiv *= beta ;
+ for ( j=0; j < m_r ; j ++) {
+ Rval[j] -= smallRiv * v[j]; // R.val[Ri + j] -= smallRiv * v[j];
+ }
+ }
+ };
+
+ // Update c
+ var updateC = function(r) {
+ var j;
+ for (j=r+1; j < n; j++) {
+ var Rjr = R.val[j*m + r];
+ c[j] -= Rjr * Rjr;
+ }
+
+ // tau, k = max ( c[r+1 : n] )
+ k=r+1;
+ tau = c[r+1];
+ for ( j=r+2; j tau ) {
+ tau = c[j];
+ k = j;
+ }
+ }
+ };
+
+ // Compute norm of residuals
+ var computeNormR22 = function(r) {
+ //normR22 = norm(get ( R, range(r+1,n), range(r+1,m), ) );
+ var normR22 = 0;
+ var i = r+1;
+ var ri = i*m;
+ var j;
+ while ( i < n && normR22 <= TOLnormR22square ) {
+ for ( j=r+1; j < m; j++) {
+ var Rij = R.val[ri + j];
+ normR22 += Rij*Rij;
+ }
+ i++;
+ ri += m;
+ }
+ return normR22;
+ }
+
+
+ while ( tau > EPS && r < n-1 && normR22 > TOLnormR22square ) {
+
+ r++;
+
+ piv[r] = k;
+ swaprows ( R, r, k);
+ c[k] = c[r];
+ c[r] = tau;
+
+ if ( r < m-1) {
+ householder = house( R.val.subarray(r*R.n + r,r*R.n + m) ); // house only reads vec so subarray is ok
+ }
+ else {
+ householder.v = [1];
+ householder.beta = 0;
+ //smallR = R[m-1][m-1];
+ }
+
+ if (r < n-1) {
+ // smallR is a matrix
+ updateR(r, householder.v, householder.beta);
+ }
+ else {
+ // smallR is a row vector (or a number if m=n):
+ if ( r < m-1) {
+ updateR(r, householder.v, householder.beta);
+ /*
+ var r_to_m = range(r,m);
+ smallR = get(R, r, r_to_m);
+ set ( R, r , r_to_m, sub ( smallR , transpose(mul( householder.beta * mul( smallR, householder.v) ,householder.v ) )) ) ;*/
+ }
+ else {
+ //var smallRnumber = R.val[(m-1)*R.n + m-1]; // beta is zero, so no update
+ //set ( R, r , r, sub ( smallRnumber , transpose(mul( householder.beta * mul( smallRnumber, householder.v) ,householder.v ) )) ) ;
+ }
+ }
+
+ // Store householder vectors and beta
+ V[r] = vectorCopy( householder.v );
+ beta[r] = householder.beta;
+
+ if ( r r+1)
+ nmax = r-1;
+ for ( j=nmax; j >=0; j--) {
+ if ( j == m-1 ) {
+ Q.val[j*m+j] -= beta[j] * V[j][0] * V[j][0] * Q.val[j*m+j];
+ }
+ else {
+ var j_to_m = range(j,m);
+ smallQ = get(Q, j_to_m, j_to_m );// matrix
+ set ( Q, j_to_m, j_to_m, subMatrices ( smallQ , outerprodVectors( V[j], mulMatrixVector( transposeMatrix(smallQ), V[j]), beta[j] ) ) );
+ }
+ }
+ }
+
+ return {"Q" : Q, "R" : transpose(R), "V": V, "beta" : beta, "piv" : piv, "rank" : r+1 };
+}
+
+function qrRnotTransposed( A, compute_Q ) {
+ // QR factorization with column pivoting AP = QR based on Householder reflections
+ // A with m rows and n cols; m >= n (well, it also works with m < n)
+ // piv = vector of permutations : P = P_rank with P_j = identity with swaprows ( j, piv(j) )
+
+ // original implementation working on columns
+
+ /* TEST
+ A = [[12,-51,4],[6,167,-68],[-4,24,-41]]
+ QR = qr(A)
+ QR.R
+
+
+ */
+ var m = A.m;
+ var n = A.n;
+
+ /*
+ if ( n > m)
+ return "QR factorization unavailable for n > m.";
+ */
+
+ var i;
+ var j;
+
+ var householder;
+ var R = matrixCopy(A);
+ var Q;
+
+ var V = new Array(); // store householder vectors in this list (not a matrix)
+ var beta = zeros(n);
+ var piv = zeros(n);
+
+ var smallR;
+
+ var r = -1; // rank estimate -1
+
+ var normA = norm(A);
+ var normR22 = normA;
+
+ var TOL = 1e-6;
+
+ var tau = 0;
+ var k = 0;
+ var c = zeros (n);
+ for ( j=0; j < n ; j++) {
+ var Aj = getCols ( A, [j]);
+ c[j] = dot(Aj, Aj);
+ if ( c[j] > tau ) {
+ tau = c[j];
+ k = j;
+ }
+ }
+
+ while ( tau > EPS && r < n-1 && normR22 > TOL * normA ) {
+
+ r++;
+
+ piv[r] = k;
+ swapcols ( R, r, k);
+ c[k] = c[r];
+ c[r] = tau;
+
+ if ( r < m-1) {
+ householder = house( get( R, range(r,m), r) );
+ smallR = get(R, range(r,m), range(r,n) );
+ }
+ else {
+ householder.v = [1];
+ householder.beta = 0;
+ smallR = R[m-1][m-1];
+ }
+
+ if (r < n-1) {
+ // smallR is a matrix
+ set ( R, range(r,m), range(r,n) , subMatrices ( smallR , outerprodVectors( householder.v, mulMatrixVector( transposeMatrix(smallR), householder.v) , householder.beta ) ) ) ;
+ }
+ else {
+ // smallR is a vector (or a number if m=n):
+ set ( R, range(r,m), r , sub ( smallR , mul( householder.beta * mul( smallR, householder.v) ,householder.v ) ) ) ;
+ }
+
+ // Store householder vectors and beta
+ if ( m > r+1 )
+ V[r] = vectorCopy( householder.v );
+ beta[r] = householder.beta;
+
+ if ( r tau ) {
+ tau = c[j];
+ k = j;
+ }
+ }
+
+ // stopping criterion for rank estimation
+ if ( r < m-1 ) {
+ //normR22 = norm(get ( R, range(r+1,m),range(r+1,n) ) );
+ normR22 = 0;
+ for ( i=r+1; i < m; i++) {
+ for ( j=r+1; j < n; j++) {
+ Rij = R[i][j];
+ normR22 += Rij*Rij;
+ }
+ }
+ normR22 = Math.sqrt(normR22);
+ }
+ else
+ normR22 = 0;
+ }
+ }
+
+ if ( compute_Q ) {
+ Q = eye(m);
+ var smallQ;
+ var nmax = r;
+ if ( m>r+1)
+ nmax = r-1;
+ for ( j=nmax; j >=0; j--) {
+ if ( j == m-1 ) {
+ Q.val[j*m+j] -= beta[j] * V[j][0] * V[j][0] * Q.val[j*m+j];
+ }
+ else {
+ smallQ = get(Q, range(j,m), range(j,m) );
+ set ( Q, range(j,m), range(j,m) , subMatrices ( smallQ , outerprodVectors( V[j], mulMatrixVector( transposeMatrix(smallQ), V[j]), beta[j] ) ) );
+ }
+ }
+
+ }
+
+ return {"Q" : Q, "R" : R, "V": V, "beta" : beta, "piv" : piv, "rank" : r+1 };
+}
+
+/** Conjugate gradient method for solving the symmetyric positive definite system Ax = b
+ * @param{{Matrix|spMatrix}}
+ * @param{Float64Array}
+ * @return{Float64Array}
+ */
+function solvecg ( A, b) {
+ if( A.type == "spmatrix" )
+ return spsolvecg(A,b);
+ else
+ return solvecgdense(A,b);
+}
+
+/** Conjugate gradient method for solving the symmetyric positive definite system Ax = b
+ * @param{Matrix}
+ * @param{Float64Array}
+ * @return{Float64Array}
+ */
+function solvecgdense ( A, b) {
+/*
+TEST
+A = randn(2000,1000)
+x = randn(1000)
+b = A*x + 0.01*randn(2000)
+tic()
+xx = solve(A,b)
+t1 = toc()
+ee = norm(A*xx - b)
+tic()
+xh=solvecg(A'*A, A'*b)
+t2 = toc()
+e = norm(A*xh - b)
+*/
+
+ const n = A.n;
+ const m = A.m;
+
+ var x = randn(n); //vectorCopy(x0);
+ var r = subVectors(b, mulMatrixVector(A, x));
+ var rhoc = dot(r,r);
+ const TOL = 1e-8;
+ var delta2 = TOL * norm(b);
+ delta2 *= delta2;
+
+ // first iteration:
+ var p = vectorCopy(r);
+ var w = mulMatrixVector(A,p);
+ var mu = rhoc / dot(p, w);
+ saxpy( mu, p, x);
+ saxpy( -mu, w, r);
+ var rho_ = rhoc;
+ rhoc = dot(r,r);
+
+ var k = 1;
+
+ var updateP = function (tau, r) {
+ for ( var i=0; i < m; i++)
+ p[i] = r[i] + tau * p[i];
+ }
+
+ while ( rhoc > delta2 && k < n ) {
+ updateP(rhoc/rho_, r);
+ w = mulMatrixVector(A,p);
+ mu = rhoc / dot(p, w);
+ saxpy( mu, p, x);
+ saxpy( -mu, w, r);
+ rho_ = rhoc;
+ rhoc = dot(r,r);
+ k++;
+ }
+ return x;
+}
+/** Conjugate gradient normal equation residual method for solving the rectangular system Ax = b
+ * @param{{Matrix|spMatrix}}
+ * @param{Float64Array}
+ * @return{Float64Array}
+ */
+function cgnr ( A, b) {
+ if( A.type == "spmatrix" )
+ return spcgnr(A,b);
+ else
+ return cgnrdense(A,b);
+}
+/** Conjugate gradient normal equation residual method for solving the rectangular system Ax = b
+ * @param{Matrix}
+ * @param{Float64Array}
+ * @return{Float64Array}
+ */
+function cgnrdense ( A, b) {
+/*
+TEST
+A = randn(2000,1000)
+x = randn(1000)
+b = A*x + 0.01*randn(2000)
+tic()
+xx = solve(A,b)
+t1 = toc()
+ee = norm(A*xx - b)
+tic()
+xh=cgnr(A, b)
+t2 = toc()
+e = norm(A*xh - b)
+*/
+
+ const n = A.n;
+ const m = A.m;
+
+ var x = randn(n); // vectorCopy(x0);
+ var At = transposeMatrix(A);
+ var r = subVectors(b, mulMatrixVector(A, x));
+ const TOL = 1e-8;
+ var delta2 = TOL * norm(b);
+ delta2 *= delta2;
+
+ // first iteration:
+ var z = mulMatrixVector(At, r);
+ var rhoc = dot(z,z);
+ var p = vectorCopy(z);
+ var w = mulMatrixVector(A,p);
+ var mu = rhoc / dot(w, w);
+ saxpy( mu, p, x);
+ saxpy( -mu, w, r);
+ z = mulMatrixVector(At, r);
+ var rho_ = rhoc;
+ rhoc = dot(z,z);
+
+ var k = 1;
+
+ var updateP = function (tau, z) {
+ for ( var i=0; i < m; i++)
+ p[i] = z[i] + tau * p[i];
+ }
+
+ while ( rhoc > delta2 && k < n ) {
+ updateP(rhoc/rho_, z);
+ w = mulMatrixVector(A,p);
+ mu = rhoc / dot(w, w);
+ saxpy( mu, p, x);
+ saxpy( -mu, w, r);
+ z = mulMatrixVector(At, r);
+ rho_ = rhoc;
+ rhoc = dot(z,z);
+ k++;
+ }
+ return x;
+}
+
+/** Lanczos algorithm
+ * @param{Matrix}
+ */
+function lanczos ( A, q1 ) {
+
+ const maxIters = 300;
+ const TOL = EPS * norm(A);
+ const n = A.n;
+ var i;
+ var k = 0;
+ var w = vectorCopy(q1);
+ var v = mulMatrixVector(A, w);
+ var alpha = dot(w,v);
+ saxpy(-alpha, w, v);
+ beta = norm(b);
+
+ while ( beta > TOL && k < maxIters ) {
+
+ for ( i=0; i < n; i++) {
+ var t = w[i];
+ w[i] = v[i] / beta;
+ v[i] = -beta / t;
+ }
+
+ var Aw = mulMatrixVector(A,w);
+
+ for ( i=0; i < n; i++)
+ v[i] += Aw[i];
+
+ alpha = dot(w,v);
+ saxpy(-alpha,w,v);
+ beta = norm(v);
+ k++;
+ }
+}
+
+/**
+ * @param{Matrix}
+ * @param{boolean}
+ * @return{Matrix}
+ */
+function tridiagonalize( A, returnQ ) {
+ // A : a square and symmetric matrix
+ // T = Q A Q' , where T is tridiagonal and Q = (H1 ... Hn-2)' is the product of Householder transformations.
+ // if returnQ, then T overwrites A
+ var k;
+ const n = A.length;
+ var T;
+ var Q;
+ var Pk;
+ if ( returnQ ) {
+ T = A;
+ Q = eye(n);
+ var beta = [];
+ var V = [];
+ }
+ else
+ T = matrixCopy(A);
+ var p;
+ var w;
+ var vwT;
+ var normTkp1k;
+ var householder;
+
+ for (k=0; k < n-2; k++) {
+ Tkp1k = get ( T, range(k+1, n), k);
+ Tkp1kp1 = get ( T, range(k+1,n), range(k+1, n));
+
+ householder = house ( Tkp1k );
+ p = mulScalarVector( householder.beta , mulMatrixVector( Tkp1kp1, householder.v ) );
+ w = subVectors ( p, mulScalarVector( 0.5*householder.beta * dot(p, householder.v ), householder.v) );
+
+ /*
+ T[k+1][k] = norm ( Tkp1k );
+ T[k][k+1] = T[k+1][k];
+ */
+ // make T really tridiagonal: the above does not modify the other entries to set them to 0
+ normTkp1k = zeros(n-k-1);
+ normTkp1k[0] = norm ( Tkp1k );
+ set ( T, k, range(k+1,n ), normTkp1k );
+ set ( T, range(k+1,n), k, normTkp1k);
+
+ vwT = outerprodVectors(householder.v,w);
+ set ( T, range(k+1,n), range(k+1, n), subMatrices( subMatrices ( Tkp1kp1, vwT) , transpose(vwT)) );
+
+ if ( returnQ ) {
+ V[k] = householder.v;
+ beta[k] = householder.beta;
+ }
+ }
+ if ( returnQ ) {
+ var updateQ = function(j, v, b) {
+ // Q = Q - b* v (Q'v)'
+ //smallQ = get(Q, range(j,n), range(j,n) );// matrix
+ //set ( Q, range(j,n), range(j,n) , subMatrices ( smallQ , outerprodVectors( V[k], mulMatrixVector( transposeMatrix(smallQ), V[k]), beta[k] ) ) );
+ var i,k;
+ var Qtv = zeros(n-j);
+ var n_j = n-j;
+ for ( i=0; i=0; k--) {
+ updateQ(k+1,V[k], beta[k]);
+ }
+ return Q;
+ }
+ else
+ return T;
+}
+function givens(a,b,Gi,Gk,n) {
+ // compute a Givens rotation:
+ var c;
+ var s;
+ var tau;
+ var G;
+
+ // Compute c and s
+ if ( b == 0) {
+ c = 1;
+ s = 0;
+ }
+ else {
+ if ( Math.abs(b) > Math.abs(a) ) {
+ tau = -a / b;
+ s = 1 / Math.sqrt(1+tau*tau);
+ c = s*tau;
+ }
+ else {
+ tau = -b / a;
+ c = 1 / Math.sqrt(1+tau*tau);
+ s = c * tau;
+ }
+ }
+
+ if ( arguments.length == 5 ) {
+ // Build Givens matrix G from c and s:
+ G = eye(n) ;
+ G.val[Gi*n+Gi] = c;
+ G.val[Gi*n+Gk] = s;
+ G.val[Gk*n+Gi] = -s;
+ G.val[Gk*n+Gk] = c;
+ return G;
+ }
+ else {
+ return [c,s];
+ }
+
+}
+/**
+ * @param {number}
+ * @param {number}
+ * @param {number}
+ * @param {number}
+ * @param {Matrix}
+ */
+function premulGivens ( c, s, i, k, A) {
+ // apply a Givens rotation to A : A([i,k],:) = G' * A([i,k],:)
+ // with G = givens (a,b,i,k) and [c,s]=givens(a,b)
+ // NOTE: this modifies A
+
+ const n = A.n;
+ var j;
+ const ri = i*n;
+ const rk = k*n;
+ var t1;
+ var t2;
+ for ( j=0; j < n; j++) {
+ t1 = A.val[ri + j];
+ t2 = A.val[rk + j];
+ A.val[ri + j] = c * t1 - s * t2;
+ A.val[rk + j] = s * t1 + c * t2;
+ }
+}
+/**
+ * @param {number}
+ * @param {number}
+ * @param {number}
+ * @param {number}
+ * @param {Matrix}
+ */
+function postmulGivens ( c, s, i, k, A) {
+ // apply a Givens rotation to A : A(:, [i,k]) = A(:, [i,k]) * G
+ // with G = givens (a,b,i,k) and [c,s]=givens(a,b)
+ // NOTE: this modifies A
+
+ const m = A.length;
+ var j;
+ var t1;
+ var t2;
+ var rj = 0;
+ for ( j=0; j < m; j++) {
+ t1 = A.val[rj + i];
+ t2 = A.val[rj + k];
+ A.val[rj + i] = c * t1 - s * t2;
+ A.val[rj + k] = s * t1 + c * t2;
+ rj += A.n;
+ }
+}
+
+function implicitSymQRWilkinsonShift( T , computeZ) {
+ // compute T = Z' T Z
+ // if computeZ: return {T,cs} such that T = Z' T Z with Z = G1.G2...
+ // and givens matrices Gk of parameters cs[k]
+
+ const n = T.length;
+ const rn2 = n*(n-2);
+ const rn1 = n*(n-1);
+
+ const d = ( T.val[rn2 + n-2] - T.val[rn1 + n-1] ) / 2;
+ const t2 = T.val[rn1 + n-2] * T.val[rn1 + n-2] ;
+ const mu = T.val[rn1 + n-1] - t2 / ( d + Math.sign(d) * Math.sqrt( d*d + t2) );
+ var x = T.val[0] - mu; // T[0][0]
+ var z = T.val[n]; // T[1][0]
+ var cs;
+ if ( computeZ)
+ var csArray = new Array(n-1);
+ //var Z = eye(n);
+
+ var k;
+ for ( k = 0; k < n-1; k++) {
+ /*
+ G = givens(x,z, k, k+1, n);
+ T = mul(transpose(G), mul(T, G) ); // can do this much faster
+ if ( computeZ ) {
+ Z = mul(Z, G );
+ }
+ */
+ cs = givens(x,z);
+ postmulGivens(cs[0], cs[1], k, k+1, T);
+ premulGivens(cs[0], cs[1], k, k+1, T);
+ if( computeZ )
+ csArray[k] = [cs[0], cs[1]];
+ //postmulGivens(cs[0], cs[1], k, k+1, Z);
+
+ if ( k < n-2 ) {
+ var r = n*(k+1) + k;
+ x = T.val[r];
+ z = T.val[r + n]; // [k+2][k];
+ }
+ }
+ if ( computeZ) {
+ return {"T": T, "cs": csArray} ;
+// return {"T": T, "Z": Z} ;
+ }
+ else
+ return T;
+}
+
+function eig( A , computeEigenvectors ) {
+ // Eigendecomposition of a symmetric matrix A (QR algorithm)
+
+ var Q;
+ var D;
+ if ( computeEigenvectors ) {
+ D = matrixCopy(A);
+ Q = tridiagonalize( D, true );
+ }
+ else {
+ D = tridiagonalize( A );
+ }
+
+ var q;
+ var p;
+ const n = A.length;
+ var i;
+
+ const TOL = 1e-12; //10 * EPS;
+
+ do {
+ for ( i=0; i= n-1 )
+ q = n;
+ }
+
+ // find smallest p such that D[p:q][p:q] is unreduced ( without zeros on subdiagonal?)
+ p = -1;
+ var zerosOnSubdiagonal ;
+ do {
+ p++;
+ zerosOnSubdiagonal = false;
+ k=p;
+ while (k z0 )
+ z0 = zi;
+ }
+
+ /*
+ // polynomial evaluation and counting sign changes (original method)
+ var polya = function (x,a,b,n) {
+ var pr_2 = 1;
+ var pr_1 = a[0] - x;
+ var pr;
+ var signchanges = 0;
+ if ( pr_1 < EPS )
+ signchanges = 1;
+
+ var r;
+ for ( r = 1; r < n ; r++) {
+ pr = (a[r] - x) * pr_1 - b[r-1] * b[r-1] * pr_2;
+
+ if ( Math.abs(pr) < EPS || (pr > 0 && pr_1 < 0 ) || (pr < 0) && (pr_1 > 0) )
+ signchanges ++;
+
+ pr_2 = pr_1;
+ pr_1 = pr;
+ }
+ return signchanges;
+ };
+ */
+
+ // ratio of polynomials evaluation and counting sign changes
+ // (modification discussed in Barth et al., 1967 for better stability due to pr ~ 0 in the above)
+ var polyq = function (x,a,b,n) {
+ var qi_1 = a[0] - x;
+ var qi;
+ var signchanges = 0;
+ if ( qi_1 < EPS )
+ signchanges = 1;
+
+ var i;
+ for ( i = 1; i < n ; i++) {
+ qi = (a[i] - x) - b[i-1] * b[i-1] / qi_1;
+
+ if ( qi < EPS )
+ signchanges ++;
+
+ if ( Math.abs(qi) < EPS )
+ qi_1 = EPS;
+ else
+ qi_1 = qi;
+ }
+ return signchanges;
+ };
+
+
+ // Start bisection
+ const TOL = 1e-10;
+ var lambda = zeros(K);
+ var xu = entrywisemul(z0,ones(K)); // upper bounds on lambdas
+ y = y0;
+ var n_lowerthan_x;// nb of eigenvalues lower than x
+ for ( var k = 1; k <= K ; k++ ) {
+ // k is the number of desired eigenvalues in this sweep
+
+ z = xu[k-1];
+ //y=y; from previous sweep
+
+ // find the (n-k+1)th eigenvalue
+ while ( Math.abs(z - y) > TOL*(Math.abs(y) + Math.abs(z)) ) {
+ x = (y+z)/2;
+ n_lowerthan_x = polyq(x,a,b,n);
+
+ if(n_lowerthan_x >= k )
+ z = x; // enough eigenvalues below x, decrease upper bound to x
+ else
+ y = x; // not enough ev below x, increase lower bound to x
+
+ // update boudns on other lambdas
+ for ( var j=k+1; j <= K; j++)
+ if ( n_lowerthan_x >= j )
+ xu[j-1] = x;
+
+ }
+ lambda[k-1] = (y+z)/2;
+ }
+ //return lambda;
+
+ // Compute eigenvectors: XXX can be faster by using inverse iteration on the tridiagonal matrix
+ // with faster system solving
+
+ var u = eigenvector( A, lambda[0] );
+ var U = mat([u],false);
+
+ for ( k = 1; k < K; k++) {
+ // deal with too close eigenvalues
+ var perturbtol = 10 * Math.max(EPS, Math.abs(EPS * lambda[k-1]));
+ if ( lambda[k] < lambda[k-1] + perturbtol )
+ lambda[k] = lambda[k-1] + perturbtol;
+
+ u = eigenvector( A, lambda[k] );
+ U = mat([U, u], false );
+ U = qroriginal( U, U.n ).Q; // orthogonalize
+ }
+
+
+ return {U: U, V: lambda};
+}
+
+
+function bidiagonalize( A, computeU, thinU , computeV ) {
+ // B = U' A V , where B is upper bidiagonal
+
+ var j;
+ const m = A.length;
+ const n = A.n;
+ var B;
+ B = matrixCopy( A );
+
+ var householder;
+
+ if ( computeU ) {
+ if ( thinU ) {
+ var U = eye(m,n);
+ var nU = n;
+ }
+ else {
+ var U = eye(m);
+ var nU = m;
+ }
+ }
+ if ( computeV ) {
+ var V = eye(n);
+ }
+
+
+ var updateB1 = function (j, v, beta) {
+ // B = B - (beta v) ( v'* B) = B-outer(beta v, B'*v)
+ //Bjmjn = get ( B, range(j,m), range(j, n));
+ //set ( B, range(j,m), range(j,n), sub ( Bjmjn , outerprod ( householder.v, mul(transpose(Bjmjn), householder.v), householder.beta) ) );
+
+ var i,k;
+ var Btv = zeros(n-j);
+ var n_j = n-j;
+ var m_j = m-j;
+ for ( i=0; i=0; j--) {
+ if (j=0; j--) {
+ updateU(j,hv[j], hb[j]);
+ }
+ }
+
+ if ( computeU && computeV ) {
+ return { "U" : U, "V": V, "B": B};
+ }
+ else if (computeV )
+ return { "V": V, "B": B};
+ else if (computeU)
+ return { "U" : U, "B": B};
+ else
+ return B;
+}
+
+
+function GolubKahanSVDstep ( B, i, j, m, n, computeUV ) {
+ // Apply GolubKahanSVDstep to B(i:i+m, j:j+n)
+ // Note: working on Utrans
+ if (type ( B ) != "matrix" )
+ return B;
+
+ if ( n < 2 )
+ return B;
+
+ const rn2 = (i+n-2)*B.n + j;
+ const dm = B.val[rn2 + n-2];
+ const fm = B.val[rn2 + n-1];
+ var fm_1 ;
+ if ( n>2)
+ fm_1 = B.val[(i+n-3)*B.n + j + n-2];
+ else
+ fm_1 = 0;
+
+ const dn = B.val[(i+n-1)*B.n + j + n-1];
+
+ const d = ( dm*dm + fm_1*fm_1 - dn*dn - fm*fm ) / 2;
+ const t2 = dm*fm * dm*fm;
+ const mu = dn*dn+fm*fm - t2 / ( d + Math.sign(d) * Math.sqrt( d*d + t2) );
+
+ var k;
+
+ //var B0 = getCols ( B, [0]);
+ //var B1 = getCols ( B, [1]) ;
+ //var y = mul( B0, B0 ) - mu;
+ //var z = mul( B0, B1 );
+ var y = - mu;
+ var z = 0.0;
+ var r0 = i*B.n + j;
+ for ( k = 0; k< n; k++) {
+ y += B.val[r0] * B.val[r0];
+ z += B.val[r0] * B.val[r0+1];
+ r0 += B.n;
+ }
+
+
+ var G;
+ var cs;
+
+ var postmulgivens = function ( c, s, k1, k2) {
+ // apply a Givens rotation to a subset of rows of B : B(i:i+m, [k1,k2]) = B(i:i+m, [k1,k2]) * G
+ var jj;
+ var t1;
+ var t2;
+ var rj = i*B.n + j;
+ for ( jj=0; jj < m; jj++) {
+ t1 = B.val[rj + k1];
+ t2 = B.val[rj + k2];
+ B.val[rj + k1] = c * t1 - s * t2;
+ B.val[rj + k2] = s * t1 + c * t2;
+ rj += B.n;
+ }
+ }
+ var premulgivens = function ( c, s, k1, k2) {
+ // apply a Givens rotation to a subset of cols of B : B([k1,k2],j:j+n) = G' * B([k1,k2],j:j+n)
+ var jj;
+ const ri = (i+k1)*B.n + j;
+ const rk = (i+k2)*B.n + j;
+ var t1;
+ var t2;
+ for ( jj=0; jj < n; jj++) {
+ t1 = B.val[ri + jj];
+ t2 = B.val[rk + jj];
+ B.val[ri + jj] = c * t1 - s * t2;
+ B.val[rk + jj] = s * t1 + c * t2;
+ }
+ }
+
+ if ( computeUV) {
+ //var U = eye(m);
+ //var V = eye(n);
+ var csU = new Array(n-1);
+ var csV = new Array(n-1);
+ }
+
+ for ( k = 0; k < n-1 ; k++) {
+ cs = givens(y,z);
+ postmulgivens(cs[0],cs[1], k, k+1);
+
+ if ( computeUV ) {
+ csV[k] = [cs[0], cs[1]];
+ // postmulGivens(cs[0],cs[1], k, k+1, V);
+ }
+
+
+ y = B.val[(i+k)*B.n + j + k];
+ z = B.val[(i+k+1)*B.n + j + k];
+
+ cs = givens(y,z);
+ premulgivens(cs[0],cs[1], k, k+1);
+
+ if ( computeUV ) {
+ csU[k] = [cs[0], cs[1]];
+ //premulGivens(cs[0],cs[1], k, k+1, U);
+ }
+
+ if ( k < n-2 ) {
+ y = B.val[(i+k)*B.n + j + k+1];
+ z = B.val[(i+k)*B.n + j + k+2];
+ }
+
+ }
+
+ if ( computeUV)
+ return {csU: csU, csV: csV};
+}
+
+function svd( A , computeUV ) {
+/* TEST:
+A=[ [-149,-50,-154],[537,180,546],[-27,-9,-25]]
+s=svd(A)
+should return [ 817.7597, 2.4750, 0.0030]
+*/
+
+ if ( type(A) == "vector" || (type(A) == "matrix" && A.n == 1) ) {
+ return { "U" : matrixCopy(A), "S" : ones(1,1), "V" : ones(1,1), "s" : [1] };
+ }
+ if ( A.m == 1) {
+ return { "U" : ones(1,1), "S" : ones(1,1), "V" : transpose(A), "s" : [1] };
+ }
+
+
+ var i;
+ var m = A.length;
+ var n = A.n;
+
+
+ var Atransposed = false;
+ if ( n > m ) {
+ Atransposed = true;
+ var At = transposeMatrix(A);
+ n = m;
+ m = At.length;
+ }
+
+ var computeU = false;
+ var computeV = false;
+ var thinU = false;
+ if ( typeof( computeUV) != "undefined" && computeUV!==false) {
+
+ if ( computeUV === "full" ) {
+ computeU = true;
+ computeV = true;
+ thinU = false;
+ }
+ else if (computeUV === true || computeUV === "thin" ) {
+ computeU = true;
+ computeV = true;
+ thinU = true;
+ }
+ else if ( typeof(computeUV) == "string") {
+ if ( computeUV.indexOf("U") >=0 )
+ computeU = true;
+ if ( computeUV.indexOf("V") >=0 )
+ computeV = true;
+ if ( computeUV.indexOf("thin") >=0 )
+ thinU = true;
+ }
+ var UBV;
+ if ( Atransposed ) {
+ var tmp = computeU;
+ computeU = computeV;
+ computeV = tmp;
+ UBV = bidiagonalize( At, computeU, thinU, computeV );
+ }
+ else
+ UBV = bidiagonalize( A, computeU, thinU, computeV );
+
+ if ( computeU ) {
+ var U = transpose(UBV.U);//Utrans
+ }
+ else
+ var U = undefined;
+
+ if( computeV ) {
+ var V = UBV.V;
+ var Vt = transposeMatrix(V);
+ }
+ else
+ var V = undefined;
+
+ var B = UBV.B;
+ }
+ else {
+ if ( Atransposed )
+ var B = bidiagonalize( At, false, false, false );
+ else
+ var B = bidiagonalize( matrixCopy(A), false, false, false );
+ }
+
+ var B22;
+ var U22;
+ var V22;
+ var cs;
+
+ var q;
+ var p;
+ var k;
+
+ const TOL = 1e-11;
+
+ do {
+
+ for ( i=0; i TOL ) || (Math.abs(B.val[(n-2)*B.n + n-1]) > TOL ) ) {
+ q = 0;
+ }
+ else {
+ q = 1;
+ while ( q < n-1 && Math.abs( B.val[(n-q-1)*B.n + n-q-2] ) < TOL && Math.abs( B.val[(n-q-2)*B.n + n-q-1] ) < TOL ) {
+ q++;
+ }
+ if ( q >= n-1 )
+ q = n;
+ }
+
+ // find smallest p such that B[p:q][p:q] has no zeros on superdiag
+ p=n-q-1;
+ while ( p > 0 && ! isZero ( B.val[(p-1)*B.n + p] ) )
+ p--;
+
+ if ( q < n ) {
+ var DiagonalofB22isZero = -1;
+ for ( k=p; k< n-q-1 ; k++) {
+ if ( Math.abs( B.val[k*B.n + k] ) < TOL ) {
+ DiagonalofB22isZero = k;
+ break;
+ }
+ }
+ if ( DiagonalofB22isZero >= 0 ) {
+
+ if ( DiagonalofB22isZero < n-q-1 ) {
+ // Zero B(k,k+1) and entire row k...
+ for (j=DiagonalofB22isZero+1; j < n; j++) {
+
+ cs = givens(- B.val[j*B.n + j] , B.val[DiagonalofB22isZero * B.n + j] );
+ premulGivens(cs[0],cs[1], DiagonalofB22isZero, j, B);
+ if ( computeU )
+ premulGivens(cs[0],cs[1], DiagonalofB22isZero, j, U);
+ }
+ }
+ else {
+ // Zero B(k-1,k) and entire row k...
+ for (j=DiagonalofB22isZero - 1; j >= p; j--) {
+
+ cs = givens(B.val[j*B.n * j] , B.val[j*B.n + n-q-1] );
+ postmulGivens(cs[0],cs[1], j, n-q-1, B);
+ if ( computeV )
+ premulGivens(cs[0],cs[1], j, n-q-1, Vt);
+// postmulGivens(cs[0],cs[1], j, n-q-1, V);
+
+ }
+ }
+
+ }
+ else {
+ //B22 = get ( B, range(p , n - q ) , range (p , n-q ) );
+
+ if ( computeUV ) {
+ // UBV = GolubKahanSVDstep( B22, true ) ;
+ // set ( U, range(p,n-q), [], mul(UBV.U, get(U, range(p,n-q), []) ) );
+ // set ( Vt, range(p,n-q), [], mul(transpose(UBV.V), getRows(Vt, range(p,n-q)) ) );
+
+ var GKstep = GolubKahanSVDstep( B, p, p, n-q-p, n-q-p, true ) ;// this updates B22 inside B
+ for ( var kk=0; kk < n-q-p-1; kk++) {
+ if ( computeU )
+ premulGivens(GKstep.csU[kk][0], GKstep.csU[kk][1], p+kk, p+kk+1, U);
+ if ( computeV )
+ premulGivens(GKstep.csV[kk][0], GKstep.csV[kk][1], p+kk, p+kk+1, Vt); // premul because Vtransposed
+ }
+ }
+ else {
+ GolubKahanSVDstep( B, p, p, n-q-p, n-q-p ) ;
+ }
+ //set ( B , range(p , n - q ) , range (p , n-q ), B22 );
+ }
+ }
+ } while ( q < n ) ;
+
+ if (computeUV ) {
+
+ if ( computeV)
+ V = transposeMatrix(Vt);
+
+ // Correct sign of singular values:
+ var s = diag(B);
+ var signs = zeros(n);
+ for ( i=0; i< n; i++) {
+ if (s[i] < 0) {
+ if ( computeV )
+ set(V, [], i, minus(get(V,[],i)));
+ s[i] = -s[i];
+ }
+ }
+
+ // Rearrange in decreasing order:
+ var indexes = sort(s,true, true);
+ if(computeV)
+ V = get( V, [], indexes);
+ if(computeU) {
+ if ( !thinU) {
+ for ( i=n; i < m; i++)
+ indexes.push(i);
+ }
+ U = get(U, indexes,[]) ;
+ }
+
+ if ( thinU )
+ var S = diag(s) ;
+ else
+ var S = mat([diag(s), zeros(m-n,n)],true) ;
+
+ var Ut = undefined;
+ if ( computeU )
+ Ut = transpose(U);
+
+ if ( Atransposed ) {
+ if ( thinU )
+ return { "U" : V, "S" : S, "V" : Ut, "s" : s };
+ else
+ return { "U" : V, "S" : transpose(S), "V" : Ut, "s" : s };
+ }
+ else {
+ return { "U" : Ut, "S" : S, "V" : V, "s" : s };
+ }
+ }
+ else
+ return sort(abs(diag(B)), true);
+}
+
+function rank( A ) {
+ const s = svd(A);
+ var rank = 0;
+ var i;
+ for ( i=0;i < s.length;i++)
+ if ( s[i] > 1e-10 )
+ rank++;
+
+ return rank;
+}
+
+function nullspace( A ) {
+ // Orthonormal basis for the null space of A
+ const s = svd( A, "V" ) ;
+ const n = A.n;
+
+ var rank = 0;
+ const TOL = 1e-8;
+ while ( rank < n && s.s[rank] > TOL )
+ rank++;
+
+ if ( rank < n )
+ return get ( s.V, [], range(rank, n) );
+ else
+ return zeros(n);
+
+}
+
+function orth( A ) {
+ // Orthonormal basis for the range of A
+ const s = svd( A, "thinU" ) ;
+ const n = A.n;
+
+ var rank = 0;
+ const TOL = 1e-8;
+ while ( rank < n && s.s[rank] > TOL )
+ rank++;
+
+ return get ( s.U, [], range(0,rank) );
+
+}
+
+/////////////////////////////
+//// Sparse matrix and vectors
+/////////////////////////////
+
+/**
+ *
+ * new spVector(n) => allocate for n nonzeros with dim n
+ * new spVector(n, nnz) => allocate for nnz nonzeros out of n
+ * new spVector(n,values,indexes) => allocate for values.length nonzeros
+ *
+ * @constructor
+ * @struct
+ */
+function spVector(n, values, indexes) {
+
+ /** @const */ this.length = n;
+ /** @const */ this.size = [n,1];
+ /** @const */ this.type = "spvector";
+
+ if ( arguments.length <= 2) {
+ if ( arguments.length == 1)
+ var nnz = n; // too large but more efficient at some point...
+ else
+ var nnz = values;
+
+ /** @type{Float64Array} */ this.val = new Float64Array(nnz); // nz values
+ /** @type{Uint32Array} */ this.ind = new Uint32Array(nnz); // ind[k] = index of val[k]
+ }
+ else {
+ var nnz = values.length;
+ /** @type{Float64Array} */ this.val = new Float64Array(values); // nz values
+ /** @type{Uint32Array} */ this.ind = new Uint32Array(indexes); // ind[k] = index of val[k]
+ }
+
+ /** @const */ this.nnz = nnz;
+}
+/*
+ * @param{number}
+ * @return{number}
+ */
+spVector.prototype.get = function ( i ) {
+ var k = this.ind.indexOf(i);
+ if ( k < 0 )
+ return 0;
+ else
+ return this.val[k];
+}
+/*
+ * @param{number}
+ * @param{number}
+ */
+spVector.prototype.set = function ( i, value ) {
+ // Inefficient do not use this, use sparse(x) instead
+ if ( i > this.n ) {
+ error( "Error in spVector.set(i,value): i > this.length)");
+ return undefined;
+ }
+ var k = this.ind.indexOf(i);
+ if ( k < 0 ) {
+ var ind = new Uint32Array(this.nnz + 1);
+ var val = new Float64Array(this.nnz + 1);
+ k = 0;
+ while ( this.ind[k] < i ) { // copy values until i
+ ind[k] = this.ind[k]; // making sure this.ind remains sorted
+ val[k] = this.val.ind[k];
+ k++;
+ }
+ ind[k] = i;// insert value
+ val[k] = value;
+ ind.set(this.ind.subarray(k), k+1);// copy rest of vector
+ val.set(this.val.subarray(k), k+1);
+ this.nnz++;
+ }
+ else
+ this.val[k] = value;
+
+ return value;
+}
+/*
+ * @return{spVector}
+ */
+spVector.prototype.copy = function () {
+ return new spVector(this.n, this.val, this.ind);
+}
+
+/**
+ *
+ * new spMatrix(m,n) => allocate for m*n nonzeros
+ * new spMatrix(m,n, nnz) => allocate for nnz nonzeros
+ * new spMatrix(m,n,values,cols,rows) => allocate for values.length nonzeros
+ *
+ * @constructor
+ * @struct
+ */
+function spMatrix(m,n, values, cols, rows) {
+
+ /** @const */ this.length = m;
+ /** @const */ this.m = m;
+ /** @const */ this.n = n;
+ /** @const */ this.size = [m,n];
+ /** @const */ this.type = "spmatrix";
+
+ if ( arguments.length <= 3) {
+ if ( arguments.length == 2)
+ var nnz = m*n; // too large but more efficient at some point...
+ else
+ var nnz = values;
+
+ /** @type{boolean} */ this.rowmajor = true;
+ /** @type{Float64Array} */ this.val = new Float64Array(nnz); // nnz values
+ /** @type{Uint32Array} */ this.cols = new Uint32Array(nnz); // cols[j] = starting index of col j in val and rows
+ /** @type{Uint32Array} */ this.rows = new Uint32Array(m+1); // rows[k] = row of val[k]
+ }
+ else {
+ var nnz = values.length;
+ if ( rows.length == nnz && cols.length == n+1 && cols[cols.length-1] == nnz ) {
+ /** @type{boolean} */ this.rowmajor = false;
+ /** @type{Float64Array} */ this.val = new Float64Array(values); // nz values
+ /** @type{Uint32Array} */ this.cols = new Uint32Array(cols); // cols[j] = starting index of col j in val and rows
+ /** @type{Uint32Array} */ this.rows = new Uint32Array(rows); // rows[k] = row of val[k]
+ }
+ else {
+ /** @type{boolean} */ this.rowmajor = true;
+ /** @type{Float64Array} */ this.val = new Float64Array(values); // nz values
+ /** @type{Uint32Array} */ this.cols = new Uint32Array(cols); // cols[k] = col of val[k]
+ /** @type{Uint32Array} */ this.rows = new Uint32Array(rows); // rows[i] = starting index of row i in val and cols
+ }
+ }
+
+ /** @const */ this.nnz = nnz;
+
+}
+/*
+ * @return{spMatrix}
+ */
+spMatrix.prototype.copy = function () {
+ return new spMatrix(this.m, this.n, this.val, this.cols, this.rows);
+}
+/*
+ * @return{spMatrix}
+ */
+spMatrix.prototype.toRowmajor = function () {
+ if ( this.rowmajor )
+ return this.copy();
+ else {
+ return sparseMatrixRowMajor( fullMatrix(this) );
+ }
+}
+/*
+ * Get a pointer to the spVector for row i
+ * @return{spVector}
+ */
+spMatrix.prototype.row = function ( i ) {
+ if ( this.rowmajor ) {
+ return new spVector(this.n, this.val.subarray(this.rows[i], this.rows[i+1]), this.cols.subarray(this.rows[i], this.rows[i+1]));
+ /*
+ var s = this.rows[i];
+ var e = this.rows[i+1];
+ var vec = new spVector(this.n);
+ vec.val.set(this.val.subarray(s,e));
+ vec.ind.set(this.cols.subarray(s,e));
+ return vec;*/
+ }
+ else {
+ error ("Cannot extract sparse column from a sparse matrix in row major format.");
+ return undefined;
+ }
+}
+/*
+ * Get a pointer to the spVector for column j
+ * @return{spVector}
+ */
+spMatrix.prototype.col = function ( j ) {
+ if ( ! this.rowmajor )
+ return new spVector(this.m, this.val.subarray(this.cols[j], this.cols[j+1]), this.rows.subarray(this.cols[j], this.cols[j+1]));
+ else {
+ error ("Cannot extract sparse column from a sparse matrix in row major format.");
+ return undefined;
+ }
+}
+
+/*
+ * @param{number}
+ * @param{number}
+ * @return{number}
+ */
+spMatrix.prototype.get = function ( i, j ) {
+ if ( this.rowmajor ) {
+ var rowind = this.cols.subarray(this.rows[i], this.rows[i+1]);
+ var k = rowind.indexOf(j);
+ if ( k < 0 )
+ return 0;
+ else
+ return this.val[this.rows[i] + k];
+ }
+ else {
+ var colind = this.rows.subarray(this.cols[j], this.cols[j+1]);
+ var k = colind.indexOf(i);
+ if ( k < 0 )
+ return 0;
+ else
+ return this.val[this.cols[j] + k];
+ }
+}
+
+function spgetRows(A, rowsrange) {
+ var n = rowsrange.length;
+ if ( A.rowmajor) {
+ if ( n > 1 ) {
+
+ var rowsidx = sort(rowsrange);
+ var Ai = new Array(n);
+ var nnz = 0;
+ for ( var i = 0; i < n; i++) {
+ Ai[i] = A.row(rowsidx[i]);
+ nnz += Ai[i].val.length;
+ }
+ var val = new Float64Array( nnz );
+ var cols = new Uint32Array( nnz );
+ var rows = new Uint32Array( n+1 );
+ var k = 0;
+ for ( var i = 0; i < n; i++) {
+ rows[i] = k;
+ val.set(Ai[i].val, k);
+ cols.set(Ai[i].ind, k);
+ k += Ai[i].val.length;
+ }
+ rows[i] = k;
+ return new spMatrix(n, A.n, val, cols, rows);
+ }
+ else
+ return A.row( rowsrange[0] ) ;
+ }
+ else {
+ return getRows(fullMatrix(A), rowsrange);
+ }
+}
+
+/**
+ * Return the full/dense version of the vector
+ * @param{spVector}
+ * @return{Float64Array}
+ */
+function fullVector (x) {
+ var k;
+ const n = x.length;
+ const nnz = x.val.length;
+ var a = new Float64Array(n);
+
+ for ( k=0; k < nnz; k++)
+ a[x.ind[k]] = x.val[k];
+
+ return a;
+}
+/**
+ * Return the full/dense version of the matrix
+ * @param{spMatrix}
+ * @return{Matrix}
+ */
+function fullMatrix (S) {
+ const n = S.n;
+ if ( S.rowmajor ) {
+ var k;
+ const m = S.m;
+ var A = new Float64Array(m * n);
+ var ri = 0;
+ for (var i = 0; i < m; i++) {
+ var s = S.rows[i];
+ var e = S.rows[i+1];
+ for ( k=s; k < e; k++) {
+ A[ri + S.cols[k] ] = S.val[k];
+ }
+ ri += n;
+ }
+ return new Matrix(m, n, A, true);
+ }
+ else {
+ var k;
+ var A = new Float64Array(S.m * n);
+ for (var j = 0; j < n; j++) {
+ var s = S.cols[j];
+ var e = S.cols[j+1];
+ for ( k=s; k < e; k++) {
+ var i = S.rows[k];
+ A[i*n + j] = S.val[k];
+ }
+ }
+ return new Matrix(S.m, n, A, true);
+ }
+}
+function full( A ) {
+ switch(type(A)) {
+ case "spvector":
+ return fullVector(A);
+ break;
+ case "spmatrix":
+ return fullMatrix(A);
+ break;
+ default:
+ return A;
+ break;
+ }
+}
+
+/**
+ * @param{Float64Array}
+ * @return{spVector}
+ */
+function sparseVector( a ) {
+ var i,k;
+ const n = a.length;
+ var val = new Array();
+ var ind = new Array();
+ for ( i=0; i < n; i++) {
+ if (!isZero(a[i]) ) {
+ val.push(a[i]);
+ ind.push(i);
+ }
+ }
+ return new spVector(n,val,ind);
+}
+/**
+ * @param{Matrix}
+ * @return{spMatrix}
+ */
+function sparseMatrix( A ) {
+ var i,j;
+ const m = A.m;
+ const n = A.n;
+ var val = new Array();
+ var rows = new Array();
+ var cols = new Uint32Array(n+1);
+ var k;
+ for ( j=0; j< n; j++) {
+ k = j;
+ for ( i=0; i < m; i++) {
+ // k = i*n+j;
+ if (!isZero(A.val[k]) ) {
+ val.push(A.val[k]);
+ rows.push(i);
+ cols[j+1]++;
+ }
+ k += n;
+ }
+ }
+ for ( j=1; j< n; j++)
+ cols[j+1] += cols[j];
+
+ return new spMatrix(m,n,val,cols,rows);
+}
+/**
+ * @param{Matrix}
+ * @return{spMatrix}
+ */
+function sparseMatrixRowMajor( A ) {
+ var i,j;
+ const m = A.m;
+ const n = A.n;
+ var val = new Array();
+ var cols = new Array();
+ var rows = new Uint32Array(m+1);
+ var k = 0;
+ for ( i=0; i < m; i++) {
+ for ( j=0; j< n; j++) {
+ // k = i*n+j;
+ if (!isZero(A.val[k]) ) {
+ val.push(A.val[k]);
+ rows[i+1]++;
+ cols.push(j);
+ }
+ k++;
+ }
+ }
+ for ( i=1; i< m; i++)
+ rows[i+1] += rows[i];
+
+ return new spMatrix(m,n,val,cols,rows);
+}
+
+function sparse( A , rowmajor ) {
+ if(typeof(rowmajor) == "undefined" )
+ var rowmajor = true;
+
+ switch(type(A)) {
+ case "vector":
+ return sparseVector(A);
+ break;
+ case "matrix":
+ if ( rowmajor )
+ return sparseMatrixRowMajor(A);
+ else
+ return sparseMatrix(A);
+ break;
+ case "spvector":
+ case "spmatrix":
+ return A.copy();
+ break;
+ default:
+ return A;
+ break;
+ }
+}
+
+/**
+ * @param{number}
+ * @return{spMatrix}
+ */
+function speye(m,n) {
+ if ( typeof(n) == "undefined" )
+ var n = m;
+ if ( m == 1 && n == 1)
+ return 1;
+
+ var e = (m 1 ) {
+ M.val.set( new Float64Array(res[k].val), p);
+ M.cols.set( new Uint32Array(res[k].ind), p);
+ M.rows[k+1] = M.rows[k] + res[k].val.length;
+ p += res[k].val.length;
+ }
+ else if (res[k].val.length == 1) {
+ M.val[p] = res[k].val[0];
+ M.cols[p] = res[k].ind[0];
+ M.rows[k+1] = M.rows[k] + 1;
+ p += 1;
+ }
+
+ }
+ return M;
+ }
+ else {
+ // not yet...
+
+ error("spmat(..., false) for columnwise concatenation of sparse vectors not yet implemented");
+
+ return res;
+ }
+}
+
+
+
+/**
+ * @param{number}
+ * @param{spVector}
+ * @return{spVector}
+ */
+function mulScalarspVector (a, b) {
+ const nnz = b.val.length;
+ var c = b.copy();
+ for ( var k=0;k < nnz; k++)
+ c.val[k] *= a;
+ return c;
+}
+/**
+ * @param{number}
+ * @param{spMatrix}
+ * @return{spMatrix}
+ */
+function mulScalarspMatrix (a, B) {
+ const nnz = B.nnz;
+ var C = B.copy();
+ for ( var k=0;k < nnz; k++)
+ C.val[k] *= a;
+ return C;
+}
+
+/**
+ * @param{spVector}
+ * @param{spVector}
+ * @return{number}
+ */
+function spdot (a, b) {
+ const nnza = a.val.length;
+ const nnzb = b.val.length;
+ var c = 0;
+ var ka = 0;
+ var kb = 0;
+ while ( ka < nnza && kb < nnzb ){
+ var i = a.ind[ka];
+ while ( b.ind[kb] < i && kb < nnzb)
+ kb++;
+ if(b.ind[kb] == i)
+ c += a.val[ka] * b.val[kb];
+ ka++;
+ }
+ return c;
+}
+/**
+ * @param{spVector}
+ * @param{Float64Array}
+ * @return{number}
+ */
+function dotspVectorVector (a, b) {
+ const nnza = a.val.length;
+ var c = 0;
+ for ( var ka=0;ka < nnza; ka++)
+ c += a.val[ka] * b[a.ind[ka]];
+
+ return c;
+}
+/**
+ * @param{Matrix}
+ * @param{spVector}
+ * @return{Float64Array}
+ */
+function mulMatrixspVector (A, b) {
+ const m = A.m;
+ const n = A.n;
+ const nnz = b.val.length;
+ var c = zeros(m);
+ var ri = 0;
+ for ( var i=0;i < n; i++) {
+ for ( var k=0; k < nnz; k++)
+ c[i] += A.val[ri + b.ind[k]] * b.val[k];
+ ri+=n;
+ }
+ return c;
+}
+/**
+ * @param{spMatrix}
+ * @param{Float64Array}
+ * @return{Float64Array}
+ */
+function mulspMatrixVector (A, b) {
+ const m = A.m;
+ const n = A.n;
+ var c = zeros(m);
+ if ( A.rowmajor) {
+ for(var i=0; i < m; i++) {
+ var s = A.rows[i];
+ var e = A.rows[i+1];
+ for(var k = s; k < e; k++) {
+ c[i] += A.val[k] * b[A.cols[k]];
+ }
+ }
+ }
+ else {
+ for ( var j=0;j < n; j++) {
+ var s = A.cols[j];
+ var e = A.cols[j+1];
+ var bj = b[j];
+ for ( var k= s; k < e; k++) {
+ c[A.rows[k]] += A.val[k] * bj;
+ }
+ }
+ }
+ return c;
+}
+/**
+ * @param{spMatrix}
+ * @param{Float64Array}
+ * @return{Float64Array}
+ */
+function mulspMatrixTransVector (A, b) {
+ const m = A.m;
+ const n = A.n;
+ var c = zeros(n);
+ if ( A.rowmajor ) {
+ for ( var j=0;j < m; j++) {
+ var s = A.rows[j];
+ var e = A.rows[j+1];
+ var bj = b[j];
+ for ( var k= s; k < e; k++) {
+ c[A.cols[k]] += A.val[k] * bj;
+ }
+ }
+ }
+ else {
+ for ( var j=0;j < n; j++) {
+ var s = A.cols[j];
+ var e = A.cols[j+1];
+ for ( var k= s; k < e; k++) {
+ c[j] += A.val[k] * b[A.rows[k]];
+ }
+ }
+ }
+ return c;
+}
+/**
+ * @param{spMatrix}
+ * @param{spVector}
+ * @return{Float64Array}
+ */
+function mulspMatrixspVector (A, b) {
+ const m = A.m;
+ const n = A.n;
+ var c = zeros(m);
+ const nnzb = b.val.length;
+ if ( A.rowmajor) {
+ for(var i=0; i < m; i++) {
+ c[i] = spdot(A.row(i), b);
+ }
+ }
+ else {
+ for ( var kb=0;kb < nnzb; kb++) {
+ var j = b.ind[kb];
+ var bj = b.val[kb];
+ var s = A.cols[j];
+ var e = A.cols[j+1];
+
+ for ( var k= s; k < e; k++) {
+ c[A.rows[k]] += A.val[k] * bj;
+ }
+ }
+ }
+ return c;
+}
+/**
+ * @param{spMatrix}
+ * @param{spVector}
+ * @return{Float64Array}
+ */
+function mulspMatrixTransspVector (A, b) {
+ const m = A.m;
+ const n = A.n;
+ var c = zeros(n);
+ const nnzb = b.val.length;
+ if (A.rowmajor) {
+ for ( var kb=0;kb < nnzb; kb++) {
+ var j = b.ind[kb];
+ var bj = b.val[kb];
+ var s = A.rows[j];
+ var e = A.rows[j+1];
+ for ( var k= s; k < e; k++) {
+ c[A.cols[k]] += A.val[k] * bj;
+ }
+ }
+ }
+ else {
+ for ( var i= 0; i < n; i++) {
+ var kb = 0;
+ var s = A.cols[i];
+ var e = A.cols[i+1];
+
+ for ( var ka=s;ka < e; ka++) {
+ var j = A.rows[ka];
+ while ( b.ind[kb] < j && kb < nnzb)
+ kb++;
+ if(b.ind[kb] == i)
+ c[i] += A.val[ka] * b.val[kb];
+ }
+ }
+ }
+ return c;
+}
+/**
+ * @param{spMatrix}
+ * @param{spMatrix}
+ * @return{Matrix}
+ */
+function mulspMatrixspMatrix (A, B) {
+ const m = A.m;
+ const n = A.n;
+ const n2 = B.n;
+ var c = zeros(m, n2);
+
+ if ( A.rowmajor ) {
+ if ( B.rowmajor ) {
+ for ( var ic = 0; ic < m; ic++) {
+ var sa = A.rows[ic];
+ var ea = A.rows[ic+1];
+
+ for ( var ka = sa; ka < ea; ka++) {
+ var j = A.cols[ka];
+ var aj = A.val[ka];
+
+ var s = B.rows[j];
+ var e = B.rows[j+1];
+
+ var rc = ic * n2 ;
+ for (var k= s; k < e; k++) {
+ c.val[rc + B.cols[k] ] += aj * B.val[k] ;
+ }
+ }
+ }
+ }
+ else {
+ var kc = 0;
+ for ( var i=0; i < m; i++) {
+ for ( var j=0; j < n2; j++) {
+ c.val[kc] = spdot(A.row(i), B.col(j));
+ kc++;
+ }
+ }
+ }
+ }
+ else {
+ if ( B.rowmajor ) {
+ for (var ja=0;ja < n; ja++) {
+ var sa = A.cols[ja];
+ var ea = A.cols[ja+1];
+ var sb = B.rows[ja];
+ var eb = B.rows[ja+1];
+ for ( var ka = sa; ka < ea; ka++) {
+ var rc = A.rows[ka] * n2;
+ var aij = A.val[ka];
+
+ for(var kb = sb; kb < eb; kb++) {
+ c.val[rc + B.cols[kb]] += aij * B.val[kb];
+ }
+ }
+ }
+ }
+ else {
+ for ( var jc = 0; jc < n2; jc++) {
+ var sb = B.cols[jc];
+ var eb = B.cols[jc+1];
+
+ for ( var kb = sb; kb < eb; kb++) {
+ var j = B.rows[kb];
+ var bj = B.val[kb];
+
+ var s = A.cols[j];
+ var e = A.cols[j+1];
+
+ for (var k= s; k < e; k++) {
+ c.val[A.rows[k] * n2 + jc] += A.val[k] * bj;
+ }
+ }
+ }
+ }
+ }
+ return c;
+}
+/**
+ * @param{Matrix}
+ * @param{spMatrix}
+ * @return{Matrix}
+ */
+function mulMatrixspMatrix (A, B) {
+ const m = A.m;
+ const n = A.n;
+ const n2 = B.n;
+ var c = zeros(m, n2);
+
+ if ( B.rowmajor ) {
+ for (var ja=0;ja < n; ja++) {
+ var sb = B.rows[ja];
+ var eb = B.rows[ja+1];
+ for ( var i = 0; i < m; i++) {
+ var rc = i * n2;
+ var aij = A.val[i * n + ja];
+
+ for(var kb = sb; kb < eb; kb++) {
+ c.val[rc + B.cols[kb]] += aij * B.val[kb];
+ }
+ }
+ }
+ }
+ else {
+ for ( var jc = 0; jc < n2; jc++) {
+ var sb = B.cols[jc];
+ var eb = B.cols[jc+1];
+
+ for ( var kb = sb; kb < eb; kb++) {
+ var j = B.rows[kb];
+ var bj = B.val[kb];
+
+ for ( i= 0; i < m; i++) {
+ c.val[i * n2 + jc] += A.val[i*n + j] * bj;
+ }
+ }
+ }
+ }
+ return c;
+}
+
+/**
+ * @param{spMatrix}
+ * @param{Matrix}
+ * @return{Matrix}
+ */
+function mulspMatrixMatrix (A, B) {
+ const m = A.m;
+ const n = A.n;
+ const n2 = B.n;
+ var c = zeros(m, n2);
+
+ if ( A.rowmajor ) {
+ for(var i=0; i < m; i++) {
+ var sa = A.rows[i];
+ var ea = A.rows[i+1];
+ for(var ka = sa; ka < ea; ka++) {
+ var ai = A.val[ka];
+ var rb = A.cols[ka] * n2;
+ var rc = i*n2;
+ for ( j=0; j < n2; j++) {
+ c.val[rc + j] += ai * B.val[rb + j];
+ }
+ }
+ }
+ }
+ else {
+ for(var j=0; j < n; j++) {
+ var s = A.cols[j];
+ var e = A.cols[j+1];
+
+ for ( var k= s; k < e; k++) {
+ var i = A.rows[k];
+ for ( var jc = 0; jc < n2; jc++)
+ c.val[i*n2 + jc ] += A.val[k] * B.val[j*n2 + jc];
+ }
+ }
+ }
+ return c;
+}
+
+/**
+ * @param{spVector}
+ * @param{spVector}
+ * @return{spVector}
+ */
+function entrywisemulspVectors (a, b) {
+ const nnza = a.val.length;
+ const nnzb = b.val.length;
+ var val = new Array();
+ var ind = new Array();
+
+ var ka = 0;
+ var kb = 0;
+ while ( ka < nnza && kb < nnzb ){
+ var i = a.ind[ka];
+ while ( b.ind[kb] < i && kb < nnzb)
+ kb++;
+ if(b.ind[kb] == i) {
+ var aibi = a.val[ka] * b.val[kb];
+ if ( !isZero(aibi) ) {
+ val.push(aibi);
+ ind.push(i);
+ }
+ }
+ ka++;
+ }
+ return new spVector(a.length, val, ind);
+}
+/**
+ * @param{spVector}
+ * @param{Float64Array}
+ * @return{spVector}
+ */
+function entrywisemulspVectorVector (a, b) {
+ // fast operation but might not yield optimal nnz:
+ var c = a.copy();
+ const nnz = a.val.length;
+ for ( var k = 0; k< nnz; k++) {
+ c.val[k] *= b[a.ind[k]];
+ }
+ return c;
+}
+/**
+ * @param{spMatrix}
+ * @param{spMatrix}
+ * @return{spMatrix}
+ */
+function entrywisemulspMatrices (A, B) {
+ if ( A.rowmajor ) {
+ if ( B.rowmajor ) {
+ var val = new Array();
+ var cols = new Array();
+ var rows = new Uint32Array(A.m+1);
+ var ka;
+ var kb;
+ var i;
+ for ( i=0; i < A.m; i++) {
+ ka = A.rows[i];
+ kb = B.rows[i];
+ var ea = A.rows[i+1];
+ var eb = B.rows[i+1];
+ while ( ka < ea & kb < eb ){
+ var j = A.cols[ka];
+ while ( B.cols[kb] < j && kb < eb)
+ kb++;
+ if(B.cols[kb] == j) {
+ val.push(A.val[ka] * B.val[kb]);
+ cols.push(j);
+ rows[i+1]++;
+ }
+ ka++;
+ }
+ }
+ for(i=1; i < A.m; i++)
+ rows[i+1] += rows[i];
+
+ return new spMatrix(A.m, A.n, val, cols, rows);
+ }
+ else {
+ return entrywisemulspMatrixMatrix(B, fullMatrix(A)); // perhaps not the fastest
+ }
+ }
+ else {
+ if ( B.rowmajor ) {
+ return entrywisemulspMatrixMatrix(A, fullMatrix(B)); // perhaps not the fastest
+ }
+ else {
+ var val = new Array();
+ var cols = new Uint32Array(A.n+1);
+ var rows = new Array();
+ var ka;
+ var kb;
+ var j;
+ for ( j=0; j < A.n; j++) {
+ ka = A.cols[j];
+ kb = B.cols[j];
+ var ea = A.cols[j+1];
+ var eb = B.cols[j+1];
+ while ( ka < ea & kb < eb ){
+ var i = A.rows[ka];
+ while ( B.rows[kb] < i && kb < eb)
+ kb++;
+ if(B.rows[kb] == i) {
+ val.push(A.val[ka] * B.val[kb]);
+ rows.push(i);
+ cols[j+1]++;
+ }
+ ka++;
+ }
+ }
+ for ( j=1; j< A.n; j++)
+ cols[j+1] += cols[j];
+
+ return new spMatrix(A.m, A.n, val, cols, rows);
+ }
+ }
+}
+/**
+ * @param{spMatrix}
+ * @param{Matrix}
+ * @return{spMatrix}
+ */
+function entrywisemulspMatrixMatrix (A, B) {
+ var c = A.copy();
+ const nnz = A.val.length;
+ const n = A.n;
+ const m = A.m;
+ if ( A.rowmajor ) {
+ for ( i=0;i< m; i++) {
+ var s = c.rows[i];
+ var e = c.rows[i+1];
+ var r = i*n;
+ for ( var k = s; k< e; k++) {
+ c.val[k] *= B.val[r + c.cols[k] ];
+ }
+ }
+ }
+ else {
+ for ( j=0;j< n; j++) {
+ var s = c.cols[j];
+ var e = c.cols[j+1];
+ for ( var k = s; k< e; k++) {
+ c.val[k] *= B.val[c.rows[k] * n + j];
+ }
+ }
+ }
+ return c;
+}
+
+/**
+ * @param{number}
+ * @param{spVector}
+ * @return{Float64Array}
+ */
+function addScalarspVector (a, b) {
+ const nnzb = b.val.length;
+ const n = b.length;
+ var c = zeros(n);
+ var k;
+ for ( k=0;k < n; k++)
+ c[k] = a;
+ for ( k=0;k < nnzb; k++)
+ c[b.ind[k]] += b.val[k];
+
+ return c;
+}
+/**
+ * @param{Float64Array}
+ * @param{spVector}
+ * @return{Float64Array}
+ */
+function addVectorspVector (a, b) {
+ const nnzb = b.val.length;
+ const n = b.length;
+ var c = new Float64Array(a);
+ for (var k=0;k < nnzb; k++)
+ c[b.ind[k]] += b.val[k];
+
+ return c;
+}
+/**
+ * @param{spVector}
+ * @param{spVector}
+ * @return{spVector}
+ */
+function addspVectors (a, b) {
+ const nnza = a.val.length;
+ const nnzb = b.val.length;
+ var c = zeros(a.length);
+ var k;
+ for ( k=0;k < nnza; k++)
+ c[a.ind[k]] = a.val[k];
+ for ( k=0;k < nnzb; k++)
+ c[b.ind[k]] += b.val[k];
+
+ return sparseVector(c);
+}
+
+/**
+ * @param{number}
+ * @param{spMatrix}
+ * @return{Matrix}
+ */
+function addScalarspMatrix (a, B) {
+ const nnzb = B.val.length;
+ const m = B.m;
+ const n = B.n;
+ const mn = m*n;
+
+ var C = zeros(m,n);
+ var i;
+ for (i = 0; i < mn; i++)
+ C.val[i] = a;
+ if ( B.rowmajor ) {
+ var ri = 0;
+ for (i = 0; i < m; i++) {
+ var s = B.rows[i];
+ var e = B.rows[i+1];
+ for (var k= s; k < e; k++)
+ C.val[ri + B.cols[k]] += B.val[k];
+ ri += n;
+ }
+ }
+ else {
+ for (i = 0; i < n; i++) {
+ var s = B.cols[i];
+ var e = B.cols[i+1];
+ for (var k= s; k < e; k++)
+ C.val[B.rows[k] * n + i] += B.val[k];
+ }
+ }
+ return C;
+}
+/**
+ * @param{Matrix}
+ * @param{spMatrix}
+ * @return{Matrix}
+ */
+function addMatrixspMatrix (A, B) {
+ const nnzb = B.val.length;
+ const m = B.m;
+ const n = B.n;
+ const mn = m*n;
+
+ var C = matrixCopy(A);
+ var i;
+ if ( B.rowmajor ) {
+ var ri = 0;
+ for (i = 0; i < m; i++) {
+ var s = B.rows[i];
+ var e = B.rows[i+1];
+ for (var k= s; k < e; k++)
+ C.val[ri + B.cols[k]] += B.val[k];
+ ri += n;
+ }
+ }
+ else {
+ for (i = 0; i < n; i++) {
+ var s = B.cols[i];
+ var e = B.cols[i+1];
+ for (var k= s; k < e; k++)
+ C.val[B.rows[k] * n + i] += B.val[k];
+ }
+ }
+ return C;
+}
+/**
+ * @param{spMatrix}
+ * @param{spMatrix}
+ * @return{spMatrix}
+ */
+function addspMatrices (A, B) {
+ const nnza = A.val.length;
+ const nnzb = B.val.length;
+ const m = A.m;
+ const n = A.n;
+
+ var C = fullMatrix(A);
+ var i;
+ if ( B.rowmajor ) {
+ var ri = 0;
+ for (i = 0; i < m; i++) {
+ var s = B.rows[i];
+ var e = B.rows[i+1];
+ for (var k= s; k < e; k++)
+ C.val[ri + B.cols[k]] += B.val[k];
+ ri += n;
+ }
+ }
+ else {
+ for (i = 0; i < n; i++) {
+ var s = B.cols[i];
+ var e = B.cols[i+1];
+ for (var k= s; k < e; k++)
+ C.val[B.rows[k] * n + i] += B.val[k];
+ }
+ }
+ return sparseMatrixRowMajor(C);
+}
+
+/** sparse SAXPY : y = y + ax with x sparse and y dense
+ * @param {number}
+ * @param {spVector}
+ * @param {Float64Array}
+ */
+function spsaxpy ( a, x, y) {
+ const nnz = x.val.length;
+ for (var k=0;k < nnz; k++)
+ y[x.ind[k]] += a * x.val[k];
+}
+
+/**
+ * @param{number}
+ * @param{spVector}
+ * @return{Float64Array}
+ */
+function subScalarspVector (a, b) {
+ const nnzb = b.val.length;
+ const n = b.length;
+ var c = zeros(n);
+ var k;
+ for ( k=0;k < n; k++)
+ c[k] = a;
+ for ( k=0;k < nnzb; k++)
+ c[b.ind[k]] -= b.val[k];
+
+ return c;
+}
+/**
+ * @param{Float64Array}
+ * @param{spVector}
+ * @return{Float64Array}
+ */
+function subVectorspVector (a, b) {
+ const nnzb = b.val.length;
+ const n = b.length;
+ var c = new Float64Array(a);
+ for (var k=0;k < nnzb; k++)
+ c[b.ind[k]] -= b.val[k];
+
+ return c;
+}
+/**
+ * @param{spVector}
+ * @param{Float64Array}
+ * @return{Float64Array}
+ */
+function subspVectorVector (a, b) {
+ return subVectors(fullVector(a), b);
+}
+/**
+ * @param{spVector}
+ * @param{spVector}
+ * @return{spVector}
+ */
+function subspVectors (a, b) {
+ const nnza = a.val.length;
+ const nnzb = b.val.length;
+ var c = zeros(a.length);
+ var k;
+ for ( k=0;k < nnza; k++)
+ c[a.ind[k]] = a.val[k];
+ for ( k=0;k < nnzb; k++)
+ c[b.ind[k]] -= b.val[k];
+
+ return sparseVector(c);
+}
+
+/**
+ * @param{number}
+ * @param{spMatrix}
+ * @return{Matrix}
+ */
+function subScalarspMatrix (a, B) {
+ const nnzb = B.val.length;
+ const m = B.m;
+ const n = B.n;
+ const mn = m*n;
+
+ var C = zeros(m,n);
+ var i;
+ for (i = 0; i < mn; i++)
+ C.val[i] = a;
+ if ( B.rowmajor ) {
+ var ri = 0;
+ for (i = 0; i < m; i++) {
+ var s = B.rows[i];
+ var e = B.rows[i+1];
+ for (var k= s; k < e; k++)
+ C.val[ri + B.cols[k]] -= B.val[k];
+ ri += n;
+ }
+ }
+ else {
+ for (i = 0; i < n; i++) {
+ var s = B.cols[i];
+ var e = B.cols[i+1];
+ for (var k= s; k < e; k++)
+ C.val[B.rows[k] * n + i] -= B.val[k];
+ }
+ }
+ return C;
+}
+/**
+ * @param{spMatrix}
+ * @param{Matrix}
+ * @return{Matrix}
+ */
+function subspMatrixMatrix (A, B) {
+ return subMatrices(fullMatrix(A), B);
+}
+/**
+ * @param{Matrix}
+ * @param{spMatrix}
+ * @return{Matrix}
+ */
+function subMatrixspMatrix (A, B) {
+ const nnzb = B.val.length;
+ const m = B.m;
+ const n = B.n;
+ const mn = m*n;
+
+ var C = matrixCopy(A);
+ var i;
+ if ( B.rowmajor ) {
+ var ri = 0;
+ for (i = 0; i < m; i++) {
+ var s = B.rows[i];
+ var e = B.rows[i+1];
+ for (var k= s; k < e; k++)
+ C.val[ri + B.cols[k]] -= B.val[k];
+ ri += n;
+ }
+ }
+ else {
+ for (i = 0; i < n; i++) {
+ var s = B.cols[i];
+ var e = B.cols[i+1];
+ for (var k= s; k < e; k++)
+ C.val[B.rows[k] * n + i] -= B.val[k];
+ }
+ }
+ return C;
+}
+/**
+ * @param{spMatrix}
+ * @param{spMatrix}
+ * @return{spMatrix}
+ */
+function subspMatrices (A, B) {
+ const nnza = A.val.length;
+ const nnzb = B.val.length;
+ const m = A.m;
+ const n = A.n;
+
+ var C = fullMatrix(A);
+ var i;
+ if ( B.rowmajor ) {
+ var ri = 0;
+ for (i = 0; i < m; i++) {
+ var s = B.rows[i];
+ var e = B.rows[i+1];
+ for (var k= s; k < e; k++)
+ C.val[ri + B.cols[k]] -= B.val[k];
+ ri += n;
+ }
+ }
+ else {
+ for (i = 0; i < n; i++) {
+ var s = B.cols[i];
+ var e = B.cols[i+1];
+ for (var k= s; k < e; k++)
+ C.val[B.rows[k] * n + i] -= B.val[k];
+ }
+ }
+ return sparseMatrixRowMajor(C);
+}
+
+/**
+ * @param{function}
+ * @param{spVector}
+ * @return{Float64Array}
+ */
+function applyspVector( f, x ) {
+ const nnz = x.val.length;
+ const n = x.length;
+ var res = new Float64Array(n);
+ var i;
+ const f0 = f(0);
+ for ( i=0; i< n; i++)
+ res[i] = f0;
+ for ( i=0; i< nnz; i++)
+ res[x.ind[i]] = f(x.val[i]);
+ return res;
+}
+/**
+ * @param{function}
+ * @param{spMatrix}
+ * @return{Matrix}
+ */
+function applyspMatrix( f, X ) {
+ const nnz = X.val.length;
+ const m = X.m;
+ const n = X.n;
+ const mn = m*n;
+ const f0 = f(0);
+ var C = zeros(m,n);
+ var i;
+ if ( !isZero(f0) ) {
+ for (i = 0; i < mn; i++)
+ C.val[i] = f0;
+ }
+ if ( X.rowmajor ) {
+ var ri = 0;
+ for (i = 0; i < m; i++) {
+ var s = X.rows[i];
+ var e = X.rows[i+1];
+ for (var k= s; k < e; k++)
+ C.val[ri + X.cols[k]] = f(X.val[k]);
+ ri += n;
+ }
+ }
+ else {
+ for (i = 0; i < n; i++) {
+ var s = X.cols[i];
+ var e = X.cols[i+1];
+ for (var k= s; k < e; k++)
+ C.val[X.rows[k] * n + i] += f(X.val[k]);
+ }
+ }
+ return C;
+}
+/**
+ * @param{spVector}
+ * @return{number}
+ */
+function sumspVector( a ) {
+ return sumVector(a.val);
+}
+/**
+ * @param{spMatrix}
+ * @return{number}
+ */
+function sumspMatrix( A ) {
+ return sumVector(A.val);
+}
+/**
+ * @param{spMatrix}
+ * @return{Matrix}
+ */
+function sumspMatrixRows( A ) {
+ var res = zeros(A.n);
+ if ( A.rowmajor ) {
+ for ( var k=0; k < A.val.length; k++)
+ res[A.cols[k]] += A.val[k];
+ }
+ else {
+ for ( var i=0; i delta2 && k < n ) {
+ updateP(rhoc/rho_, r);
+ w = mulspMatrixVector(A,p);
+ mu = rhoc / dot(p, w);
+ saxpy( mu, p, x);
+ saxpy( -mu, w, r);
+ rho_ = rhoc;
+ rhoc = dot(r,r);
+ k++;
+ }
+ return x;
+}
+/** Sparse Conjugate gradient normal equation residual method for solving the rectangular system Ax = b
+ * @param{spMatrix}
+ * @param{Float64Array}
+ * @return{Float64Array}
+ */
+function spcgnr ( A, b) {
+/*
+TEST
+A = randnsparse(0.3,10000,1000)
+x = randn(1000)
+b = A*x + 0.01*randn(10000)
+tic()
+xx = cgnr(A,b)
+t1 = toc()
+ee = norm(A*xx - b)
+tic()
+xh=spcgnr(sparse(A), b)
+t2 = toc()
+e = norm(A*xh - b)
+*/
+
+ const n = A.n;
+ const m = A.m;
+
+ var x = randn(n);
+ var r = subVectors(b, mulspMatrixVector(A, x));
+ const TOL = 1e-8;
+ var delta2 = TOL * norm(b);
+ delta2 *= delta2;
+
+ // first iteration:
+ var z = mulspMatrixTransVector(A, r);
+ var rhoc = dot(z,z);
+ var p = vectorCopy(z);
+ var w = mulspMatrixVector(A,p);
+ var mu = rhoc / dot(w, w);
+ saxpy( mu, p, x);
+ saxpy( -mu, w, r);
+ z = mulspMatrixTransVector(A, r);
+ var rho_ = rhoc;
+ rhoc = dot(z,z);
+
+ var k = 1;
+
+ var updateP = function (tau, z) {
+ for ( var i=0; i < m; i++)
+ p[i] = z[i] + tau * p[i];
+ }
+
+ while ( rhoc > delta2 && k < n ) {
+ updateP(rhoc/rho_, z);
+ w = mulspMatrixVector(A,p);
+ mu = rhoc / dot(w, w);
+ saxpy( mu, p, x);
+ saxpy( -mu, w, r);
+ z = mulspMatrixTransVector(A, r);
+ rho_ = rhoc;
+ rhoc = dot(z,z);
+ k++;
+ }
+ return x;
+}
+
+
+/* glpk.js is now included (cat) in lalolib.js
+if ( self.hasOwnProperty("window") ) {
+ // in main window
+}
+else {
+ // in worker
+ importScripts("glpk.js");
+ //importScripts("glpk.min.js");
+}*/
+
+// Install glpk as lp function:
+if ( typeof(lp) == "undefined" ) {
+ lp = glp;
+ linprog = glp;
+}
+
+function glp (c, A, b, Aeq, beq, lb , ub, integer_variables, verbose) {
+/*
+ Call GLPK to solve
+ min c' x s.t. Ax<= b, Aeq = beq, lb<= x <= ub, x[integer_variables] in Z
+*/
+
+/* TESTS:
+Aineq = [[1, 1]; [-1,1]]
+Bineq = [2; 1]
+costineq = [-1; -2]
+lb = [0;0]
+xsol = glp(costineq, Aineq, Bineq, [], [], lb)
+
+A = [[3,2,1,1,0],[2,5,3,0,1]]
+b=[10,15]
+c=[-2,-3,-4,0,0]
+lb = zeros(5)
+xsol = glp(c, [],[],A, b,lb,[])
+
+*/
+ var prob = glp_create_prob();
+ glp_set_obj_dir ( prob, GLP_MIN ) ;
+
+ if ( typeof(Aeq) == "undefined" )
+ var Aeq = [];
+
+ glp_add_cols(prob, c.length);
+ if ( A.length + Aeq.length > 0 )
+ glp_add_rows(prob, A.length + Aeq.length);
+
+ var i;
+ var j;
+ var indexes ;
+ var values;
+ var n = c.length;
+
+ if ( lb ) {
+ var lbdense = vectorCopy(lb);
+ for ( i=0; i < lbdense.length; i++){
+ if ( !isFinite( lbdense[i] ) )
+ lbdense[i] = NaN;
+ }
+ }
+ else
+ var lbdense = [];
+
+ if ( ub ) {
+ var ubdense = vectorCopy(ub);
+ for ( i=0; i < ubdense.length; i++){
+ if ( !isFinite( ubdense[i] ) )
+ lbdense[i] = NaN;
+ }
+ }
+ else
+ var ubdense = [];
+
+ for ( i=0; i < c.length; i++) {
+ // variable bounds
+ var lbi = NaN;
+ var ubi = NaN;
+ if ( lbdense.length > 0)
+ lbi = lbdense[i];
+ if ( ubdense.length > 0 )
+ ubi = ubdense[i] ;
+
+ if ( !isNaN(lbi) && !isNaN(ubi))
+ glp_set_col_bnds( prob, i+1, GLP_DB, lbi , ubi );
+ else if ( !isNaN(lbi) )
+ glp_set_col_bnds( prob, i+1, GLP_LO, lbi );
+ else if ( !isNaN(ubi) )
+ glp_set_col_bnds( prob, i+1, GLP_UP, 0, ubi );
+ else
+ glp_set_col_bnds( prob, i+1, GLP_FR );
+
+ // cost
+ glp_set_obj_coef ( prob, i+1, c[i] );
+
+ }
+
+ // Integer variables
+ if ( integer_variables ) {
+ for ( i=0; i< integer_variables.length ; i++)
+ glp_set_col_kind(prob, integer_variables[i]+1, GLP_IV );
+ }
+
+ // inequalities
+ if ( A.length == 1 && typeof(b) == "number")
+ b = [b];
+ for ( i=0; i 0) {
+ // Solve with MILP solver
+ var iocp = new IOCP({presolve: GLP_ON});
+ glp_scale_prob(prob, GLP_SF_AUTO);
+ rc = glp_intopt(prob, iocp);
+
+ // get solution
+ if ( rc == 0 ) {
+ var sol = zeros(n);
+ for ( i=0; i";});
+ return "RC=" + rc + " ; Status : " + glp_get_status(prob) + "(OPT=" + GLP_OPT + ",FEAS=" + GLP_FEAS + ",INFEAS=" + GLP_INFEAS + ",NOFEAS=" + GLP_NOFEAS + ",UNBND=" + GLP_UNBND + ",UNDEF=" + GLP_UNDEF + ")" ;
+ }
+ }
+
+}
+
+///////////////////////////////:
+/////// L1-minimization and sparse recovery //////////
+///////////
+function minl1 ( A, b) {
+ /*
+ Solves min ||x||_1 s.t. Ax = b
+
+ as
+
+ min sum a_i s.t. -a <= x <= a and Ax = b
+
+ example:
+A = randn(10,20)
+r = zeros(20)
+r[0:3] = randn(3)
+x=minl1(A,A*r)
+
+ */
+ const n = A.n;
+
+ var Aineq = zeros ( 2*n, 2*n ) ;
+ var i;
+
+ //set ( Aineq, range(0,n),range(0,n) , I) ;
+ //set ( Aineq, range(0,n),range(n,2*n) , I_) ;
+ //set ( Aineq, range(n,2*n),range(0,n) , I_) ;
+ //set ( Aineq, range(n,2*n),range(n,2*n) , I_) ;
+ for ( i=0; i < n; i++) {
+ Aineq.val[i*Aineq.n + i] = 1;
+ Aineq.val[i*Aineq.n + n+i] = -1;
+ Aineq.val[(n+i)*Aineq.n + i] = -1;
+ Aineq.val[(n+i)*Aineq.n + n+i] = -1;
+ }
+ var bineq = zeros ( 2*n);
+
+ var Aeq = zeros(A.length, 2*n);
+ set ( Aeq , [], range( 0,n), A );
+
+ var cost = zeros(2*n);
+ set ( cost, range(n,2*n), ones(n) );
+
+ var lb = zeros(2*n); // better to constraint a>=0
+ set ( lb, range(n), mulScalarVector(-Infinity , ones( n )) ) ;
+//console.log( cost, Aineq, bineq, Aeq, b, lb);
+// var lpsol = lp( cost, Aineq, bineq, Aeq, b, lb, [], 0 , 1e-6 );
+ var lpsol = glp( cost, Aineq, bineq, Aeq, b, lb);
+
+ return get(lpsol, range(n) );
+}
+
+
+
+function minl0 ( A, b, M) {
+ /*
+ Solves min ||x||_0 s.t. Ax = b -M <= x <= M
+
+ as a mixed integer linear program
+
+ min sum a_i s.t. -M a <= x <= M a , Ax = b and a_i in {0,1}
+
+ example:
+A = randn(10,20)
+r = zeros(20)
+r[0:3] = randn(3)
+x=minl0(A,A*r)
+
+ */
+
+ if ( typeof(M) == "undefined" )
+ var M = 10;
+
+ var n = A.n;
+
+ var Aineq = zeros ( 2*n, 2*n ) ;
+ //set ( Aineq, range(0,n),range(0,n) , I) ;
+ //set ( Aineq, range(0,n),range(n,2*n) , mul(M, I_) ) ;
+ //set ( Aineq, range(n,2*n),range(0,n) , I_) ;
+ //set ( Aineq, range(n,2*n),range(n,2*n) ,mul(M, I_) ) ;
+ var i;
+ for ( i=0; i < n; i++) {
+ Aineq.val[i*Aineq.n + i] = 1;
+ Aineq.val[i*Aineq.n + n+i] = -M;
+ Aineq.val[(n+i)*Aineq.n + i] = -1;
+ Aineq.val[(n+i)*Aineq.n + n+i] = -M;
+
+ }
+ var bineq = zeros ( 2*n);
+
+ var Aeq = zeros(A.length, 2*n);
+ set ( Aeq , [], range( 0,n), A );
+
+ var cost = zeros(2*n);
+ set ( cost, range(n,2*n), ones(n) );
+
+ var lb = zeros(2*n); // better to constraint a>=0
+ set ( lb, range(n), mulScalarVector(-M , ones( n )) ) ;
+
+ var ub = ones(2*n) ;
+ set(ub, range(n), mulScalarVector(M, ones(n) ) );
+
+ var lpsol = glp( cost, Aineq, bineq, Aeq, b, lb, ub, range(n,2*n) ); // glptweak??
+
+ // set to 0 the x corresponding to 0 binary variables:
+ var x = entrywisemulVector( getSubVector(lpsol, range(n) ), getSubVector(lpsol, range(n,2*n) ) );
+
+ return x;
+}
+
+
+///////////////////////////////////////////
+/// Quadratic Programming
+////////////////
+quadprog = qp;
+
+function qp(Q,c,A,b,Aeq,beq,lb,ub,x0, epsilon) {
+ // Solve quad prog by Frank-Wolfe algorithm
+ /*
+ min 0.5 x' * Q * x c' * x
+ s.t. Ax <= b and lu <= x <= ub
+
+ NOTE: all variables should be bounded or constrained,
+ otherwise the LP might be unbounded even if the QP is well-posed
+ */
+ if (typeof(epsilon) === 'undefined')
+ var epsilon = 1e-3;
+
+ var normdiff;
+ var normgrad;
+ var grad;
+ var y;
+ var gamma;
+ var direction;
+
+ var x;
+ if ( typeof(x0) === 'undefined' ) {
+ //console.log ("providing an initial x0 might be better for qp.");
+ x = glp(zeros(c.length),A, b, Aeq, beq, lb, ub, [], false) ;
+ if ( typeof(x) == "string")
+ return "infeasible";
+ }
+ else {
+ x = vectorCopy(x0);
+ }
+
+ var iter = 0;
+ do {
+
+ // Compute gradient : grad = Qx + c
+ grad = add( mul( Q, x) , c );
+ normgrad = norm(grad);
+
+ // Find direction of desecnt : direction = argmin_y y'*grad s.t. same constraints as QP
+ y = glp(grad, A, b, Aeq, beq, lb, ub, [], false) ;
+/* if ( typeof(y) == "string")
+ return x; // error return current solution;
+ */
+
+ // Step size: gamma = -(y - x)' [ Qx + c] / (y-x)'Q(y-x) = numerator / denominator
+ direction = sub (y, x);
+
+ numerator = - mul(direction, grad);
+
+ denominator = mul(direction, mul(Q, direction) );
+
+ if ( Math.abs(denominator) > 1e-8 && denominator > 0)
+ gamma = numerator / denominator;
+ else
+ gamma = 0;
+
+ if ( gamma > 1 )
+ gamma = 1;
+
+ // Update x <- x + gamma * direction
+ if ( gamma > 0 ) {
+ x = add(x, mul(gamma, direction) );
+ normdiff = gamma * norm(direction) ;
+ }
+ else
+ normdiff = 0;
+
+ iter++;
+ } while ( normdiff > epsilon && normgrad > epsilon && iter < 10000) ;
+
+ return x;
+}
+
+
+
+/////////////////////////////////////////:
+//// Unconstrained Minimization
+/////////////////////////////////////////
+function minimize( f, grad, x0 ) {
+/*
+function loss(x) {
+return (norm(b - A*x)^2)
+}
+function grad(x) {
+return (2*A'*A*x - 2*A'*b)
+}
+x = randn(10)
+A = randn(100,10)
+b = A*x + 0.01*randn(100)
+xh = minimize(A.n, loss, grad)
+norm(x - xh)
+*/
+ var x;
+ var n = 1; // dimension of x
+
+ if ( arguments.length == 3 ) {
+ if ( typeof(x0) == "number" ) {
+ if( x0 > 0 && Math.floor(x0) == x0 ) {
+ n = x0;
+ x = sub(mul(20,rand(n)), 10);
+ }
+ else {
+ n = 1;
+ x = x0;
+ }
+ }
+ else {
+ n = x0.length;
+ x = x0;
+ }
+ }
+ else {
+ n = 1;
+ x = 20 * Math.random() - 10;
+ }
+
+ if ( n == 1 )
+ return secant(f, grad, x);
+ else if ( n > 500 )
+ return steepestdescent(f, grad, x);
+ else
+ return bfgs(f, grad, x);
+}
+
+function secant( f, grad, x0 ) {
+ // for a unidimensional function f
+ // find a root to f'(x) = 0 with the secant method
+ const TOLx = 1e-6;
+
+ var x = x0;
+ var g = grad(x);
+ var dx = -0.01*g;
+ x += dx;
+ var gprev,dg;
+ do {
+ gprev = g;
+ g = grad(x);
+ dg = g-gprev;
+
+ dx *= -g / dg;
+ x += dx;
+
+ } while ( Math.abs(dx) > TOLx);
+ return x;
+}
+
+
+function steepestdescent(f, grad, x0) {
+ // assume x is a vector
+
+ const TOLobj = 1e-8;
+ const TOLx = 1e-6;
+ const TOLgrad = 1e-4;
+
+ var x = x0;
+ var xprev;
+ var obj = f(x);
+ var g = grad(x);
+ var normg = norm(g);
+ var iter = 0;
+ do {
+
+ // line search
+ var linesearch = armijo(f, x, obj, g, normg);
+
+ // take the step
+ xprev = vectorCopy(x);
+ prevobj = obj;
+ x = linesearch.x;
+ obj = linesearch.obj;
+ g = grad(x);
+ normg = norm(g);
+
+ iter++;
+ //console.log(linesearch.lambda, x, obj, g);
+ } while ( normg > TOLgrad && prevobj - obj > TOLobj && norm(subVectors(x, xprev) ) > TOLx ) ;
+ console.log(" OBJ: " + obj + ", norm(grad): " + normg, "prevobj - obj", prevobj - obj, "iter: ", iter );
+ return x;
+}
+
+function bfgs( f, grad, x0 ) {
+ // assume x is a vector
+
+ const n = x0.length;
+ const TOLobj = 1e-8;
+ const TOLx = 1e-6;
+ const TOLgrad = 1e-4;
+
+ var x = x0;
+ var xprev;
+ var obj = f(x);
+ var H = eye(n);
+ var g,direction, delta, gamma, ls;
+ var normg;
+ var Hgamma;
+ var dH;
+ var iter = 0;
+ do {
+ g = grad(x);
+ normg = norm(g);
+ direction = minusVector( mulMatrixVector(H, g ) );
+
+ // line search
+ var linesearch = armijodir (f, x, obj, g, direction );
+
+ // take the step
+ xprev = vectorCopy(x);
+ prevobj = obj;
+ x = linesearch.x;
+ obj = linesearch.obj;
+
+ // update Hessian inverse approximation
+ delta = subVectors(x,xprev);
+ gamma = subVectors(grad(x) , g);
+
+ Hgamma = mulMatrixVector(H, gamma);
+
+ var deltagamma = dot(delta,gamma);
+ var delta_ = mulScalarVector(1/deltagamma, delta);
+
+ var deltagammaH = outerprodVectors(delta_, Hgamma);
+
+ dH = subMatrices(outerprodVectors(delta_, delta, 1+ dot(gamma, Hgamma)/deltagamma) , addMatrices(deltagammaH, transposeMatrix(deltagammaH) ) );
+ //--
+
+ H = add(H, dH);
+
+ iter++;
+
+ } while ( normg > TOLgrad && prevobj - obj > TOLobj && norm(subVectors(x, xprev) ) > TOLx ) ;
+ console.log(" OBJ: " + obj + ", norm(grad): " + normg, "prevobj - obj", prevobj - obj, "iters: ", iter );
+ return x;
+}
+
+
+/**
+ * Return minimizer of p(x) = p0 + p1 x + p2 x^2 + p3 x^3 with p(x1) = px1, p(x2) = px2
+ * within [lb, ub]
+ *
+ * @param {number}
+ * @param {number}
+ * @param {number}
+ * @param {number}
+ * @param {number}
+ * @param {number}
+ * @param {number}
+ * @param {number}
+ * @return {number}
+ */
+function mincubic(p0, p1, x1, px1, x2, px2, lb, ub) {
+
+ const x1square = x1*x1;
+ const x2square = x2*x2;
+
+ var A = new Matrix(2,2, [x1square, x1*x1square, x2square, x2*x2square]);
+ var b = new Float64Array([px1 - p0 - p1*x1, px2 - p0 - p1*x2]);
+ var c = solve(A,b);
+ var x = (-c[0] + Math.sqrt(c[0]*c[0] - 3 *c[1] * p1))/(3*c[1]);
+
+ return Math.min(ub, Math.max(lb, x));
+}
+/**
+ * Return minimizer of p(x) = p0 + p1 x + p2 x^2 with p(x1) = px1 (x1 ~ 1)
+ * within [lb, ub]
+ *
+ * @param {number}
+ * @param {number}
+ * @param {number}
+ * @param {number}
+ * @param {number}
+ * @param {number}
+ * @return {number}
+ */
+function minquadratic(p0, p1, px1, x1, lb, ub) {
+ var x = - p1/(2 * x1 * (px1 - p0 - p1) );
+ return Math.min(ub, Math.max(lb, x));
+}
+
+/**
+ * Armijo line search with objective function f
+ * and starting point xc, fc, g
+ *
+ * @param {function}
+ * @param {{Float64Array|number}}
+ * @param {number}
+ * @param {{Float64Array|number}}
+ * @param {number}
+ * @return {{Float64Array|number}}
+ */
+function armijo (f, xc, fc, g, normg ) {
+ // Armijo's rule line search in the direction of gradient g
+ const alpha = 0.0001;
+ const blow = 0.1;
+ const bhigh = 0.5;
+ const normg2 = normg * normg;
+
+ var lambda = Math.min(1,100/(1+normg));
+ var fgoal = fc - alpha * lambda * normg2;
+
+ var lambda1 = lambda;
+ var xt = subVectors(xc, mulScalarVector(lambda, g) );
+ var ft_1 = fc;
+ var ft = f(xt);
+
+ var iter = 1;
+
+ // first iter
+ lambda = minquadratic(fc, -normg2, lambda1, ft, blow*lambda1, bhigh*lambda1);
+ var ft_1 = ft;
+ var lambda2 = lambda1;
+ lambda1 = lambda;
+
+ iter++;
+ // next iterations
+ while(ft > fgoal && iter <= 10) {
+
+ lambda = mincubic(fc, -normg2, lambda1, ft, lambda2, ft_1, blow*lambda1, bhigh*lambda1);
+ lambda2 = lambda1;
+ lambda1 = lambda;
+
+ xt = subVectors(xc, mulScalarVector(lambda, g) );
+ ft_1 = ft;
+ ft = f(xt);
+
+ fgoal = fc - alpha * lambda * normg2;
+
+ iter++;
+ }
+ return {"lambda": lambda, "x": xt, "obj": ft};
+}
+function armijodir (f, xc, fc, g, d ) {
+ // Armijo's rule line search in the direction d
+ const alpha = 0.0001;
+ const blow = 0.1;
+ const bhigh = 0.5;
+ const p1 = dot( g, d);
+
+ var lambda = Math.min(1,100/(1+norm(g)));
+ var fgoal = fc + alpha * lambda * p1;
+
+ var lambda1 = lambda;
+ var xt = addVectors(xc, mulScalarVector(lambda, d) );
+ var ft_1 = fc;
+ var ft = f(xt);
+
+ var iter = 1;
+
+ // first iter
+ lambda = minquadratic(fc, p1, lambda1, ft, blow*lambda1, bhigh*lambda1);
+ var ft_1 = ft;
+ var lambda2 = lambda1;
+ lambda1 = lambda;
+
+ iter++;
+ // next iterations
+ while(ft > fgoal && iter <= 10) {
+
+ lambda=mincubic(fc, p1, lambda1, ft, lambda2, ft_1, blow*lambda1, bhigh*lambda1 );
+ lambda2 = lambda1;
+ lambda1 = lambda;
+
+ xt = addVectors(xc, mulScalarVector(lambda, d) );
+ ft_1 = ft;
+ ft = f(xt);
+
+ fgoal = fc + alpha * lambda * p1;
+
+ iter++;
+ }
+ return {"lambda": lambda, "x": xt, "obj": ft};
+}
+
+/*! glpk.js - v4.49.0
+* https://github.com/hgourvest/glpk.js
+* Copyright (c) 2013 Henri Gourvest; Licensed GPLv2 */
+(function(exports) {
+var s=Number.MAX_VALUE,aa=Number.MIN_VALUE;function w(a){throw Error(a);}function x(){}exports.glp_get_print_func=function(){return x};exports.glp_set_print_func=function(a){x=a};function da(a,b){for(var c in b)a[c]=b[c]}function ga(a,b,c,d,e){for(;0=a||127==a}function ua(a){a="string"==typeof a?a.charCodeAt(0):-1;return 65<=a&&90>=a||97<=a&&122>=a}function va(a){a="string"==typeof a?a.charCodeAt(0):-1;return 65<=a&&90>=a||97<=a&&122>=a||48<=a&&57>=a}function wa(a){a="string"==typeof a?a.charCodeAt(0):-1;return 48<=a&&57>=a}
+function xa(){function a(a,d,e,k,l,p,m){a>>>=0;e=e&&a&&{2:"0b",8:"0",16:"0x"}[d]||"";a=e+c(a.toString(d),p||0,"0",!1);return b(a,e,k,l,m)}function b(a,b,d,e,l,p){var m=e-a.length;0=b?"":Array(1+b-a.length>>>0).join(c);return d?a+b:b+a}var d=arguments,e=0;return d[e++].replace(/%%|%(\d+\$)?([-+\'#0 ]*)(\*\d+\$|\*|\d+)?(\.(\*\d+\$|\*|\d+))?([scboxXuideEfFgG])/g,function(f,
+g,h,k,l,p,m){var q,r;if("%%"==f)return"%";var n=!1;r="";var t=l=!1;q=" ";for(var y=h.length,E=0;h&&Ek&&(k=-k,n=!0);if(!isFinite(k))throw Error("sprintf: (minimum-)width must be finite");p=p?"*"==p?+d[e++]:"*"==p.charAt(0)?+d[p.slice(1,-1)]:+p:-1<"fFeE".indexOf(m)?6:"d"==m?0:void 0;g=g?
+d[g.slice(0,-1)]:d[e++];switch(m){case "s":return m=String(g),null!=p&&(m=m.slice(0,p)),b(m,"",n,k,l,q);case "c":return m=String.fromCharCode(+g),null!=p&&(m=m.slice(0,p)),b(m,"",n,k,l,void 0);case "b":return a(g,2,t,n,k,p,l);case "o":return a(g,8,t,n,k,p,l);case "x":return a(g,16,t,n,k,p,l);case "X":return a(g,16,t,n,k,p,l).toUpperCase();case "u":return a(g,10,t,n,k,p,l);case "i":case "d":return q=+g||0,q=Math.round(q-q%1),f=0>q?"-":r,g=f+c(String(Math.abs(q)),p,"0",!1),b(g,f,n,k,l);case "e":case "E":case "f":case "F":case "g":case "G":return q=
++g,f=0>q?"-":r,r=["toExponential","toFixed","toPrecision"]["efg".indexOf(m.toLowerCase())],m=["toString","toUpperCase"]["eEfFgG".indexOf(m)%2],g=f+Math.abs(q)[r](p),b(g,f,n,k,l)[m]();default:return f}})}function ya(a){a.Dd=3621377730;a.ke=null;a.V=null;a.name=null;a.eb=null;a.dir=za;a.ha=0;a.hb=100;a.K=200;a.g=a.i=0;a.L=0;a.n=Array(1+a.hb);a.f=Array(1+a.K);a.fc={};a.Lc={};a.valid=0;a.head=new Int32Array(1+a.hb);a.Qd=null;a.U=null;a.na=a.sa=Aa;a.aa=0;a.$=0;a.some=0;a.df=Aa;a.ae=0;a.za=Aa;a.ta=0}
+var Ba=exports.glp_create_prob=function(){var a={};ya(a);return a},Ca=exports.glp_set_prob_name=function(a,b){var c=a.V;null!=c&&0!=c.reason&&w("glp_set_prob_name: operation not allowed");a.name=b},Da=exports.glp_set_obj_name=function(a,b){var c=a.V;null!=c&&0!=c.reason&&w("glp_set_obj_name: operation not allowed");a.eb=b},Fa=exports.glp_set_obj_dir=function(a,b){var c=a.V;null!=c&&0!=c.reason&&w("glp_set_obj_dir: operation not allowed");b!=za&&b!=Ea&&w("glp_set_obj_dir: dir = "+b+"; invalid direction flag");
+a.dir=b},La=exports.glp_add_rows=function(a,b){var c=a.V,d;1>b&&w("glp_add_rows: nrs = "+b+"; invalid number of rows");b>1E8-a.g&&w("glp_add_rows: nrs = "+b+"; too many rows");var e=a.g+b;if(a.hbb&&w("glp_add_cols: ncs = "+b+"; invalid number of columns");b>1E8-a.i&&w("glp_add_cols: ncs = "+b+"; too many columns");var d=a.i+b;if(a.K5E8-a.L&&w("glp_set_mat_row: i = "+b+"; len = "+c+"; too many constraint coefficients");for(h=1;h<=c;h++)g=d[h],1<=g&&g<=a.i||w("glp_set_mat_row: i = "+b+"; ind["+h+"] = "+g+"; column index out of range"),f=a.f[g],null!=f.k&&f.k.n.ea==b&&w("glp_set_mat_row: i = "+b+"; ind["+h+"] = "+g+"; duplicate column indices not allowed"),g={},a.L++,g.n=k,g.f=f,g.j=e[h],g.ua=null,g.B=k.k,g.ra=null,g.I=f.k,null!=g.B&&(g.B.ua=g),null!=g.I&&(g.I.ra=g),k.k=f.k=g,f.m==A&&0!=g.j&&(a.valid=
+0);for(g=k.k;null!=g;g=b)b=g.B,0==g.j&&(null==g.ua?k.k=b:g.ua.B=b,null!=b&&(b.ua=g.ua),g.f.k=g.I,null!=g.I&&(g.I.ra=null),a.L--)},Za=exports.glp_set_mat_col=function(a,b,c,d,e){var f=a.V,g,h,k;null!=f&&0!=f.reason&&w("glp_set_mat_col: operation not allowed");1<=b&&b<=a.i||w("glp_set_mat_col: j = "+b+"; column number out of range");for(f=a.f[b];null!=f.k;)h=f.k,f.k=h.I,g=h.n,null==h.ua?g.k=h.B:h.ua.B=h.B,null!=h.B&&(h.B.ua=h.ua),a.L--;0<=c&&c<=a.g||w("glp_set_mat_col: j = "+b+"; len = "+c+"; invalid column length");
+c>5E8-a.L&&w("glp_set_mat_col: j = "+b+"; len = "+c+"; too many constraint coefficients");for(k=1;k<=c;k++)h=d[k],1<=h&&h<=a.g||w("glp_set_mat_col: j = "+b+"; ind["+k+"] = "+h+"; row index out of range"),g=a.n[h],null!=g.k&&g.k.f.C==b&&w("glp_set_mat_col: j = "+b+"; ind["+k+"] = "+h+"; duplicate row indices not allowed"),h={},a.L++,h.n=g,h.f=f,h.j=e[k],h.ua=null,h.B=g.k,h.ra=null,h.I=f.k,null!=h.B&&(h.B.ua=h),null!=h.I&&(h.I.ra=h),g.k=f.k=h;for(h=f.k;null!=h;h=b)b=h.I,0==h.j&&(h.n.k=h.B,null!=h.B&&
+(h.B.ua=null),null==h.ra?f.k=b:h.ra.I=b,null!=b&&(b.ra=h.ra),a.L--);f.m==A&&(a.valid=0)};
+exports.glp_load_matrix=function(a,b,c,d,e){var f=a.V,g,h,k,l;null!=f&&0!=f.reason&&w("glp_load_matrix: operation not allowed");for(k=1;k<=a.g;k++)for(f=a.n[k];null!=f.k;)h=f.k,f.k=h.B,a.L--;for(h=1;h<=a.i;h++)a.f[h].k=null;0>b&&w("glp_load_matrix: ne = "+b+"; invalid number of constraint coefficients");5E8a&&w("glp_check_dup: m = %d; invalid parameter");0>b&&w("glp_check_dup: n = %d; invalid parameter");0>c&&w("glp_check_dup: ne = %d; invalid parameter");0=c&&w("glp_set_rii: i = "+b+"; rii = "+c+"; invalid scale factor");if(a.valid&&a.n[b].ma!=c)for(var d=a.n[b].k;null!=d;d=d.B)if(d.f.m==A){a.valid=0;break}a.n[b].ma=c},Ab=exports.glp_set_sjj=function(a,b,c){1<=b&&b<=a.i||w("glp_set_sjj: j = "+b+"; column number out of range");0>=c&&w("glp_set_sjj: j = "+b+"; sjj = "+c+"; invalid scale factor");a.valid&&a.f[b].va!=c&&a.f[b].m==A&&(a.valid=0);a.f[b].va=c},Cb=exports.glp_get_rii=
+function(a,b){1<=b&&b<=a.g||w("glp_get_rii: i = "+b+"; row number out of range");return a.n[b].ma},Db=exports.glp_get_sjj=function(a,b){1<=b&&b<=a.i||w("glp_get_sjj: j = "+b+"; column number out of range");return a.f[b].va},Eb=exports.glp_unscale_prob=function(a){var b=kb(a),c=lb(a),d;for(d=1;d<=b;d++)zb(a,d,1);for(b=1;b<=c;b++)Ab(a,b,1)},Fb=exports.glp_set_row_stat=function(a,b,c){1<=b&&b<=a.g||w("glp_set_row_stat: i = "+b+"; row number out of range");c!=A&&c!=G&&c!=Ua&&c!=Ra&&c!=Na&&w("glp_set_row_stat: i = "+
+b+"; stat = "+c+"; invalid status");b=a.n[b];if(c!=A)switch(b.type){case Ka:c=Ra;break;case Sa:c=G;break;case Ta:c=Ua;break;case I:c!=Ua&&(c=G);break;case B:c=Na}if(b.m==A&&c!=A||b.m!=A&&c==A)a.valid=0;b.m=c},Gb=exports.glp_set_col_stat=function(a,b,c){1<=b&&b<=a.i||w("glp_set_col_stat: j = "+b+"; column number out of range");c!=A&&c!=G&&c!=Ua&&c!=Ra&&c!=Na&&w("glp_set_col_stat: j = "+b+"; stat = "+c+"; invalid status");b=a.f[b];if(c!=A)switch(b.type){case Ka:c=Ra;break;case Sa:c=G;break;case Ta:c=
+Ua;break;case I:c!=Ua&&(c=G);break;case B:c=Na}if(b.m==A&&c!=A||b.m!=A&&c==A)a.valid=0;b.m=c},Hb=exports.glp_std_basis=function(a){var b;for(b=1;b<=a.g;b++)Fb(a,b,A);for(b=1;b<=a.i;b++){var c=a.f[b];c.type==I&&Math.abs(c.c)>Math.abs(c.d)?Gb(a,b,Ua):Gb(a,b,G)}},sc=exports.glp_simplex=function(a,b){function c(a,b){var c;if(!Ib(a)&&(c=Jb(a),0!=c&&(c==Kb?b.o>=Lb&&x("glp_simplex: initial basis is invalid"):c==Mb?b.o>=Lb&&x("glp_simplex: initial basis is singular"):c==Nb&&b.o>=Lb&&x("glp_simplex: initial basis is ill-conditioned")),
+0!=c))return c;b.cb==Ob?c=Pb(a,b):b.cb==Qb?(c=Rb(a,b),c==Sb&&a.valid&&(c=Pb(a,b))):b.cb==Tb&&(c=Rb(a,b));return c}function d(a,b){function d(){Ub(e,f);f=null;Vb(e,a);return r=0}var e,f=null,g={},r;b.o>=Wb&&x("Preprocessing...");e=Xb();Yb(e,a,Zb);r=$b(e,0);0!=r&&(r==ac?b.o>=Wb&&x("PROBLEM HAS NO PRIMAL FEASIBLE SOLUTION"):r==bc&&b.o>=Wb&&x("PROBLEM HAS NO DUAL FEASIBLE SOLUTION"));if(0!=r)return r;f=Ba();cc(e,f);if(0==f.g&&0==f.i)return f.na=f.sa=dc,f.aa=f.ha,b.o>=fc&&0==b.fb&&x(a.$+": obj = "+f.aa+
+" infeas = 0.0"),b.o>=Wb&&x("OPTIMAL SOLUTION FOUND BY LP PREPROCESSOR"),d();b.o>=Wb&&x(f.g+" row"+(1==f.g?"":"s")+", "+f.i+" column"+(1==f.i?"":"s")+", "+f.L+" non-zero"+(1==f.L?"":"s")+"");eb(a,g);fb(f,g);var g=na,n=g.Fb;g.Fb=!n||b.o=Lb&&x("glp_simplex: unable to recover undefined or non-optimal solution"),0==r&&(f.na==jc?r=ac:f.sa==jc&&(r=bc)),r):d()}function e(a,
+b){function c(){f.m=G;f.r=f.c}function d(){f.m=Ua;f.r=f.d}var e,f,g,n,t;a.valid=0;a.na=a.sa=dc;a.aa=a.ha;n=t=a.some=0;for(g=1;g<=a.g;g++){e=a.n[g];e.m=A;e.r=e.J=0;if(e.type==Sa||e.type==I||e.type==B)e.c>+b.Gb&&(a.na=jc,0==a.some&&b.cb!=Ob&&(a.some=g)),n<+e.c&&(n=+e.c);if(e.type==Ta||e.type==I||e.type==B)e.d<-b.Gb&&(a.na=jc,0==a.some&&b.cb!=Ob&&(a.some=g)),n<-e.d&&(n=-e.d)}for(e=g=1;e<=a.i;e++)f=a.f[e],gg*f.u?d():Math.abs(f.c)<=Math.abs(f.d)?c():d():f.type==B&&(f.m=Na,f.r=f.c);f.J=f.u;a.aa+=f.u*f.r;if(f.type==Ka||f.type==Sa)g*f.J<-b.tb&&(a.sa=jc,0==a.some&&b.cb==Ob&&(a.some=a.g+e)),t<-g*f.J&&(t=-g*f.J);if(f.type==Ka||f.type==Ta)g*f.J>+b.tb&&(a.sa=jc,0==a.some&&b.cb==Ob&&(a.some=a.g+e)),t<+g*f.J&&(t=+g*f.J)}b.o>=fc&&0==b.fb&&x("~"+a.$+": obj = "+a.aa+" infeas = "+(b.cb==Ob?n:t)+"");b.o>=Wb&&0==b.fb&&(a.na==dc&&a.sa==dc?x("OPTIMAL SOLUTION FOUND"):
+a.na==jc?x("PROBLEM HAS NO FEASIBLE SOLUTION"):b.cb==Ob?x("PROBLEM HAS UNBOUNDED SOLUTION"):x("PROBLEM HAS NO DUAL FEASIBLE SOLUTION"))}var f;null!=a&&3621377730==a.Dd||w("glp_simplex: P = "+a+"; invalid problem object");null!=a.V&&0!=a.V.reason&&w("glp_simplex: operation not allowed");null==b&&(b=new kc);b.o!=lc&&b.o!=Lb&&b.o!=fc&&b.o!=Wb&&b.o!=mc&&w("glp_simplex: msg_lev = "+b.o+"; invalid parameter");b.cb!=Ob&&b.cb!=Qb&&b.cb!=Tb&&w("glp_simplex: meth = "+b.cb+"; invalid parameter");b.fd!=nc&&b.fd!=
+oc&&w("glp_simplex: pricing = "+b.fd+"; invalid parameter");b.ne!=pc&&b.ne!=qc&&w("glp_simplex: r_test = "+b.ne+"; invalid parameter");0b.Gb||w("glp_simplex: tol_bnd = "+b.Gb+"; invalid parameter");0b.tb||w("glp_simplex: tol_dj = "+b.tb+"; invalid parameter");0b.xe||w("glp_simplex: tol_piv = "+b.xe+"; invalid parameter");0>b.oc&&w("glp_simplex: it_lim = "+b.oc+"; invalid parameter");0>b.sb&&w("glp_simplex: tm_lim = "+b.sb+"; invalid parameter");1>b.bc&&w("glp_simplex: out_frq = "+
+b.bc+"; invalid parameter");0>b.fb&&w("glp_simplex: out_dly = "+b.fb+"; invalid parameter");b.yc!=bb&&b.yc!=cb&&w("glp_simplex: presolve = "+b.yc+"; invalid parameter");a.na=a.sa=Aa;a.aa=0;a.some=0;for(f=1;f<=a.g;f++){var g=a.n[f];if(g.type==I&&g.c>=g.d)return b.o>=Lb&&x("glp_simplex: row "+f+": lb = "+g.c+", ub = "+g.d+"; incorrect bounds"),f=rc}for(f=1;f<=a.i;f++)if(g=a.f[f],g.type==I&&g.c>=g.d)return b.o>=Lb&&x("glp_simplex: column "+f+": lb = "+g.c+", ub = "+g.d+"; incorrect bounds"),f=rc;b.o>=
+Wb&&(x("GLPK Simplex Optimizer, v"+ra()+""),x(a.g+" row"+(1==a.g?"":"s")+", "+a.i+" column"+(1==a.i?"":"s")+", "+a.L+" non-zero"+(1==a.L?"":"s")+""));0==a.L?(e(a,b),f=0):f=b.yc?d(a,b):c(a,b);return f},kc=exports.SMCP=function(a){a=a||{};this.o=a.msg_lev||Wb;this.cb=a.meth||Ob;this.fd=a.pricing||oc;this.ne=a.r_test||qc;this.Gb=a.tol_bnd||1E-7;this.tb=a.tol_dj||1E-7;this.xe=a.tol_piv||1E-10;this.hf=a.obj_ll||-s;this.jf=a.obj_ul||+s;this.oc=a.it_lim||2147483647;this.sb=a.tm_lim||2147483647;this.bc=a.out_frq||
+500;this.fb=a.out_dly||0;this.yc=a.presolve||cb},xc=exports.glp_get_status=function(a){var b;b=tc(a);switch(b){case dc:switch(uc(a)){case dc:b=vc;break;case jc:b=wc}}return b},tc=exports.glp_get_prim_stat=function(a){return a.na},uc=exports.glp_get_dual_stat=function(a){return a.sa},yc=exports.glp_get_obj_val=function(a){return a.aa},zc=exports.glp_get_row_stat=function(a,b){1<=b&&b<=a.g||w("glp_get_row_stat: i = "+b+"; row number out of range");return a.n[b].m},Ac=exports.glp_get_row_prim=function(a,
+b){1<=b&&b<=a.g||w("glp_get_row_prim: i = "+b+"; row number out of range");return a.n[b].r},Bc=exports.glp_get_row_dual=function(a,b){1<=b&&b<=a.g||w("glp_get_row_dual: i = "+b+"; row number out of range");return a.n[b].J},Cc=exports.glp_get_col_stat=function(a,b){1<=b&&b<=a.i||w("glp_get_col_stat: j = "+b+"; column number out of range");return a.f[b].m},Dc=exports.glp_get_col_prim=function(a,b){1<=b&&b<=a.i||w("glp_get_col_prim: j = "+b+"; column number out of range");return a.f[b].r},Ec=exports.glp_get_col_dual=
+function(a,b){1<=b&&b<=a.i||w("glp_get_col_dual: j = "+b+"; column number out of range");return a.f[b].J};exports.glp_get_unbnd_ray=function(a){var b=a.some;b>a.g+a.i&&(b=0);return b};
+var Hc=exports.glp_set_col_kind=function(a,b,c){1<=b&&b<=a.i||w("glp_set_col_kind: j = "+b+"; column number out of range");var d=a.f[b];switch(c){case Ma:d.kind=Ma;break;case Fc:d.kind=Fc;break;case Gc:d.kind=Fc;d.type==I&&0==d.c&&1==d.d||Wa(a,b,I,0,1);break;default:w("glp_set_col_kind: j = "+b+"; kind = "+c+"; invalid column kind")}},Ic=exports.glp_get_col_kind=function(a,b){1<=b&&b<=a.i||w("glp_get_col_kind: j = "+b+"; column number out of range");var c=a.f[b],d=c.kind;switch(d){case Fc:c.type==
+I&&0==c.c&&1==c.d&&(d=Gc)}return d},Jc=exports.glp_get_num_int=function(a){for(var b,c=0,d=1;d<=a.i;d++)b=a.f[d],b.kind==Fc&&c++;return c},Kc=exports.glp_get_num_bin=function(a){for(var b,c=0,d=1;d<=a.i;d++)b=a.f[d],b.kind==Fc&&b.type==I&&0==b.c&&1==b.d&&c++;return c};
+exports.glp_intopt=function(a,b){function c(a,b){var c;if(xc(a)!=vc)return b.o>=Lb&&x("glp_intopt: optimal basis to initial LP relaxation not provided"),c=Lc;b.o>=Wb&&x("Integer optimization begins...");var d=a.g;c=a.i;var e,f;a.V=e={};e.i=c;e.wc=d;e.ac=new Int8Array(1+d+c);e.bd=new Float64Array(1+d+c);e.cd=new Float64Array(1+d+c);e.mf=new Int8Array(1+d+c);e.lf=new Float64Array(1+d+c);e.kf=new Float64Array(1+d+c);for(f=1;f<=d;f++){var q=a.n[f];e.ac[f]=q.type;e.bd[f]=q.c;e.cd[f]=q.d;e.mf[f]=q.m;e.lf[f]=
+q.r;e.kf[f]=q.J}for(f=1;f<=c;f++)q=a.f[f],e.ac[d+f]=q.type,e.bd[d+f]=q.c,e.cd[d+f]=q.d,e.mf[d+f]=q.m,e.lf[d+f]=q.r,e.kf[d+f]=q.J;e.ih=a.aa;e.fe=0;e.Sc=0;e.ya=null;e.head=e.Xa=null;e.Pd=e.Zf=e.Jg=0;e.Ig=0;e.se=null;e.qe=e.te=null;e.re=null;e.N=null;e.A=a;e.ad=new Int8Array(1+c);e.Fg=e.Gg=0;e.qf=null;e.of=e.rf=null;e.pf=null;d={size:0};d.head=d.Xa=null;d.fh=0;d.N=null;e.Bd=d;e.Xf=null;e.Ne=null;e.Fd=null;e.Bg=new Int32Array(1+c);e.Tg=new Float64Array(1+c);e.p=b;e.hc=ja();e.Lg=0;e.qh=0;e.reason=0;e.pe=
+0;e.tf=0;e.Tc=0;e.Jf=0;e.ud=0;e.gf=0;e.stop=0;Mc(e,null);c=Nc(e);var d=e.A,r=d.g;f=d.i;if(r!=e.wc){var n,r=r-e.wc;n=new Int32Array(1+r);for(q=1;q<=r;q++)n[q]=e.wc+q;ab(d,r,n)}r=e.wc;for(q=1;q<=r;q++)Va(d,q,e.ac[q],e.bd[q],e.cd[q]),Fb(d,q,e.mf[q]),d.n[q].r=e.lf[q],d.n[q].J=e.kf[q];for(q=1;q<=f;q++)Wa(d,q,e.ac[r+q],e.bd[r+q],e.cd[r+q]),Gb(d,q,e.mf[r+q]),d.f[q].r=e.lf[r+q],d.f[q].J=e.kf[r+q];d.na=d.sa=dc;d.aa=e.ih;Oc(e.Bd);d.V=null;0==c?a.za==dc?(b.o>=Wb&&x("INTEGER OPTIMAL SOLUTION FOUND"),a.za=vc):
+(b.o>=Wb&&x("PROBLEM HAS NO INTEGER FEASIBLE SOLUTION"),a.za=jc):c==Pc?b.o>=Wb&&x("RELATIVE MIP GAP TOLERANCE REACHED; SEARCH TERMINATED"):c==Qc?b.o>=Wb&&x("TIME LIMIT EXCEEDED; SEARCH TERMINATED"):c==Sb?b.o>=Lb&&x("glp_intopt: cannot solve current LP relaxation"):c==Rc&&b.o>=Wb&&x("SEARCH TERMINATED BY APPLICATION");return c}function d(a,b){function d(){Ub(m,q);q=null;Vb(m,a);return n}var e=na,f=e.Fb,m,q=null,r={},n;b.o>=Wb&&x("Preprocessing...");m=Xb();Yb(m,a,Sc);e.Fb=!f||b.o=Wb&&x("PROBLEM HAS NO PRIMAL FEASIBLE SOLUTION"):n==bc&&b.o>=Wb&&x("LP RELAXATION HAS NO DUAL FEASIBLE SOLUTION"));if(0!=n)return n;q=Ba();cc(m,q);if(0==q.g&&0==q.i)return q.za=vc,q.ta=q.ha,b.o>=Wb&&(x("Objective value = "+q.ta+""),x("INTEGER OPTIMAL SOLUTION FOUND BY MIP PREPROCESSOR")),d();if(b.o>=Wb){var t=Jc(q),y=Kc(q);x(q.g+" row"+(1==q.g?"":"s")+", "+q.i+" column"+(1==q.i?"":"s")+", "+q.L+" non-zero"+(1==q.L?"":"s")+"");x(t+" integer variable"+(1==t?"":"s")+", "+(0==
+y?"none of":1==t&&1==y?"":1==y?"one of":y==t?"all of":y+" of")+" which "+(1==y?"is":"are")+" binary")}eb(a,r);fb(q,r);e.Fb=!f||b.o=Wb&&x("Solving LP relaxation...");e=new kc;e.o=b.o;q.$=a.$;n=sc(q,e);a.$=q.$;if(0!=n)return b.o>=Lb&&x("glp_intopt: cannot solve LP relaxation"),n=Sb;n=xc(q);n==vc?n=0:n==jc?n=ac:n==wc&&(n=bc);if(0!=n)return n;q.$=a.$;n=c(q,b);a.$=q.$;return q.za!=vc&&q.za!=dc?(a.za=q.za,n):d()}var e,f;null!=a&&
+3621377730==a.Dd||w("glp_intopt: P = "+a+"; invalid problem object");null!=a.V&&w("glp_intopt: operation not allowed");null==b&&(b=new Yc);b.o!=lc&&b.o!=Lb&&b.o!=fc&&b.o!=Wb&&b.o!=mc&&w("glp_intopt: msg_lev = "+b.o+"; invalid parameter");b.Jb!=Zc&&b.Jb!=$c&&b.Jb!=ad&&b.Jb!=bd&&b.Jb!=cd&&w("glp_intopt: br_tech = "+b.Jb+"; invalid parameter");b.kc!=dd&&b.kc!=ed&&b.kc!=fd&&b.kc!=gd&&w("glp_intopt: bt_tech = "+b.kc+"; invalid parameter");0b.Ub||w("glp_intopt: tol_int = "+b.Ub+"; invalid parameter");
+0b.we||w("glp_intopt: tol_obj = "+b.we+"; invalid parameter");0>b.sb&&w("glp_intopt: tm_lim = "+b.sb+"; invalid parameter");0>b.bc&&w("glp_intopt: out_frq = "+b.bc+"; invalid parameter");0>b.fb&&w("glp_intopt: out_dly = "+b.fb+"; invalid parameter");0<=b.Me&&256>=b.Me||w("glp_intopt: cb_size = "+b.Me+"; invalid parameter");b.ed!=hd&&b.ed!=id&&b.ed!=jd&&w("glp_intopt: pp_tech = "+b.ed+"; invalid parameter");0>b.ce&&w("glp_intopt: mip_gap = "+b.ce+"; invalid parameter");b.Ed!=bb&&b.Ed!=cb&&
+w("glp_intopt: mir_cuts = "+b.Ed+"; invalid parameter");b.Ad!=bb&&b.Ad!=cb&&w("glp_intopt: gmi_cuts = "+b.Ad+"; invalid parameter");b.xd!=bb&&b.xd!=cb&&w("glp_intopt: cov_cuts = "+b.xd+"; invalid parameter");b.vd!=bb&&b.vd!=cb&&w("glp_intopt: clq_cuts = "+b.vd+"; invalid parameter");b.yc!=bb&&b.yc!=cb&&w("glp_intopt: presolve = "+b.yc+"; invalid parameter");b.sd!=bb&&b.sd!=cb&&w("glp_intopt: binarize = "+b.sd+"; invalid parameter");b.Xe!=bb&&b.Xe!=cb&&w("glp_intopt: fp_heur = "+b.Xe+"; invalid parameter");
+a.za=Aa;a.ta=0;for(e=1;e<=a.g;e++)if(f=a.n[e],f.type==I&&f.c>=f.d)return b.o>=Lb&&x("glp_intopt: row "+e+": lb = "+f.c+", ub = "+f.d+"; incorrect bounds"),e=rc;for(e=1;e<=a.i;e++)if(f=a.f[e],f.type==I&&f.c>=f.d)return b.o>=Lb&&x("glp_intopt: column "+e+": lb = "+f.c+", ub = "+f.d+"; incorrect bounds"),e=rc;for(e=1;e<=a.i;e++)if(f=a.f[e],f.kind==Fc){if((f.type==Sa||f.type==I)&&f.c!=Math.floor(f.c))return b.o>=Lb&&x("glp_intopt: integer column "+e+" has non-integer lower bound "+f.c+""),e=rc;if((f.type==
+Ta||f.type==I)&&f.d!=Math.floor(f.d))return b.o>=Lb&&x("glp_intopt: integer column "+e+" has non-integer upper bound "+f.d+""),e=rc;if(f.type==B&&f.c!=Math.floor(f.c))return b.o>=Lb&&x("glp_intopt: integer column "+e+" has non-integer fixed value "+f.c+""),e=rc}b.o>=Wb&&(e=Jc(a),f=Kc(a),x("GLPK Integer Optimizer, v"+ra()+""),x(a.g+" row"+(1==a.g?"":"s")+", "+a.i+" column"+(1==a.i?"":"s")+", "+a.L+" non-zero"+(1==a.L?"":"s")+""),x(e+" integer variable"+(1==e?"":"s")+", "+(0==f?"none of":1==e&&1==f?
+"":1==f?"one of":f==e?"all of":f+" of")+" which "+(1==f?"is":"are")+" binary"));return e=b.yc?d(a,b):c(a,b)};
+var Yc=exports.IOCP=function(a){a=a||{};this.o=a.msg_lev||Wb;this.Jb=a.br_tech||bd;this.kc=a.bt_tech||fd;this.Ub=a.tol_int||1E-5;this.we=a.tol_obj||1E-7;this.sb=a.tm_lim||2147483647;this.bc=a.out_frq||5E3;this.fb=a.out_dly||1E4;this.ob=a.cb_func||null;this.Uc=a.cb_info||null;this.Me=a.cb_size||0;this.ed=a.pp_tech||jd;this.ce=a.mip_gap||0;this.Ed=a.mir_cuts||cb;this.Ad=a.gmi_cuts||cb;this.xd=a.cov_cuts||cb;this.vd=a.clq_cuts||cb;this.yc=a.presolve||cb;this.sd=a.binarize||cb;this.Xe=a.fp_heur||cb};
+exports.glp_mip_status=function(a){return a.za};exports.glp_mip_obj_val=function(a){return a.ta};
+var kd=exports.glp_mip_row_val=function(a,b){1<=b&&b<=a.g||w("glp_mip_row_val: i = "+b+"; row number out of range");return a.n[b].Sa},ld=exports.glp_mip_col_val=function(a,b){1<=b&&b<=a.i||w("glp_mip_col_val: j = "+b+"; column number out of range");return a.f[b].Sa},Ib=exports.glp_bf_exists=function(a){return 0==a.g||a.valid},Jb=exports.glp_factorize=function(a){function b(a,b,c,d){var e=a.g,f;f=a.head[b];if(f<=e)b=1,c[1]=f,d[1]=1;else for(b=0,a=a.f[f-e].k;null!=a;a=a.I)b++,c[b]=a.n.ea,d[b]=-a.n.ma*
+a.j*a.f.va;return b}var c=a.g,d=a.i,e=a.n,f=a.f,g=a.head,h,k,l;h=a.valid=0;for(k=1;k<=c+d;k++)if(k<=c?(l=e[k].m,e[k].bind=0):(l=f[k-c].m,f[k-c].bind=0),l==A){h++;if(h>c)return a=Kb;g[h]=k;k<=c?e[k].bind=h:f[k-c].bind=h}if(hc.Cd&&w("glp_set_bfcp: lu_size = "+c.Cd+"; invalid parameter"),0c.cc||w("glp_set_bfcp: piv_tol = "+c.cc+"; invalid parameter"),1>c.xc&&w("glp_set_bfcp: piv_lim = "+c.xc+"; invalid parameter"),c.gc!=bb&&c.gc!=cb&&w("glp_set_bfcp: suhl = "+c.gc+"; invalid parameter"),0<=c.Mb&&1E-6>=c.Mb||
+w("glp_set_bfcp: eps_tol = "+c.Mb+"; invalid parameter"),1>c.sc&&w("glp_set_bfcp: max_gro = "+c.sc+"; invalid parameter"),1<=c.$c&&32767>=c.$c||w("glp_set_bfcp: nfs_max = "+c.$c+"; invalid parameter"),0c.ic||w("glp_set_bfcp: upd_tol = "+c.ic+"; invalid parameter"),1<=c.vc&&32767>=c.vc||w("glp_set_bfcp: nrs_max = "+c.vc+"; invalid parameter"),0>c.kd&&w("glp_set_bfcp: rs_size = "+c.vc+"; invalid parameter"),0==c.kd&&(c.kd=20*c.vc));null!=a.U&&nd(a)},td=exports.glp_get_bhead=function(a,b){0==
+a.g||a.valid||w("glp_get_bhead: basis factorization does not exist");1<=b&&b<=a.g||w("glp_get_bhead: k = "+b+"; index out of range");return a.head[b]},ud=exports.glp_get_row_bind=function(a,b){0==a.g||a.valid||w("glp_get_row_bind: basis factorization does not exist");1<=b&&b<=a.g||w("glp_get_row_bind: i = "+b+"; row number out of range");return a.n[b].bind},vd=exports.glp_get_col_bind=function(a,b){0==a.g||a.valid||w("glp_get_col_bind: basis factorization does not exist");1<=b&&b<=a.i||w("glp_get_col_bind: j = "+
+b+"; column number out of range");return a.f[b].bind},xd=exports.glp_ftran=function(a,b){var c=a.g,d=a.n,e=a.f,f,g;0==c||a.valid||w("glp_ftran: basis factorization does not exist");for(f=1;f<=c;f++)b[f]*=d[f].ma;0b.d+f&&(a.na=Ad)}for(d=1;d<=a.i;d++)if(b=a.f[d],b.m==A){b.r=e[b.bind];c=b.type;if(c==Sa||c==I||c==B)f=1E-6+1E-9*Math.abs(b.c),b.rb.d+f&&(a.na=Ad)}a.aa=a.ha;for(d=1;d<=a.i;d++)b=a.f[d],a.aa+=b.u*b.r;for(d=1;d<=a.g;d++)e[d]=0;for(d=1;d<=a.i;d++)b=a.f[d],b.m==
+A&&(e[b.bind]=b.u);zd(a,e);a.sa=dc;for(d=1;d<=a.g;d++)if(b=a.n[d],b.m==A)b.J=0;else if(b.J=-e[d],c=b.m,b=a.dir==za?+b.J:-b.J,(c==Ra||c==G)&&-1E-5>b||(c==Ra||c==Ua)&&1E-5b||(c==Ra||c==Ua)&&1E-5f||w("glp_prim_rtest: eps = "+f+"; invalid parameter");h=kb(a);k=lb(a);l=0;C=s;r=0;for(p=1;p<=b;p++)if(g=c[p],1<=g&&g<=h+k||w("glp_prim_rtest: ind["+p+"] = "+g+"; variable number out of range"),g<=h?(m=ob(a,g),t=pb(a,g),y=qb(a,g),q=zc(a,g),n=Ac(a,g)):(m=rb(a,g-h),t=sb(a,g-h),
+y=tb(a,g-h),q=Cc(a,g-h),n=Dc(a,g-h)),q!=A&&w("glp_prim_rtest: ind["+p+"] = "+g+"; non-basic variable not allowed"),g=0-f)continue;E=(t-n)/g}else if(m==Ta){if(g<+f)continue;E=(y-n)/g}else if(m==I)if(0>g){if(g>-f)continue;E=(t-n)/g}else{if(g<+f)continue;E=(y-n)/g}else if(m==B){if(-fE&&(E=0);if(C>E||C==E&&rf||w("glp_dual_rtest: eps = "+f+"; invalid parameter");h=kb(a);k=lb(a);n=jb(a)==za?1:-1;l=0;y=s;q=0;for(p=1;p<=b;p++){g=c[p];1<=g&&g<=h+k||w("glp_dual_rtest: ind["+p+"] = "+g+"; variable number out of range");g<=h?(m=zc(a,g),r=Bc(a,g)):(m=Cc(a,g-h),r=Ec(a,g-h));m==A&&w("glp_dual_rtest: ind["+p+"] = "+g+"; basic variable not allowed");g=0-f)continue;t=n*r/g}else if(m==
+Ra){if(-ft&&(t=0);if(y>t||y==t&&q=f)return 1;l=1}else if(e==Ta){if(q<=f)return 1;l=-1}else w("glp_analyze_row: type = "+e+"; invalid parameter");e=f-q;b=Fd(a,b,c,d,l,1E-9);if(0==b)return 2;k=c[b];m=k<=a.g?a.n[k].r:a.f[k-a.g].r;c=e/d[b];g(b,m,c,q,e,k<=a.g?a.n[k].J*c:a.f[k-a.g].J*c);return p}
+exports.glp_analyze_bound=function(a,b,c){var d,e,f,g,h,k,l,p,m,q,r,n,t,y;r=n=t=y=null;null!=a&&3621377730==a.Dd||w("glp_analyze_bound: P = "+a+"; invalid problem object");e=a.g;f=a.i;a.na==dc&&a.sa==dc||w("glp_analyze_bound: optimal basic solution required");0==e||a.valid||w("glp_analyze_bound: basis factorization required");1<=b&&b<=e+f||w("glp_analyze_bound: k = "+b+"; variable number out of range");d=b<=e?a.n[b]:a.f[b-e];g=d.m;f=d.r;g==A&&w("glp_analyze_bound: k = "+b+"; basic variable not allowed ");
+g=new Int32Array(1+e);q=new Float64Array(1+e);k=Cd(a,b,g,q);for(b=-1;1>=b;b+=2)l=Ed(a,k,g,q,b,1E-9),0==l?(h=0,l=0>b?-s:+s):(h=g[l],h<=e?(d=a.n[h],p=pb(a,d.ea),m=qb(a,d.ea)):(d=a.f[h-e],p=sb(a,d.C),m=tb(a,d.C)),d=d.r,d=0>b&&0q[l]?p-d:m-d,l=f+d/q[l]),0>b?(r=l,n=h):(t=l,y=h);c(r,n,t,y)};
+exports.glp_analyze_coef=function(a,b,c){var d,e,f,g,h,k,l,p,m,q,r,n,t,y,E,C,D,H,R,V,O=null,Q=null,F=null,W=null,X=null,ca=null;null!=a&&3621377730==a.Dd||w("glp_analyze_coef: P = "+a+"; invalid problem object");e=a.g;f=a.i;a.na==dc&&a.sa==dc||w("glp_analyze_coef: optimal basic solution required");0==e||a.valid||w("glp_analyze_coef: basis factorization required");1<=b&&b<=e+f||w("glp_analyze_coef: k = "+b+"; variable number out of range");b<=e?(d=a.n[b],g=d.type,n=d.c,t=d.d,y=0):(d=a.f[b-e],g=d.type,
+n=d.c,t=d.d,y=d.u);h=d.m;E=d.r;h!=A&&w("glp_analyze_coef: k = "+b+"; non-basic variable not allowed");h=new Int32Array(1+e);V=new Float64Array(1+e);r=new Int32Array(1+f);R=new Float64Array(1+f);m=Bd(a,b,r,R);for(f=-1;1>=f;f+=2)a.dir==za?l=-f:a.dir==Ea&&(l=+f),q=Fd(a,m,r,R,l,1E-9),0==q?(C=0>f?-s:+s,k=0,q=E):(k=r[q],d=k<=e?a.n[k]:a.f[k-e],l=d.J,d=-l/R[q],C=y+d,l=0>f&&0R[q]?1:-1,a.dir==Ea&&(l=-l),p=Cd(a,k,h,V),d=b<=e?a.n[b]:a.f[b-e],d.type=Ka,d.c=d.d=0,p=Ed(a,p,h,V,l,1E-9),d=b<=e?a.n[b]:
+a.f[b-e],d.type=g,d.c=n,d.d=t,0==p?q=0>l&&0R[q]?-s:+s:(d=h[p],d<=e?(d=a.n[d],D=pb(a,d.ea),H=qb(a,d.ea)):(d=a.f[d-e],D=sb(a,d.C),H=tb(a,d.C)),d=d.r,d=0>l&&0V[p]?D-d:H-d,q=E+R[q]/V[p]*d)),0>f?(O=C,Q=k,F=q):(W=C,X=k,ca=q);c(O,Q,F,W,X,ca)};exports.glp_ios_reason=function(a){return a.reason};exports.glp_ios_get_prob=function(a){return a.A};function Hd(a){a.reason!=Ia&&w("glp_ios_pool_size: operation not allowed");return a.Bd.size}
+function Id(a,b,c,d,e,f,g){a.reason!=Ia&&w("glp_ios_add_row: operation not allowed");var h=a.Bd,k,l;k={name:null};0<=b&&255>=b||w("glp_ios_add_row: klass = "+b+"; invalid cut class");k.qc=b;k.k=null;0<=c&&c<=a.i||w("glp_ios_add_row: len = "+c+"; invalid cut length");for(l=1;l<=c;l++)b={},1<=d[l]&&d[l]<=a.i||w("glp_ios_add_row: ind["+l+"] = "+d[l]+"; column index out of range"),b.C=d[l],b.j=e[l],b.e=k.k,k.k=b;f!=Sa&&f!=Ta&&f!=B&&w("glp_ios_add_row: type = "+f+"; invalid cut type");k.type=f;k.cg=g;
+k.ca=h.Xa;k.e=null;null==k.ca?h.head=k:k.ca.e=k;h.Xa=k;h.size++}function Jd(a,b){1<=b&&b<=a.A.i||w("glp_ios_can_branch: j = "+b+"; column number out of range");return a.ad[b]}
+function Kd(a,b){var c=a.A,d=a.wc,e=a.i,f,g;g=c.ha;for(f=1;f<=e;f++){var h=c.f[f];if(h.kind==Fc&&b[f]!=Math.floor(b[f]))return 1;g+=h.u*b[f]}if(c.za==dc)switch(c.dir){case za:if(g>=a.A.ta)return 1;break;case Ea:if(g<=a.A.ta)return 1}a.p.o>=fc&&x("Solution found by heuristic: "+g+"");c.za=dc;c.ta=g;for(f=1;f<=e;f++)c.f[f].Sa=b[f];for(e=1;e<=d;e++)for(f=c.n[e],f.Sa=0,g=f.k;null!=g;g=g.B)f.Sa+=g.j*g.f.Sa;return 0}exports.glp_mpl_alloc_wksp=function(){return Ld()};
+exports._glp_mpl_init_rand=function(a,b){0!=a.D&&w("glp_mpl_init_rand: invalid call sequence\n");Md(a.Gd,b)};var Od=exports.glp_mpl_read_model=function(a,b,c,d){0!=a.D&&w("glp_mpl_read_model: invalid call sequence");a=Nd(a,b,c,d);1==a||2==a?a=0:4==a&&(a=1);return a};exports.glp_mpl_read_model_from_string=function(a,b,c,d){var e=0;return Od(a,b,function(){return eh)h=0;if(g==Ka||g==Sa||1Math.abs(h)&&(h=0),1E-9>Math.abs(k)&&(k=0),ne(a,d,g,h,k);
+for(d=1;d<=f;d++)c==Zb?(g=Cc(b,d),h=Dc(b,d),k=Ec(b,d)):c==le?(g=0,h=glp_ipt_col_prim(b,d),k=glp_ipt_col_dual(b,d)):c==Sc&&(g=0,h=ld(b,d),k=0),1E-9>Math.abs(h)&&(h=0),1E-9>Math.abs(k)&&(k=0),oe(a,d,g,h,k);a=pe(a);3==a?a=0:4==a&&(a=1);return a};
+function qe(a,b){var c,d,e;c=null;for(d=a.root;null!=d;)c=d,0>=a.yg(a.info,b,c.key)?(e=0,d=c.left,c.pa++):(e=1,d=c.right);d={};d.key=b;d.type=0;d.link=null;d.pa=1;d.R=c;d.ba=null==c?0:e;d.wa=0;d.left=null;d.right=null;a.size++;for(null==c?a.root=d:0==e?c.left=d:c.right=d;null!=c;){if(0==e){if(0c.wa){re(a,c);break}c.wa=-1}else{if(0>c.wa){c.wa=0;break}if(0b.wa?(c=b.R,d=b.left,e=d.right,0>=d.wa?(null==c?a.root=d:0==b.ba?c.left=d:c.right=d,b.pa-=d.pa,d.R=c,d.ba=b.ba,d.wa++,d.right=b,b.R=d,b.ba=1,b.wa=-d.wa,b.left=e,null!=e&&(e.R=b,e.ba=0)):(f=e.left,g=e.right,null==c?a.root=e:0==b.ba?c.left=e:c.right=e,b.pa-=d.pa+e.pa,e.pa+=d.pa,b.wa=0<=e.wa?0:1,d.wa=0>=e.wa?0:-1,e.R=c,e.ba=b.ba,e.wa=0,e.left=d,e.right=b,b.R=e,b.ba=1,b.left=g,d.R=e,d.ba=0,d.right=f,null!=f&&(f.R=d,f.ba=1),null!=g&&(g.R=b,g.ba=0))):(c=b.R,d=b.right,e=
+d.left,0<=d.wa?(null==c?a.root=d:0==b.ba?c.left=d:c.right=d,d.pa+=b.pa,d.R=c,d.ba=b.ba,d.wa--,d.left=b,b.R=d,b.ba=0,b.wa=-d.wa,b.right=e,null!=e&&(e.R=b,e.ba=1)):(f=e.left,g=e.right,null==c?a.root=e:0==b.ba?c.left=e:c.right=e,d.pa-=e.pa,e.pa+=b.pa,b.wa=0>=e.wa?0:-1,d.wa=0<=e.wa?0:1,e.R=c,e.ba=b.ba,e.wa=0,e.left=b,e.right=d,b.R=e,b.ba=0,b.right=f,d.R=e,d.ba=1,d.left=g,null!=f&&(f.R=b,f.ba=1),null!=g&&(g.R=d,g.ba=0)))}var pd=1,qd=2,se=3,te=4,ue=5;
+function od(a,b,c,d){var e,f;f=a.valid=0;switch(a.type){case md:a.Za=null;null==a.gb&&(f={},f.hb=f.g=0,f.valid=0,f.ia=ve(),f.$d=50,f.Pb=0,f.Ze=f.af=f.$e=null,f.je=f.ie=null,f.ug=null,f.vg=null,f.ic=1E-6,f.ag=0,a.gb=f,f=1);break;case rd:case sd:a.gb=null,null==a.Za&&(we&&x("lpf_create_it: warning: debug mode enabled"),f={valid:0},f.Nc=f.ef=0,f.ia=ve(),f.g=0,f.Cf=null,f.K=50,f.i=0,f.Md=f.Ld=null,f.Od=f.Nd=null,f.Qc=null,f.Ge=f.Fe=null,f.Ie=f.He=null,f.Jd=1E3,f.od=0,f.Wb=null,f.Xb=null,f.jb=f.Gc=null,
+a.Za=f,f=1)}null!=a.gb?e=a.gb.ia:null!=a.Za&&(e=a.Za.ia);f&&(e.Va=a.Cd);e.cc=a.cc;e.xc=a.xc;e.gc=a.gc;e.Mb=a.Mb;e.sc=a.sc;null!=a.gb&&(f&&(a.gb.$d=a.$c),a.gb.ic=a.ic);null!=a.Za&&(f&&(a.Za.K=a.vc),f&&(a.Za.Jd=a.kd));if(null!=a.gb){a:{e=a.gb;1>b&&w("fhv_factorize: m = "+b+"; invalid parameter");1E8b&&w("lpf_factorize: m = "+b+"; invalid parameter");1E8=f||w("scf_create_it: n_max = "+f+"; invalid parameter"),g={},g.K=f,g.i=0,g.Nb=new Float64Array(1+f*f),g.v=new Float64Array(1+f*(f+1)/2),g.s=new Int32Array(1+f),g.fg=De,g.pa=0,g.l=Ce?new Float64Array(1+f*f):null,g.ig=new Float64Array(1+f),e.Qc=g);null==e.Wb&&(e.Wb=new Int32Array(1+
+e.Jd));null==e.Xb&&(e.Xb=new Float64Array(1+e.Jd));e.Ncb?"\n"==a.l?(a.count--,b=-1):(e(a,"missing final end of line"),b="\n"):"\n"!=b&&(0<=" \t\n\v\f\r".indexOf(b)?b=" ":ta(b)&&d(a,"invalid control character "+b.charCodeAt(0)));a.l=b}function g(a){a.h+=a.l;f(a)}function h(a,b){return a.toLowerCase()==b.toLowerCase()?1:0}function k(a){function b(){for(a.b=
+Q;va(a.l)||0<=Xe.indexOf(a.l);)g(a);c&&(h(a.h,"minimize")?a.b=y:h(a.h,"minimum")?a.b=y:h(a.h,"min")?a.b=y:h(a.h,"maximize")?a.b=E:h(a.h,"maximum")?a.b=E:h(a.h,"max")?a.b=E:h(a.h,"subject")?" "==a.l&&(f(a),"t"==a.l.toLowerCase()&&(a.b=C,a.h+=" ",g(a),"o"!=a.l.toLowerCase()&&d(a,"keyword `subject to' incomplete"),g(a),ua(a.l)&&d(a,"keyword `"+a.h+a.l+"...' not recognized"))):h(a.h,"such")?" "==a.l&&(f(a),"t"==a.l.toLowerCase()&&(a.b=C,a.h+=" ",g(a),"h"!=a.l.toLowerCase()&&d(a,"keyword `such that' incomplete"),
+g(a),"a"!=a.l.toLowerCase()&&d(a,"keyword `such that' incomplete"),g(a),"t"!=a.l.toLowerCase()&&d(a,"keyword `such that' incomplete"),g(a),ua(a.l)&&d(a,"keyword `"+a.h+a.l+"...' not recognized"))):h(a.h,"st")?a.b=C:h(a.h,"s.t.")?a.b=C:h(a.h,"st.")?a.b=C:h(a.h,"bounds")?a.b=D:h(a.h,"bound")?a.b=D:h(a.h,"general")?a.b=H:h(a.h,"generals")?a.b=H:h(a.h,"gen")?a.b=H:h(a.h,"integer")?a.b=R:h(a.h,"integers")?a.b=R:h(a.h,"int")?a.b=R:h(a.h,"binary")?a.b=V:h(a.h,"binaries")?a.b=V:h(a.h,"bin")?a.b=V:h(a.h,"end")&&
+(a.b=O))}var c;a.b=-1;a.h="";for(a.value=0;;){for(c=0;" "==a.l;)f(a);if(-1==a.l)a.b=t;else if("\n"==a.l)if(f(a),ua(a.l))c=1,b();else continue;else if("\\"==a.l){for(;"\n"!=a.l;)f(a);continue}else if(ua(a.l)||"."!=a.l&&0<=Xe.indexOf(a.l))b();else if(wa(a.l)||"."==a.l){for(a.b=F;wa(a.l);)g(a);if("."==a.l)for(g(a),1!=a.h.length||wa(a.l)||d(a,"invalid use of decimal point");wa(a.l);)g(a);if("e"==a.l||"E"==a.l)for(g(a),"+"!=a.l&&"-"!=a.l||g(a),wa(a.l)||d(a,"numeric constant `"+a.h+"' incomplete");wa(a.l);)g(a);
+a.value=Number(a.h);a.value==Number.NaN&&d(a,"numeric constant `"+a.h+"' out of range")}else"+"==a.l?(a.b=W,g(a)):"-"==a.l?(a.b=X,g(a)):":"==a.l?(a.b=ca,g(a)):"<"==a.l?(a.b=ka,g(a),"="==a.l&&g(a)):">"==a.l?(a.b=P,g(a),"="==a.l&&g(a)):"="==a.l?(a.b=u,g(a),"<"==a.l?(a.b=ka,g(a)):">"==a.l&&(a.b=P,g(a))):d(a,"character `"+a.l+"' not recognized");break}for(;" "==a.l;)f(a)}function l(a,b){var c=xb(a.Ka,b);if(0==c){c=Oa(a.Ka,1);Qa(a.Ka,c,b);if(a.Kf&&d(a,"invalid use of `-inf' as upper bound"),q(a,b,+s),k(a)):d(a,"missing upper bound")):a.b==F?(q(a,b,a.value),k(a)):d(a,"missing upper bound")):
+a.b==P?(c&&d(a,"invalid bound definition"),k(a),a.b==W||a.b==X?(f=a.b==W?1:-1,k(a),a.b==F?(m(a,b,f*a.value),k(a)):h(a.h,"infinity")||0==h(a.h,"inf")?(0Xe.indexOf(a[b]))return 1;return 0}function e(a){for(var b=0;b= "+l.c;else if(l.type==Ta)q=" <= "+l.d;else if(l.type==I||l.type==B)q=" = "+l.c;72= "+l.c),r++):l.type==Ta?(c(" -Inf <= "+b+" <= "+l.d),
+r++):l.type==I?(c(" "+l.c+" <= "+b+" <= "+l.d),r++):l.type==B&&(c(" "+b+" = "+l.c),r++);q&&c("");r++;q=0;for(m=1;m<=a.i;m++)l=a.f[m],l.kind!=Ma&&(q||(c("Generals"),q=1,r++),c(" "+g(k,m)),r++);q&&(c(""),r++);return h()};exports.glp_read_lp_from_string=function(a,b,c){var d=0;return Ye(a,b,function(){return dp[u]&&Ze(h,u,l[u]+10))return a.valid=
+0,h.Va=h.Ga+h.Ga,a=Re;f=k[u]+l[u];D[f]=b;H[f]=ka[z];l[u]++;dd)return a.valid=0,a=ze;u=t[e];b=C[e];for(z=e;zn[b]&&$e(h,b,r[b]+10))return a.valid=0,h.Va=h.Ga+h.Ga,a=Re;X=q[b]+r[b];D[X]=u;H[X]=e;r[b]++;c++;ca[c]=b;ka[c]=e}if(p[u]=b?a:b));m=new Int32Array(1+a);q=new Int32Array(1+b);r=new Int32Array(1+a);n=new Int32Array(1+a);y=new Int32Array(1+b);E=new Int32Array(1+b);for(D=1;D<=b;D++)H=d(c,-D,p),y[D]=m[H],m[H]=D;for(H=t=0;H<=a;H++)for(D=m[H];0!=D;D=y[D])E[D]=t,t=D;H=0;for(D=t;0!=D;D=E[D])y[D]=H,H=D;for(C=1;C<=a;C++)m[C]=H=d(c,+C,p),r[C]=0,n[C]=q[H],0!=n[C]&&(r[n[C]]=
+C),q[H]=C;for(C=1;C<=a;C++)k[C]=0;for(D=1;D<=b;D++)l[D]=0;R=1;for(V=b;R<=V;){C=q[1];if(0!=C){D=0;for(O=d(c,+C,p);1<=O;O--)H=p[O],0==l[H]&&(D=H);k[C]=l[D]=R;R++;Q++}else D=t,l[D]=V,V--;0==y[D]?t=E[D]:E[y[D]]=E[D];0!=E[D]&&(y[E[D]]=y[D]);for(O=d(c,-D,p);1<=O;O--)C=p[O],H=m[C],0==r[C]?q[H]=n[C]:n[r[C]]=n[C],0!=n[C]&&(r[n[C]]=r[C]),m[C]=--H,r[C]=0,n[C]=q[H],0!=n[C]&&(r[n[C]]=C),q[H]=C}for(C=1;C<=a;C++)0==k[C]&&(k[C]=R++);for(D=1;D<=b;D++);for(r=1;r<=a;r++)m[r]=0;for(C=1;C<=a;C++)r=k[C],m[r]=C;for(H=1;H<=
+b;H++)q[H]=0;for(D=1;D<=b;D++)H=l[D],q[H]=D;for(r=1;r<=Q;r++)for(C=m[r],O=d(c,+C,p);1<=O;O--);return Q}function c(a,b,c){var d=kb(a);lb(a);var k,l,p,m=0;if(0c;d--)a.ya[d].rb=null,a.ya[d].e=a.Sc,a.Sc=d;d=a.Sc;a.Sc=a.ya[d].e;a.ya[d].e=0;c=d;d={};a.ya[c].rb=d;d.s=c;d.R=b;d.La=null==b?0:b.La+1;d.count=0;d.Na=null;d.zc=null;d.ec=null;d.eg=0;d.rc=null==b?a.A.dir==za?-s:+s:b.rc;d.bound=null==b?a.A.dir==za?-s:+s:b.bound;d.Tc=0;d.tg=0;d.Ag=0;d.Zc=0;d.Rd=0;d.data=0==a.p.Me?null:{};d.ja=null;d.ca=a.Xa;d.e=null;null==a.head?
+a.head=d:a.Xa.e=d;a.Xa=d;a.Pd++;a.Zf++;a.Jg++;null!=b&&b.count++;return d}
+function pf(a,b){var c=a.A,d,e,f,g;d=a.ya[b].rb;a.N=d;e=a.ya[1].rb;if(d!=e){for(d.ja=null;null!=d;d=d.R)null!=d.R&&(d.R.ja=d);for(d=e;null!=d;d=d.ja){var h=c.g;e=c.i;if(null==d.ja){a.Fg=h;a.Ggl;g--){h=b.n[g];t={};f=mb(b,g);t.name=null==f?null:f;t.type=h.type;t.c=h.c;t.d=h.d;t.k=null;p=ub(b,g,m,n);for(f=1;f<=p;f++)q={},q.C=m[f],q.j=n[f],q.e=t.k,t.k=q;t.ma=h.ma;t.m=h.m;t.e=e.ec;e.ec=t}if(c!=k){c-=k;e=new Int32Array(1+c);for(g=1;g<=c;g++)e[g]=k+g;ab(b,c,e)}c=b.g;for(g=1;g<=c;g++)Va(b,g,a.se[g],a.qe[g],a.te[g]),Fb(b,g,a.re[g]);for(k=1;k<=d;k++)Wa(b,k,a.se[c+k],a.qe[c+k],a.te[c+k]),Gb(b,k,a.re[c+k])}a.N=
+null}function rf(a,b,c){var d;b=a.ya[b].rb;null==b.ca?a.head=b.e:b.ca.e=b.e;null==b.e?a.Xa=b.ca:b.e.ca=b.ca;b.ca=b.e=null;a.Pd--;for(d=1;2>=d;d++)c[d]=Mc(a,b).s}
+function sf(a,b){var c;c=a.ya[b].rb;null==c.ca?a.head=c.e:c.ca.e=c.e;null==c.e?a.Xa=c.ca:c.e.ca=c.ca;c.ca=c.e=null;for(a.Pd--;;){for(var d;null!=c.Na;)d=c.Na,c.Na=d.e;for(;null!=c.zc;)d=c.zc,c.zc=d.e;for(;null!=c.ec;){d=c.ec;for(d.name=null;null!=d.k;)d.k=d.k.e;c.ec=d.e}b=c.s;a.ya[b].rb=null;a.ya[b].e=a.Sc;a.Sc=b;c=c.R;a.Zf--;if(null!=c&&(c.count--,0==c.count))continue;break}}
+function tf(a,b,c){var d=a.A,e=d.g,f,g,h,k,l=a.Bg,p=a.Tg,m,q;xc(d);Ib(d);a=d.f[b].r;b=Bd(d,e+b,l,p);for(f=-1;1>=f;f+=2)if(h=l,g=Fd(d,b,h,p,f,1E-9),g=0==g?0:h[g],0==g)d.dir==za?0>f?m=+s:q=+s:d.dir==Ea&&(0>f?m=-s:q=-s);else{for(h=1;h<=b&&l[h]!=g;h++);h=p[h];g<=e?(k=d.n[g].m,g=d.n[g].J):(k=d.f[g-e].m,g=d.f[g-e].J);if(d.dir==za){if(k==G&&0>g||k==Ua&&0g||k==Ra)&&(g=0);k=(0>f?Math.floor(a):Math.ceil(a))-a;k/=h;h=g*k;0>f?m=d.aa+h:q=d.aa+h}c(m,q)}
+function uf(a,b){var c=a.A,d=c.i,e,f,g,h=a.Bg,k;g=0;k=c.ha;e=0;for(f=1;f<=d;f++){var l=c.f[f];if(0!=l.u)if(l.type==B)k+=l.u*l.r;else{if(l.kind!=Fc||l.u!=Math.floor(l.u))return b;2147483647>=Math.abs(l.u)?h[++g]=Math.abs(l.u)|0:e=1}}if(0==e){if(0==g)return b;d=0;for(e=1;e<=g;e++){if(1==e)d=h[1];else for(f=h[e],l=void 0;0=Math.floor(c)+0.001&&(c=Math.ceil(c),b=e*c+k)):c.dir==Ea&&b!=-s&&(c=(b-k)/e,c<=Math.ceil(c)-0.001&&(c=Math.floor(c),
+b=e*c+k));return b}function vf(a,b){var c=a.A,d=1,e;if(c.za==dc)switch(e=a.p.we*(1+Math.abs(c.ta)),c.dir){case za:b>=c.ta-e&&(d=0);break;case Ea:b<=c.ta+e&&(d=0)}else switch(c.dir){case za:b==+s&&(d=0);break;case Ea:b==-s&&(d=0)}return d}function wf(a){var b=null;switch(a.A.dir){case za:for(a=a.head;null!=a;a=a.e)if(null==b||b.bound>a.bound)b=a;break;case Ea:for(a=a.head;null!=a;a=a.e)if(null==b||b.boundb[f])if(d[f]==+s)if(0==g)g=f;else{h=-s;g=0;break}else h+=b[f]*d[f];e.Wd=h;e.Uf=g;g=h=0;for(f=1;f<=a;f++)if(0b[f])if(c[f]==-s)if(0==g)g=f;else{h=+s;g=0;break}else h+=b[f]*c[f];e.Vd=h;e.Tf=g}function d(a,b){b(0==a.Uf?a.Wd:-s,0==a.Tf?a.Vd:+s)}function e(a,b,c,d,e,f,g,h){var k,
+l,m,n;c==-s||a.Vd==+s?k=-s:0==a.Tf?0b[g]&&(k=c-(a.Vd-b[g]*e[g])):k=a.Tf==g?c-a.Vd:-s;d==+s||a.Wd==-s?l=+s:0==a.Uf?0b[g]&&(l=d-(a.Wd-b[g]*f[g])):l=a.Uf==g?d-a.Wd:+s;1E-6>Math.abs(b[g])?(m=-s,n=+s):0b[g]&&(m=l==+s?-s:l/b[g],n=k==-s?+s:k/b[g]);h(m,n)}function f(a,b,c,e,f){var g=0,h=b[c],k=e[f],l=null,m=null;d(a,function(a,b){l=a;m=b});if(h!=-s&&(a=0.001*(1+Math.abs(h)),mk+a))return 1;h!=-s&&(a=1E-12*(1+Math.abs(h)),l>h-a&&(b[c]=-s));k!=+s&&(a=1E-12*(1+Math.abs(k)),mp-Math.floor(p)?Math.floor(p):Math.ceil(p)),r!=+s&&(r=0.001>Math.ceil(r)-r?Math.ceil(r):Math.floor(r)));if(n!=-s&&(a=0.001*(1+Math.abs(n)),rq+a))return 1;p!=-s&&(a=0.001*(1+Math.abs(p)),nr+a&&(q=r));n!=-s&&q!=+s&&(a=Math.abs(n),b=Math.abs(q),n>q-1E-10*(1+(a<=b?a:b))&&(n==f[k]?q=n:q==g[k]?n=q:a<=b?q=n:n=q));l(n,q);return m}function h(a,b,c,d,e){var f,g=0;b=0.25*f&&g++));c>e&&(a||c==+s?g++:(f=b==-s?1+Math.abs(c):1+(c-b),c-e>=0.25*f&&g++));return g}var k=a.A,l=k.g,p=k.i,m,q,r,n=0,t,y,E,C;t=new Float64Array(1+l);y=new Float64Array(1+l);switch(k.za){case Aa:t[0]=-s;y[0]=+s;break;case dc:switch(k.dir){case za:t[0]=
+-s;y[0]=k.ta-k.ha;break;case Ea:t[0]=k.ta-k.ha,y[0]=+s}}for(m=1;m<=l;m++)t[m]=pb(k,m),y[m]=qb(k,m);E=new Float64Array(1+p);C=new Float64Array(1+p);for(m=1;m<=p;m++)E[m]=sb(k,m),C[m]=tb(k,m);q=l+1;r=new Int32Array(1+q);for(m=1;m<=q;m++)r[m]=m-1;if(function(a,b,d,e,k,l,m,n){var q=a.g,p=a.i,r={},t,u,E=0,C,v,y,M,ba,J,ea,fa;C=new Int32Array(1+p);v=new Int32Array(1+q+1);y=new Int32Array(1+q+1);M=new Int32Array(1+q+1);ba=new Float64Array(1+p);J=new Float64Array(1+p);ea=new Float64Array(1+p);u=0;for(t=1;t<=
+l;t++)q=m[t],v[++u]=q,y[q]=1;for(;0=n||b[fa]==-s&&d[fa]==+s||0!=y[fa]||(v[++u]=fa,y[fa]=1)}}return E}(k,t,y,E,C,q,r,b))return 1;for(m=1;m<=l;m++)zc(k,m)==A&&(t[m]==-s&&y[m]==+s?Va(k,m,Ka,0,0):y[m]==+s?Va(k,m,Sa,t[m],0):t[m]==-s&&Va(k,m,Ta,0,y[m]));for(m=1;m<=p;m++)Wa(k,m,E[m]==-s&&C[m]==+s?Ka:C[m]==+s?Sa:E[m]==-s?Ta:E[m]!=C[m]?I:B,E[m],C[m]);return n}
+function Nc(a){function b(a,b){var c,d,e,f;d=a.A.za==dc?String(a.A.ta):"not found yet";c=wf(a);0==c?e="tree is empty":(c=a.ya[c].rb.bound,e=c==-s?"-inf":c==+s?"+inf":c);a.A.dir==za?f=">=":a.A.dir==Ea&&(f="<=");c=xf(a);x("+"+a.A.$+": "+(b?">>>>>":"mip =")+" "+d+" "+f+" "+e+" "+(0==c?" 0.0%":0.001>c?" < 0.1%":9.999>=c?" "+Number(100*c).toFixed(1)+"%":"")+" ("+a.Pd+"; "+(a.Jg-a.Zf)+")");a.Lg=ja()}function c(a,b){return vf(a,a.ya[b].rb.bound)}function d(a){var b=a.A,c,d,e=0,f,g,h,k,l,m=0;for(c=1;c<=
+b.i;c++)if(h=b.f[c],a.ad[c]=0,h.kind==Fc&&h.m==A){d=h.type;f=h.c;g=h.d;h=h.r;if(d==Sa||d==I||d==B){k=f-a.p.Ub;l=f+a.p.Ub;if(k<=h&&h<=l)continue;if(hg)continue}k=Math.floor(h+0.5)-a.p.Ub;l=Math.floor(h+0.5)+a.p.Ub;k<=h&&h<=l||(a.ad[c]=1,e++,k=h-Math.floor(h),l=Math.ceil(h)-h,m+=k<=l?k:l)}a.N.Ag=e;a.N.Zc=m;a.p.o>=mc&&(0==e?x("There are no fractional columns"):1==e?x("There is one fractional column, integer infeasibility is "+
+m+""):x("There are "+e+" fractional columns, integer infeasibility is "+m+""))}function e(a){var b=a.A,c;b.za=dc;b.ta=b.aa;for(c=1;c<=b.g;c++){var d=b.n[c];d.Sa=d.r}for(c=1;c<=b.i;c++)d=b.f[c],d.kind==Ma?d.Sa=d.r:d.kind==Fc&&(d.Sa=Math.floor(d.r+0.5));a.qh++}function f(a,b,c){var d=a.A,e,f=d.g,g,h,k,l,m,n=Array(3),q,p,r,t,y=null,v=null,S;g=d.f[b].type;q=d.f[b].c;p=d.f[b].d;e=d.f[b].r;r=Math.floor(e);t=Math.ceil(e);switch(g){case Ka:h=Ta;k=Sa;break;case Sa:h=q==r?B:I;k=Sa;break;case Ta:h=Ta;k=t==p?
+B:I;break;case I:h=q==r?B:I,k=t==p?B:I}tf(a,b,function(a,b){y=a;v=b});g=uf(a,y);S=uf(a,v);l=!vf(a,g);m=!vf(a,S);if(l&&m)return a.p.o>=mc&&x("Both down- and up-branches are hopeless"),2;if(m)return a.p.o>=mc&&x("Up-branch is hopeless"),Wa(d,b,h,q,r),a.N.rc=y,d.dir==za?a.N.boundg&&(a.N.bound=g),1;if(l)return a.p.o>=mc&&x("Down-branch is hopeless"),Wa(d,b,k,t,p),a.N.rc=v,d.dir==za?a.N.boundS&&(a.N.bound=S),1;a.p.o>=mc&&x("Branching on column "+
+b+", primal value is "+e+"");l=a.N.s;a.N.Tc=b;a.N.tg=e;qf(a);rf(a,l,n);a.p.o>=mc&&x("Node "+n[1]+" begins down branch, node "+n[2]+" begins up branch ");e=a.ya[n[1]].rb;e.Na={};e.Na.pc=f+b;e.Na.type=h;e.Na.c=q;e.Na.d=r;e.Na.e=null;e.rc=y;d.dir==za?e.boundg&&(e.bound=g);e=a.ya[n[2]].rb;e.Na={};e.Na.pc=f+b;e.Na.type=k;e.Na.c=t;e.Na.d=p;e.Na.e=null;e.rc=v;d.dir==za?e.boundS&&(e.bound=S);c==Af?a.ud=0:c==Bf?a.ud=n[1]:c==Cf&&(a.ud=n[2]);
+return 0}function g(a){var b=a.A,c,d,e=0,f,g,h,k;f=b.aa;for(c=1;c<=b.i;c++)if(k=b.f[c],k.kind==Fc)switch(g=k.c,h=k.d,d=k.m,k=k.J,b.dir){case za:d==G?(0>k&&(k=0),f+k>=b.ta&&(Wa(b,c,B,g,g),e++)):d==Ua&&(0=b.ta&&(Wa(b,c,B,h,h),e++));break;case Ea:d==G?(0k&&(k=0),f-k<=b.ta&&(Wa(b,c,B,h,h),e++))}a.p.o>=mc&&0!=e&&(1==e?x("One column has been fixed by reduced cost"):x(e+" columns have been fixed by reduced costs"))}function h(a){var b,c=0,
+d=null;for(b=a.wc+1;b<=a.A.g;b++)a.A.n[b].origin==Ja&&a.A.n[b].La==a.N.La&&a.A.n[b].m==A&&(null==d&&(d=new Int32Array(1+a.A.g)),d[++c]=b);0c&&(c=1E3);d=0;for(b=a.wc+1;b<=a.A.g;b++)a.A.n[b].origin==Ja&&d++;if(!(d>=c)){a.p.Ad==bb&&5>a.N.Rd&&Hf(a);a.p.Ed==bb&&If(a,a.Xf);if(a.p.xd==bb){b=a.A;c=kb(b);var e=lb(b),f,g,h,k,l;xc(b);d=new Int32Array(1+e);k=new Float64Array(1+e);l=new Float64Array(1+e);for(e=1;e<=c;e++)for(h=1;2>=h;h++){g=ob(b,e)-Ka+gf;if(1==h){if(g!=lf&&g!=nf)continue;g=ub(b,e,d,k);k[0]=Jf(b,e)}else{if(g!=jf&&g!=nf)continue;g=ub(b,e,d,k);for(f=1;f<=g;f++)k[f]=-k[f];k[0]=
+-Kf(b,e)}a:{var m=b;f=d;for(var n=k,q=l,p=null,r=null,t=Array(5),y=void 0,v=void 0,S=void 0,M=S=void 0,ba=void 0,J=M=M=void 0,S=0,v=1;v<=g;v++)y=f[v],rb(m,y)-Ka+gf==cf?n[0]-=n[v]*Lf(m,y):(S++,f[S]=f[v],n[S]=n[v]);g=S;S=0;for(v=1;v<=g;v++)y=f[v],(Ic(m,y)==Ma?Mf:Nf)==Nf&&rb(m,y)-Ka+gf==nf&&0==Lf(m,y)&&1==Of(m,y)&&(S++,ba=f[S],M=n[S],f[S]=f[v],n[S]=n[v],f[v]=ba,n[v]=M);if(2>S)g=0;else{ba=M=0;for(v=S+1;v<=g;v++){y=f[v];if(rb(m,y)-Ka+gf!=nf){g=0;break a}0J&&(J=0);J>M&&(J=M);n[0]-=ba;for(v=1;v<=S;v++)y=f[v],q[v]=Dc(m,y),0>q[v]&&(q[v]=0),1n[v]&&(f[v]=-f[v],n[v]=-n[v],n[0]+=n[v],q[v]=1-q[v]);m=S;v=n[0];y=J;J=void 0;for(J=1;J<=m;J++);for(J=1;J<=m;J++);J=void 0;b:{for(var ea=J=void 0,fa=0,sa=0,K=void 0,oa=void 0,Bb=0.001,K=0.001*(1+Math.abs(v)),J=1;J<=m;J++)for(ea=J+1;ea<=m;ea++){fa++;if(1E3v+K&&(oa=n[J]+
+n[ea]-v,p=1/(oa+M),r=2-p*oa,oa=q[J]+q[ea]+p*y-r,Bbv+oa&&(Bb=n[J]+n[ea]+n[fa]-v,p=1/(Bb+M),r=3-p*Bb,Bb=q[J]+q[ea]+q[fa]+p*y-r,ecv+Bb&&(ec=n[J]+n[ea]+n[fa]+n[sa]-v,p=1/(ec+M),r=4-p*ec,ec=q[J]+q[ea]+q[fa]+q[sa]+p*y-r,Wjn&&w("lpx_eval_row: len = "+n+"; invalid row length");for(S=1;S<=n;S++)t=q[S],1<=t&&t<=r||w("lpx_eval_row: j = "+t+"; column number out of range"),ba+=p[S]*Dc(f,t);f=ba-k[0];0.001>f||Id(a,Ff,g,d,k,Ta,k[0])}}}a.p.vd==bb&&null!=a.Ne&&(0==a.N.La&&50>a.N.Rd||0a.N.Rd)&&(c=a.Ne,d=lb(a.A),b=new Int32Array(1+d),d=new Float64Array(1+d),c=Pf(a.A,c,b,d),0=mc&&(1==e?x("One hopeless branch has been pruned"):1=mc&&x("Active list is empty!");n=0;r=3;break}if(null!=a.p.ob&&(a.reason=Qf,a.p.ob(a,a.p.Uc),a.reason=0,a.stop)){n=Rc;r=3;break}0==a.gf&&(a.gf=1==a.Pd?a.head.s:0!=a.ud?a.ud:Rf(a));pf(a,a.gf);a.gf=a.ud=0;null!=a.N.R&&a.N.R.s!=t&&(t=0);m=a.N.s;a.p.o>=mc&&(x("------------------------------------------------------------------------"),
+x("Processing node "+m+" at level "+a.N.La+""));1==m&&(a.p.Ad==bb&&a.p.o>=Wb&&x("Gomory's cuts enabled"),a.p.Ed==bb&&(a.p.o>=Wb&&x("MIR cuts enabled"),a.Xf=Sf(a)),a.p.xd==bb&&a.p.o>=Wb&&x("Cover cuts enabled"),a.p.vd==bb&&(a.p.o>=Wb&&x("Clique cuts enabled"),a.Ne=Tf(a.A)));case 1:(a.p.o>=mc||a.p.o>=fc&&a.p.bc-1<=1E3*la(a.Lg))&&b(a,0);a.p.o>=Wb&&60<=la(y)&&(x("Time used: "+la(a.hc)+" secs"),y=ja());if(0=mc&&x("Relative gap tolerance reached; search terminated ");n=Pc;
+r=3;break}if(2147483647>a.p.sb&&a.p.sb-1<=1E3*la(a.hc)){a.p.o>=mc&&x("Time limit exhausted; search terminated");n=Qc;r=3;break}if(null!=a.p.ob&&(a.reason=Uf,a.p.ob(a,a.p.Uc),a.reason=0,a.stop)){n=Rc;r=3;break}if(a.p.ed!=hd)if(a.p.ed==id){if(0==a.N.La&&zf(a,100)){r=2;break}}else if(a.p.ed==jd&&zf(a,0==a.N.La?100:10)){r=2;break}if(!c(a,m)){x("*** not tested yet ***");r=2;break}a.p.o>=mc&&x("Solving LP relaxation...");n=yf(a);if(0!=n&&n!=Vf&&n!=Wf){a.p.o>=Lb&&x("ios_driver: unable to solve current LP relaxation; glp_simplex returned "+
+n+"");n=Sb;r=3;break}q=a.A.na;r=a.A.sa;if(q==dc&&r==dc)a.p.o>=mc&&x("Found optimal solution to LP relaxation");else if(r==jc){a.p.o>=Lb&&x("ios_driver: current LP relaxation has no dual feasible solution");n=Sb;r=3;break}else if(q==Ad&&r==dc){a.p.o>=mc&&x("LP relaxation has no solution better than incumbent objective value");r=2;break}else if(q==jc){a.p.o>=mc&&x("LP relaxation has no feasible solution");r=2;break}q=a.N.rc=a.A.aa;q=uf(a,q);a.A.dir==za?a.N.bound
+q&&(a.N.bound=q);a.p.o>=mc&&x("Local bound is "+q+"");if(!c(a,m)){a.p.o>=mc&&x("Current branch is hopeless and can be pruned");r=2;break}if(null!=a.p.ob){a.reason=Ga;a.p.ob(a,a.p.Uc);a.reason=0;if(a.stop){n=Rc;r=3;break}if(a.pe){a.pe=a.tf=0;r=1;break}a.tf&&(a.tf=0,Jb(a.A))}d(a);if(0==a.N.Ag){a.p.o>=mc&&x("New integer feasible solution found");a.p.o>=Wb&&k(a);e(a);a.p.o>=fc&&b(a,1);if(null!=a.p.ob&&(a.reason=Xf,a.p.ob(a,a.p.Uc),a.reason=0,a.stop)){n=Rc;r=3;break}r=2;break}a.A.za==dc&&g(a);if(null!=
+a.p.ob){a.reason=Yf;a.p.ob(a,a.p.Uc);a.reason=0;if(a.stop){n=Rc;r=3;break}if(!c(a,m)){a.p.o>=mc&&x("Current branch became hopeless and can be pruned");r=2;break}}if(a.p.Xe&&(a.reason=Yf,Zf(a),a.reason=0,!c(a,m))){a.p.o>=mc&&x("Current branch became hopeless and can be pruned");r=2;break}if(null!=a.p.ob&&(a.reason=Ia,a.p.ob(a,a.p.Uc),a.reason=0,a.stop)){n=Rc;r=3;break}if(0==a.N.La||0==t)a.reason=Ia,l(a),a.reason=0;0=Wb&&0==a.N.La&&k(a);null!=a.Fd&&ag(a);if(null!=a.p.ob&&(a.reason=bg,a.p.ob(a,a.p.Uc),a.reason=0,a.stop)){n=Rc;r=3;break}0==a.Tc&&(a.Tc=cg(a,function(b){a.Jf=b}));q=a.N.s;n=f(a,a.Tc,a.Jf);a.Tc=a.Jf=0;if(0==n){t=q;r=0;break}else if(1==n){a.N.eg=a.N.Rd=0;r=1;break}else if(2==n){r=2;break}case 2:a.p.o>=mc&&x("Node "+m+" fathomed");qf(a);sf(a,m);a.A.za==dc&&p(a);r=t=0;break;case 3:return a.p.o>=fc&&b(a,0),a.Xf=null,a.Ne=null,n}if(null==r)break;q=r}}
+function dg(a){var b;b={};b.i=a;b.L=0;b.Ja=new Int32Array(1+a);b.Z=new Int32Array(1+a);b.j=new Float64Array(1+a);return b}function eg(a,b,c){var d=a.Ja[b];0==c?0!=d&&(a.Ja[b]=0,dMath.abs(W))){switch(R){case Ra:return;case G:Q=-W;break;case Ua:Q=+W;break;case Na:continue}switch(H){case Fc:if(1E-10>Math.abs(Q-Math.floor(Q+0.5)))continue;else X=b(Q)<=
+b(F)?b(Q):b(F)/(1-b(F))*(1-b(Q));break;case Ma:X=0<=Q?+Q:b(F)/(1-b(F))*-Q}switch(R){case G:c[l]=+X;ca+=X*V;break;case Ua:c[l]=-X,ca-=X*O}}}for(d=1;d<=f;d++)if(!(1E-10>Math.abs(c[d])))for(R=e.n[d],Q=R.k;null!=Q;Q=Q.B)c[f+Q.f.C]+=c[d]*Q.j;D=0;for(d=1;d<=g;d++)1E-10>Math.abs(c[f+d])||(R=e.f[d],R.type==B?ca-=c[f+d]*R.c:(D++,h[D]=d,k[D]=c[f+d]));1E-12>Math.abs(ca)&&(ca=0);for(l=1;l<=D;l++)if(0.001>Math.abs(k[l])||1E3=l&&(e++,g[e].C=h,g[e].Nb=l))}ma(g,e,function(a,b){return a.Nb>b.Nb?-1:a.NbMath.abs(b-Math.floor(b+0.5)))b=1;else{h=b-Math.floor(b);for(c=1;c<=a;c++)l=g[c]-Math.floor(g[c])-h,g[c]=0>=l?Math.floor(g[c]):Math.floor(g[c])+l/(1-h);q=Math.floor(b);r=1/(1-h);b=0}if(b)return 1;for(c=1;c<=a;c++)e[c]&&(g[c]=-g[c],q+=g[c]*d[c]);r/=f;return 0}var h,l,m,n,p;m=Array(4);var t,v,y,D;y=new Int8Array(1+a);D=Array(1+a);for(l=1;l<=
+a;l++)y[l]=e[l]>=0.5*d[l];v=p=0;for(l=1;l<=a;l++)if(h=1E-9*(1+Math.abs(d[l])),!(e[l]d[l]-h||(h=k(a,b,c,d,y,Math.abs(b[l]),g),h))){t=-q-r*f;for(h=1;h<=a;h++)t+=g[h]*e[h];vv&&(v=0);if(0==v)return v;m[1]=p/2;m[2]=p/4;m[3]=p/8;for(l=1;3>=l;l++)if(h=k(a,b,c,d,y,m[l],g),!h){t=-q-r*f;for(h=1;h<=a;h++)t+=g[h]*e[h];vd[l]-h||(m++,D[m].C=l,D[m].xf=Math.abs(e[l]-0.5*d[l]));ma(D,m,function(a,
+b){return a.xfb.xf?1:0});for(n=1;n<=m;n++)if(l=D[n].C,y[l]=!y[l],h=k(a,b,c,d,y,p,g),y[l]=!y[l],!h){t=-q-r*f;for(h=1;h<=a;h++)t+=g[h]*e[h];vg[e]&&(g[e]=0);k=0;for(e=h+1;e<=a.G.L;e++)f=a.G.Z[e],a.lb[f]==lg?(g=a.Yb[f],g=0==g?a.x[f]-a.c[f]:a.x[f]-a.c[f]*a.x[g]):a.lb[f]==mg&&(g=a.zb[f],g=0==g?a.d[f]-a.x[f]:a.d[f]*a.x[g]-a.x[f]),
+0>g&&(g=0),k-=a.G.j[e]*g;k=c(h,a.G.j,a.Lb,l,g,k,m);if(0==k)return k;for(e=1;e<=h;e++)a.G.j[e]=m[e];for(e=h+1;e<=a.G.L;e++)f=a.G.Z[e],f<=b+d&&(a.G.j[e]*=0);a.Lb=null;return k}function k(a){var b,c,d,e;for(b=1;b<=a.G.L;b++)d=a.G.Z[b],a.ab[d]&&(a.lb[d]==lg?a.Lb+=a.G.j[b]*a.c[d]:a.lb[d]==mg&&(a.Lb-=a.G.j[b]*a.d[d],a.G.j[b]=-a.G.j[b]));for(b=1;b<=a.G.L;b++)d=a.G.Z[b],a.ab[d]||(a.lb[d]==lg?(e=a.Yb[d],0==e?a.Lb+=a.G.j[b]*a.c[d]:(c=a.G.Ja[e],0==c&&(eg(a.G,e,1),c=a.G.Ja[e],a.G.j[c]=0),a.G.j[c]-=a.G.j[b]*a.c[d])):
+a.lb[d]==mg&&(e=a.zb[d],0==e?a.Lb-=a.G.j[b]*a.d[d]:(c=a.G.Ja[e],0==c&&(eg(a.G,e,1),c=a.G.Ja[e],a.G.j[c]=0),a.G.j[c]+=a.G.j[b]*a.d[d]),a.G.j[b]=-a.G.j[b]))}function l(a,b){var c=a.A,d=b.g,e,f,g,h;for(f=b.G.L;1<=f;f--)if(e=b.G.Z[f],!(e>d)){for(e=c.n[e].k;null!=e;e=e.B)g=d+e.f.C,h=b.G.Ja[g],0==h&&(eg(b.G,g,1),h=b.G.Ja[g],b.G.j[h]=0),b.G.j[h]+=b.G.j[f]*e.j;b.G.j[f]=0}gg(b.G,0)}function p(a,b){var c=b.g,d=b.i,e,f,g=new Int32Array(1+d),h=new Float64Array(1+d);f=0;for(d=b.G.L;1<=d;d--)e=b.G.Z[d],f++,g[f]=
+e-c,h[f]=b.G.j[d];Id(a,Ef,f,g,h,Ta,b.Lb)}function m(a,b){var c=a.A,d=b.g,e=b.i,f,g,h,k=0,l=0,m,n=0;for(f=1;f<=b.Ya.L;f++)g=b.Ya.Z[f],g<=d||b.ab[g]||0.001>Math.abs(b.Ya.j[f])||(h=b.Yb[g],m=0==h?b.c[g]==-s?s:b.x[g]-b.c[g]:b.x[g]-b.c[g]*b.x[h],h=b.zb[g],h=0==h?b.zb[g]==+s?s:b.d[g]-b.x[g]:b.d[g]*b.x[h]-b.x[g],m=m<=h?m:h,!(0.001>m)&&nd)return 2;b.Je++;b.Ef[b.Je]=
+g;b.Eb[g]=2;e=dg(d+e);eg(e,g,1);for(f=c.n[g].k;null!=f;f=f.B)eg(e,d+f.f.C,-f.j);f=b.Ya.Ja[k];c=b.Ya;d=-b.Ya.j[f]/e.j[e.Ja[k]];for(g=1;g<=e.L;g++)f=e.Z[g],n=void 0,n=c.Ja[f],n=0==n?0:c.j[n],m=e.j[g],eg(c,f,n+d*m);eg(b.Ya,k,0);return l}var q,r,n=b.g,t=b.i,y,E,C;(function(a,b){var c=a.A,d=b.g,e=b.i,f;for(f=1;f<=d;f++)b.x[f]=c.n[f].r;for(f=d+1;f<=d+e;f++)b.x[f]=c.f[f-d].r})(a,b);ha(b.lb,1,kg,n+t);for(y=1;y<=n;y++)if(!b.Eb[y]){for(d(a,b,y);;){e(b);if(ig)for(E=1;E<=n+t;E++);f(b);g(b);C=h(b);0f[h]){g=e(a,g);if(g==+s){k=-s;break}k+=f[h]*g}return k}function h(a,b,c,f){var g,h,k;k=0;for(h=1;h<=b;h++)if(g=c[h],0f[h]){g=d(a,g);if(g==-s){k=+s;break}k+=f[h]*g}return k}function k(a,b,c,d,e,f,g,h){b!=-s&&g&&(b-=a[f]);c!=+s&&g&&(c-=a[f]);d!=-s&&(0>a[f]&&(d-=a[f]),0>
+a[h]&&(d-=a[h]));e!=+s&&(0f?1:0}var l=null,p,m,q,r,n,t,y,E,C,D,H,R,V,O,Q,F;x("Creating the conflict graph...");p=kb(a);m=lb(a);q=0;D=new Int32Array(1+m);H=new Int32Array(1+m);C=new Int32Array(1+m);F=new Float64Array(1+m);for(r=1;r<=p;r++)if(R=b(a,r),V=c(a,r),R!=-s||V!=+s)if(E=ub(a,r,C,F),!(500c?f(a,b*(b-1)/2+c):f(a,c*(c-1)/2+b)}function f(a,b){return a.Jc[b/1]&1<<0-b%1}function g(a,b,c,d,f,h){var k,l,m,n,q,p,r,ca;ca=new Int32Array(a.i);if(0>=b){if(0==b&&(a.set[d++]=c[0],f+=h),f>a.gd)for(a.gd=f,a.bg=d,k=0;kk&&(k=0),100l||f.Ic[h+1]==l&&q[h]>m)&&(l=f.Ic[h+1],m=q[h],k=h);b[a]=k;n[k]=1;for(h=0;h=q;q+=2){p=Fd(c,r,n,R,q,1E-9);0!=
+p&&(p=n[p]);if(0==p)p=a.A.dir==za?+s:-s;else{for(m=1;m<=r&&n[m]!=p;m++);m=R[m];y=(0>q?Math.floor(t):Math.ceil(t))-t;y/=m;p>f&&Ic(c,p-f)!=Ma&&0.001p||m==Ua&&0p||m==Ra)p=0}p*=y}0>q?e=p:E=p}if(H=mc&&(x("branch_drtom: column "+l+" chosen to branch on"),Math.abs(C)==s?x("branch_drtom: down-branch is infeasible"):x("branch_drtom: down-branch bound is "+(yc(c)+C)+""),Math.abs(D)==s?x("branch_drtom: up-branch is infeasible"):x("branch_drtom: up-branch bound is "+(yc(c)+D)+"")),b(d));c=l}else a.p.Jb==cd&&(c=pg(a,b));return c}
+function og(a,b){var c,d,e,f,g,h;d=0;g=s;for(c=1;c<=a.i;c++)a.ad[c]&&(f=Dc(a.A,c),h=Math.floor(f)+0.5,g>Math.abs(f-h)&&(d=c,g=Math.abs(f-h),e=fc?(d.yd[b]++,d.Ud[b]+=a):(d.Id[b]++,d.ye[b]+=a))}
+function pg(a,b){function c(a,b,c){var d,e;xc(a);d=Ba();hb(d,a,0);Wa(d,b,B,c,c);b=new kc;b.o=lc;b.cb=Tb;b.oc=30;b.fb=1E3;b.cb=Tb;b=sc(d,b);0==b||b==rg?tc(d)==jc?e=s:uc(d)==dc?(a.dir==za?e=d.aa-a.aa:a.dir==Ea&&(e=a.aa-d.aa),e<1E-6*(1+0.001*Math.abs(a.aa))&&(e=0)):e=0:e=0;return e}function d(a,b,d){var e=a.Fd,f;if(d==Bf){if(0==e.yd[b]){d=a.A.f[b].r;a=c(a.A,b,Math.floor(d));if(a==s)return f=s;e.yd[b]=1;e.Ud[b]=a/(d-Math.floor(d))}f=e.Ud[b]/e.yd[b]}else if(d==Cf){if(0==e.Id[b]){d=a.A.f[b].r;a=c(a.A,b,
+Math.ceil(d));if(a==s)return f=s;e.Id[b]=1;e.ye[b]=a/(Math.ceil(d)-d)}f=e.ye[b]/e.Id[b]}return f}function e(a){var b=a.Fd,c,d=0,e=0;for(c=1;c<=a.i;c++)Jd(a,c)&&(d++,0l?m:l;q=bb&&10<=la(f)&&(e(a),f=ja())}if(0==q)return h=og(a,b);b(k);return h}
+function Zf(a){var b=a.A,c=b.i,d=null,e=null,f=null,g,h,k,l,p,m,q,r;for(l=0;;){var n=null;switch(l){case 0:xc(b);if(0!=a.N.La||1!=a.N.eg){n=5;break}q=0;for(k=1;k<=c;k++)if(g=b.f[k],g.kind!=Ma&&g.type!=B)if(g.type==I&&0==g.c&&1==g.d)q++;else{a.p.o>=Wb&&x("FPUMP heuristic cannot be applied due to general integer variables");n=5;break}if(null!=n)break;if(0==q){n=5;break}a.p.o>=Wb&&x("Applying FPUMP heuristic...");e=Array(1+q);ia(e,1,q);l=0;for(k=1;k<=c;k++)g=b.f[k],g.kind==Fc&&g.type==I&&(e[++l].C=k);
+d=Ba();case 1:hb(d,b,cb);if(b.za==dc){La(d,1);p=new Int32Array(1+c);m=new Float64Array(1+c);for(k=1;k<=c;k++)p[k]=k,m[k]=b.f[k].u;Ya(d,d.g,c,p,m);p=0.1*b.aa+0.9*b.ta;b.dir==za?Va(d,d.g,Ta,0,p-b.ha):b.dir==Ea&&Va(d,d.g,Sa,p-b.ha,0)}m=0;for(l=1;l<=q;l++)e[l].x=-1;case 2:if(m++,a.p.o>=Wb&&x("Pass "+m+""),r=s,p=0,1n&&(n=0),g=Math.abs(e[l].x-g.r),0.5g.r?0:1,
+e[l].x!=g&&(h=0,e[l].x=g);if(h){for(l=1;l<=q;l++)g=d.f[e[l].C],e[l].Td=Math.abs(g.r-e[l].x);ma(e,q,function(a,b){return a.Td>b.Td?-1:a.Tde[l].Td||10<=l);l++)e[l].x=1-e[l].x}case 4:if(2147483647>a.p.sb&&a.p.sb-1<=1E3*la(a.hc)){n=5;break}d.dir=za;d.ha=0;for(k=1;k<=c;k++)d.f[k].u=0;for(l=1;l<=q;l++)k=e[l].C,0==e[l].x?d.f[k].u=1:(d.f[k].u=-1,d.ha+=1);h=new kc;a.p.o<=Lb?h.o=a.p.o:a.p.o<=Wb&&(h.o=fc,h.fb=1E4);l=sc(d,h);if(0!=l){a.p.o>=Lb&&x("Warning: glp_simplex returned "+
+l+"");n=5;break}l=xc(d);if(l!=vc){a.p.o>=Lb&&x("Warning: glp_get_status returned "+l+"");n=5;break}a.p.o>=mc&&x("delta = "+d.aa+"");k=0.3*a.p.Ub;for(l=1;l<=q&&!(g=d.f[e[l].C],kq){g=new Float64Array(1+c);for(k=1;k<=c;k++)g[k]=d.f[k].r,b.f[k].kind==Fc&&(g[k]=Math.floor(g[k]+0.5));d.ha=b.ha;d.dir=b.dir;for(l=1;l<=q;l++)d.f[e[l].C].c=g[e[l].C],d.f[e[l].C].d=g[e[l].C],d.f[e[l].C].type=B;for(k=1;k<=c;k++)d.f[k].u=b.f[k].u;l=sc(d,h);if(0!=l){a.p.o>=Lb&&x("Warning: glp_simplex returned "+
+l+"");n=5;break}l=xc(d);if(l!=vc){a.p.o>=Lb&&x("Warning: glp_get_status returned "+l+"");n=5;break}for(k=1;k<=c;k++)b.f[k].kind!=Fc&&(g[k]=d.f[k].r);l=Kd(a,g);if(0==l){n=vf(a,a.N.bound)?1:5;break}}r==s||d.aa<=r-1E-6*(1+r)?(p=0,r=d.aa):p++;if(3>p){n=3;break}5>m&&(n=2)}if(null==n)break;l=n}}
+function $f(a){function b(a,b,c){var d,e=0,f=0,g=0;for(d=a.k;null!=d;d=d.e)c[d.C]=d.j,f+=d.j*d.j;for(d=b.k;null!=d;d=d.e)e+=c[d.C]*d.j,g+=d.j*d.j;for(d=a.k;null!=d;d=d.e)c[d.C]=0;a=Math.sqrt(f)*Math.sqrt(g);4.930380657631324E-32>a&&(a=2.220446049250313E-16);return e/a}var c,d,e,f,g,h,k,l,p,m;c=a.Bd;f=Array(1+c.size);l=new Int32Array(1+a.i);p=new Float64Array(1+a.i);m=new Float64Array(1+a.i);g=0;for(d=c.head;null!=d;d=d.e)g++,f[g].Re=d,f[g].ba=0;for(g=1;g<=c.size;g++){var q=null,r=null;d=f[g].Re;h=
+k=0;for(e=d.k;null!=e;e=e.e)k++,l[k]=e.C,p[k]=e.j,h+=e.j*e.j;4.930380657631324E-32>h&&(h=2.220446049250313E-16);k=Dd(a.A,k,l,p);d=Gd(a.A,k,l,p,d.type,d.cg,function(a,b,c,d,e,f){q=e;r=f});0==d?(f[g].Xc=Math.abs(q)/Math.sqrt(h),a.A.dir==za?(0>r&&(r=0),f[g].Ab=+r):(0f[g].Ab&&(f[g].Ab=0)}ma(f,c.size,function(a,b){if(0==a.Ab&&0==b.Ab){if(a.Xc>b.Xc)return-1;if(a.Xcb.Ab)return-1;if(a.Abc.size&&(h=c.size);for(g=1;g<=h;g++)if(!(0.01>f[g].Ab&&0.01>f[g].Xc)){for(c=1;ca.R.Zc&&(b=a.s,c=a.R.Zc);return b}function c(a){var b,c,d,e,p;b=a.ya[1].rb;e=(a.A.ta-b.bound)/b.Zc;c=0;d=s;for(b=a.head;null!=b;b=b.e)p=b.R.bound+e*b.R.Zc,a.A.dir==Ea&&(p=-p),d>p&&(c=b.s,d=p);return c}function d(a){var b,c=null,d,e;switch(a.A.dir){case za:d=+s;for(b=a.head;null!=b;b=b.e)d>b.bound&&(d=b.bound);e=0.001*(1+Math.abs(d));for(b=a.head;null!=b;b=b.e)b.bound<=d+e&&(null==c||c.R.Zc>b.R.Zc)&&(c=b);break;case Ea:d=-s;
+for(b=a.head;null!=b;b=b.e)d=d-e&&(null==c||c.rc=a/Math.pow(2,b)?b-1:b)}function vg(a,b){var c=Number(a);if(isNaN(c))return 2;switch(c){case Number.POSITIVE_INFINITY:case Number.NEGATIVE_INFINITY:return 1;default:return b(c),0}}
+function wg(a,b){var c=Number(a);if(isNaN(c))return 2;switch(c){case Number.POSITIVE_INFINITY:case Number.NEGATIVE_INFINITY:return 1;default:return 0==c%1?(b(c),0):2}}function xg(a,b,c){var d,e;if(!(1<=a&&31>=a&&1<=b&&12>=b&&1<=c&&4E3>=c))return-1;3<=b?b-=3:(b+=9,c--);d=c/100|0;c=(146097*d/4|0)+(1461*(c-100*d)/4|0);c+=(153*b+2)/5|0;c+=a+1721119;yg(c,function(a){e=a});a!=e&&(c=-1);return c}
+function yg(a,b){var c,d,e;1721426<=a&&3182395>=a&&(a-=1721119,e=(4*a-1)/146097|0,c=(4*a-1)%146097/4|0,a=(4*c+3)/1461|0,c=((4*c+3)%1461+4)/4|0,d=(5*c-3)/153|0,c=(5*c-3)%153,c=(c+5)/5|0,e=100*e+a,9>=d?d+=3:(d-=9,e++),b(c,d,e))}var Ee=1;LPF_ECOND=2;LPF_ELIMIT=3;var we=0;function Me(a,b,c,d){var e=a.i,f=a.Md,g=a.Ld,h=a.Wb;a=a.Xb;var k,l,p,m;for(k=1;k<=e;k++){m=0;l=f[k];for(p=l+g[k];lM&&(M=-M),f=L)){S=p[V];if(0>S){for(F=O;F<=Q;F++)M=l[F],0>M&&(M=-M),SM&&(M=-M);if(!(MS){for(F=O;F<=Q;F++)M=l[F],0>M&&(M=-M),S=L)&&(M=l[F],0>M&&(M=-M),!(Mv&&(v=-v),Q[z]=0,P--,0==v||vk[u]){if(Ze(a,u,h[u]+P))return ca=1;M=g[b];ba=M+h[b]-1;l=p[c];J=l+m[c]-1}ka=0;for(P=M;P<=ba;P++)z=r[P],Q[z]?(v=S=-ea*F[z],0>v&&(v=-v),0==v||vq[z]){if($e(a,z,m[z]+10))return ca=1;M=g[b];ba=M+h[b]-1;l=p[c];J=l+m[c]-1}v=p[z]+m[z];r[v]=u;m[z]++}D[u]=0;H[u]=C[h[u]];0!=H[u]&&(D[H[u]]=u);C[h[u]]=u;E[u]=-1;if(1>a.Ma-a.Fa){af(a);if(1>a.Ma-a.Fa)return ca=1;M=g[b];
+ba=M+h[b]-1;l=p[c];J=l+m[c]-1}a.Ma--;r[a.Ma]=u;n[a.Ma]=ea;f[b]++}q[c]=0;L=d+c;0==t[L]?a.ld=y[L]:y[t[L]]=y[L];0==y[L]?a.Sb=t[L]:t[y[L]]=t[L];e[b]=a.Ma;for(P=M;P<=ba;P++)if(z=r[P],Q[z]=0,F[z]=0,1==m[z]||V[z]!=z||O[z]!=z)V[z]=0,O[z]=R[m[z]],0!=O[z]&&(V[O[z]]=z),R[m[z]]=z;return ca}
+function Yg(a){var b=a.i,c=a.Fc,d=a.Ec,e=a.Dc,f=a.Cc,g=a.Rc,h=a.wb,k=a.xb,l=a.Hd,p=a.md,m,q,r,n;n=0;for(m=1;m<=b;m++){q=c[m];for(r=q+d[m]-1;q<=r;q++)g[h[q]]++;n+=d[m]}a.Qb=n;if(a.Ma-a.Fal*a.Cg)return a.pa=q-1,y=Ae,!1}af(a);return Yg(a)||Zg(a)?(a.Va=a.Ga+a.Ga,!0):!1}var f,g,h,k,l=a.sc,p,m,q,r,n,t,y=null;1>b&&w("luf_factorize: n = "+
+b+"; invalid parameter");1E8b&&(b=$g),a.Vc++,b==$g?"\n"==a.l?a.bb--:ok(a,"final NL missing before end of file"):"\n"!=b&&(0<=" \t\n\v\f\r".indexOf(b)?b=" ":ta(b)&&(mk(a),U(a,"control character "+b+" not allowed"))),a.l=b)}function pk(a){a.h+=a.l;a.Bb++;nk(a)}
+function Y(a){function b(){mk(a);U(a,"keyword s.t. incomplete")}function c(){mk(a);U(a,"cannot convert numeric literal "+a.h+" to floating-point number")}function d(){if("e"==a.l||"E"==a.l)for(pk(a),"+"!=a.l&&"-"!=a.l||pk(a),wa(a.l)||(mk(a),U(a,"numeric literal "+a.h+" incomplete"));wa(a.l);)pk(a);if(ua(a.l)||"_"==a.l)mk(a),U(a,"symbol "+a.h+a.l+"... should be enclosed in quotes")}a.Hf=a.b;a.Gf=a.Bb;a.Ff=a.h;a.If=a.value;if(a.Ue)a.Ue=0,a.b=a.Mf,a.Bb=a.Lf,a.h=a.Kf,a.value=a.Nf;else{for(;;){a.b=0;a.Bb=
+0;a.h="";for(a.value=0;" "==a.l||"\n"==a.l;)nk(a);if(a.l==$g)a.b=Ah;else if("#"==a.l){for(;"\n"!=a.l&&a.l!=$g;)nk(a);continue}else if(a.nc||!ua(a.l)&&"_"!=a.l)if(!a.nc&&wa(a.l)){for(a.b=Dh;wa(a.l);)pk(a);var e=!1;if("."==a.l)if(pk(a),"."==a.l)a.Bb--,a.h=a.h.substr(0,a.h.length-1),a.Te=1,e=!0;else for(;wa(a.l);)pk(a);e||d();vg(a.h,function(b){a.value=b})&&c()}else if("'"==a.l||'"'==a.l){var f=a.l,g=!1;a.b=Eh;nk(a);e=function(){for(;;){if("\n"==a.l&&!g||a.l==$g)mk(a),U(a,"unexpected end of line; string literal incomplete");
+if(a.l==f)if(nk(a),a.l==f){if(g)if(nk(a),a.l==f){nk(a);break}else a.h+='""',a.Bb+=2}else if(g)a.h+='"',a.Bb++;else break;pk(a)}};a.l==f?(nk(a),a.l==f&&(g=!0,nk(a),e())):e()}else if(a.nc||"+"!=a.l)if(a.nc||"-"!=a.l)if("*"==a.l)a.b=$h,pk(a),"*"==a.l&&(a.b=bi,pk(a));else if("/"==a.l){if(a.b=ai,pk(a),"*"==a.l){for(nk(a);;)if(a.l==$g)U(a,"unexpected end of file; comment sequence incomplete");else if("*"==a.l){if(nk(a),"/"==a.l)break}else nk(a);nk(a);continue}}else if("^"==a.l)a.b=bi,pk(a);else if("<"==
+a.l)a.b=ci,pk(a),"="==a.l?(a.b=di,pk(a)):">"==a.l?(a.b=hi,pk(a)):"-"==a.l&&(a.b=yi,pk(a));else if("="==a.l)a.b=ei,pk(a),"="==a.l&&pk(a);else if(">"==a.l)a.b=gi,pk(a),"="==a.l?(a.b=fi,pk(a)):">"==a.l&&(a.b=wi,pk(a));else if("!"==a.l)a.b=Rh,pk(a),"="==a.l&&(a.b=hi,pk(a));else if("&"==a.l)a.b=ii,pk(a),"&"==a.l&&(a.b=Fh,pk(a));else if("|"==a.l)a.b=ji,pk(a),"|"==a.l&&(a.b=Sh,pk(a));else if(a.nc||"."!=a.l)if(","==a.l)a.b=li,pk(a);else if(":"==a.l)a.b=mi,pk(a),"="==a.l&&(a.b=oi,pk(a));else if(";"==a.l)a.b=
+ni,pk(a);else if("("==a.l)a.b=qi,pk(a);else if(")"==a.l)a.b=ri,pk(a);else if("["==a.l)a.b=si,pk(a);else if("]"==a.l)a.b=ti,pk(a);else if("{"==a.l)a.b=ui,pk(a);else if("}"==a.l)a.b=vi,pk(a);else if("~"==a.l)a.b=xi,pk(a);else if(va(a.l)||0<="+-._".indexOf(a.l)){for(a.b=Ch;va(a.l)||0<="+-._".indexOf(a.l);)pk(a);switch(vg(a.h,function(b){a.value=b})){case 0:a.b=Dh;break;case 1:c()}}else mk(a),U(a,"character "+a.l+" not allowed");else if(a.b=ki,pk(a),a.Te)a.b=pi,a.Bb=2,a.h="..",a.Te=0;else if("."==a.l)a.b=
+pi,pk(a);else{if(wa(a.l)){a.b=Dh;for(pk(a);wa(a.l);)pk(a);d();vg(a.h,function(b){a.value=b})&&c()}}else a.b=Zh,pk(a);else a.b=Yh,pk(a);else{for(a.b=Bh;va(a.l)||"_"==a.l;)pk(a);"and"==a.h?a.b=Fh:"by"==a.h?a.b=Gh:"cross"==a.h?a.b=Hh:"diff"==a.h?a.b=Ih:"div"==a.h?a.b=Jh:"else"==a.h?a.b=Kh:"if"==a.h?a.b=Lh:"in"==a.h?a.b=Mh:"Infinity"==a.h?a.b=Nh:"inter"==a.h?a.b=Oh:"less"==a.h?a.b=Ph:"mod"==a.h?a.b=Qh:"not"==a.h?a.b=Rh:"or"==a.h?a.b=Sh:"s"==a.h&&"."==a.l?(a.b=Th,pk(a),"t"!=a.l&&b(),pk(a),"."!=a.l&&b(),
+pk(a)):"symdiff"==a.h?a.b=Uh:"then"==a.h?a.b=Vh:"union"==a.h?a.b=Wh:"within"==a.h&&(a.b=Xh)}break}mk(a);a.Pf=0}}function qk(a){a.Ue=1;a.Mf=a.b;a.Lf=a.Bb;a.Kf=a.h;a.Nf=a.value;a.b=a.Hf;a.Bb=a.Gf;a.h=a.Ff;a.value=a.If}function rk(a,b){return a.b==Bh&&a.h==b}function sk(a){return a.b==Fh&&"a"==a.h[0]||a.b==Gh||a.b==Hh||a.b==Ih||a.b==Jh||a.b==Kh||a.b==Lh||a.b==Mh||a.b==Oh||a.b==Ph||a.b==Qh||a.b==Rh&&"n"==a.h[0]||a.b==Sh&&"o"==a.h[0]||a.b==Uh||a.b==Vh||a.b==Wh||a.b==Xh}
+function tk(a,b,c,d){var e={};e.Ta=a;e.T=0;e.a=lk();e.value={};switch(a){case Fi:e.a.Q=b.Q;break;case Gi:e.a.M=b.M;break;case Hi:e.a.index.ya=b.index.ya;e.a.index.e=b.index.e;break;case Ii:case Ji:for(a=b.S.list;null!=a;a=a.e)a.x.R=e,e.T|=a.x.T;e.a.S.S=b.S.S;e.a.S.list=b.S.list;break;case Ki:for(a=b.set.list;null!=a;a=a.e)a.x.R=e,e.T|=a.x.T;e.a.set.set=b.set.set;e.a.set.list=b.set.list;break;case Li:for(a=b.t.list;null!=a;a=a.e)a.x.R=e,e.T|=a.x.T;e.a.t.t=b.t.t;e.a.t.list=b.t.list;e.a.t.Ac=b.t.Ac;
+break;case Mi:for(a=b.H.list;null!=a;a=a.e)a.x.R=e,e.T|=a.x.T;e.a.H.H=b.H.H;e.a.H.list=b.H.list;e.a.H.Ac=b.H.Ac;break;case Ni:case Oi:for(a=b.list;null!=a;a=a.e)a.x.R=e,e.T|=a.x.T;e.a.list=b.list;break;case Pi:e.a.slice=b.slice;break;case Qi:case Ri:case Si:case Ti:e.T=1;break;case Ui:case Vi:case Wi:case Xi:case Yi:case Zi:case $i:case aj:case bj:case cj:case dj:case ej:case fj:case gj:case hj:case ij:case jj:case kj:case lj:case mj:case nj:case oj:b.a.x.R=e;e.T|=b.a.x.T;e.a.a.x=b.a.x;break;case pj:case qj:case rj:case sj:case tj:case uj:case vj:case wj:case xj:case yj:case zj:case Aj:a==
+Aj&&(e.T=1);case Bj:a==Bj&&(e.T=1);case Cj:case Dj:case Ej:case Fj:case Gj:case Hj:case Ij:case Jj:case Kj:case Lj:case Mj:case Nj:case Oj:case Pj:case Qj:case Rj:case Sj:case Tj:case Uj:case Vj:case Xj:b.a.x.R=e;e.T|=b.a.x.T;b.a.y.R=e;e.T|=b.a.y.T;e.a.a.x=b.a.x;e.a.a.y=b.a.y;break;case Yj:case Zj:case ak:b.a.x.R=e;e.T|=b.a.x.T;b.a.y.R=e;e.T|=b.a.y.T;null!=b.a.z&&(b.a.z.R=e,e.T|=b.a.z.T);e.a.a.x=b.a.x;e.a.a.y=b.a.y;e.a.a.z=b.a.z;break;case bk:case ck:for(a=b.list;null!=a;a=a.e)a.x.R=e,e.T|=a.x.T;
+e.a.list=b.list;break;case dk:case ek:case fk:case gk:case hk:case ik:case jk:case kk:a=b.loop.domain;null!=a.code&&(a.code.R=e,e.T|=a.code.T);for(a=a.list;null!=a;a=a.e)a.code.R=e,e.T|=a.code.T;null!=b.loop.x&&(b.loop.x.R=e,e.T|=b.loop.x.T);e.a.loop.domain=b.loop.domain;e.a.loop.x=b.loop.x}e.type=c;e.q=d;e.R=null;e.valid=0;e.value={};return e}function Z(a,b,c,d){var e=lk();e.a.x=b;return tk(a,e,c,d)}function uk(a,b,c,d,e){var f=lk();f.a.x=b;f.a.y=c;return tk(a,f,d,e)}
+function vk(a,b,c,d,e,f){var g=lk();g.a.x=b;g.a.y=c;g.a.z=d;return tk(a,g,e,f)}function wk(a,b){var c={},d;c.x=b;c.e=null;if(null==a)a=c;else{for(d=a;null!=d.e;d=d.e);d.e=c}return a}function xk(a){var b;for(b=0;null!=a;a=a.e)b++;return b}
+function yk(a){var b,c,d,e,f,g,h,k,l,p=lk(),m=a.V[a.h];null==m&&U(a,a.h+" not defined");switch(m.type){case kh:b=m.link;k=b.name;l=0;break;case uh:c=m.link;k=c.name;l=c.q;0==c.X&&(c.X=1);break;case sh:d=m.link;k=d.name;l=d.q;break;case yh:e=m.link;k=e.name;l=e.q;break;case ch:f=m.link,k=f.name,l=f.q}Y(a);if(a.b==si){0==l&&U(a,k+" cannot be subscripted");Y(a);for(var q=null;;)if(g=zk(a),g.type==N&&(g=Z(Vi,g,T,0)),g.type!=T&&U(a,"subscript expression has invalid type"),q=wk(q,g),a.b==li)Y(a);else if(a.b==
+ti)break;else U(a,"syntax error in subscript list");g=q;l!=xk(g)&&U(a,k+" must have "+l+" subscript"+(1==l?"":"s")+" rather than "+xk(g));Y(a)}else 0!=l&&U(a,k+" must be subscripted"),g=null;l=a.Ob||m.type!=yh?Di:zi;a.b==ki&&(Y(a),a.b!=Bh&&U(a,"invalid use of period"),m.type!=yh&&m.type!=ch&&U(a,k+" cannot have a suffix"),"lb"==a.h?l=Ai:"ub"==a.h?l=Bi:"status"==a.h?l=Ci:"val"==a.h?l=Di:"dual"==a.h?l=Ei:U(a,"suffix ."+a.h+" invalid"),Y(a));switch(m.type){case kh:p.index.ya=b;p.index.e=b.list;h=tk(Hi,
+p,T,0);b.list=h;break;case uh:p.set.set=c;p.set.list=g;h=tk(Ki,p,fh,c.X);break;case sh:p.S.S=d;p.S.list=g;h=d.type==T?tk(Ji,p,T,0):tk(Ii,p,N,0);break;case yh:a.Ob||l!=Ci&&l!=Di&&l!=Ei||U(a,"invalid reference to status, primal value, or dual value of variable "+e.name+" above solve statement");p.t.t=e;p.t.list=g;p.t.Ac=l;h=tk(Li,p,l==zi?jh:N,0);break;case ch:a.Ob||l!=Ci&&l!=Di&&l!=Ei||U(a,"invalid reference to status, primal value, or dual value of "+(f.type==ch?"constraint":"objective")+" "+f.name+
+" above solve statement"),p.H.H=f,p.H.list=g,p.H.Ac=l,h=tk(Mi,p,N,0)}return h}function Ak(a,b){var c=zk(a);c.type==T&&(c=Z(Ui,c,N,0));c.type!=N&&U(a,"argument for "+b+" has invalid type");return c}function Bk(a,b){var c=zk(a);c.type==N&&(c=Z(Vi,c,T,0));c.type!=T&&U(a,"argument for "+b+" has invalid type");return c}function Ck(a,b,c){var d={};d.name=b;d.code=c;d.value=null;d.list=null;d.e=null;if(null==a.list)a.list=d;else{for(a=a.list;null!=a.e;a=a.e);a.e=d}}
+function Dk(a){var b,c=lk(),d=Array(21);ia(d,0,21);var e,f,g,h=0;e=a.Pf;Y(a);for(g=1;;g++){20=a.value&&Math.floor(a.value)==a.value||U(a,"dimension must be integer between 1 and 20");l=a.value+0.5|0;h&&U(a,"at most one dimension attribute allowed");0=k.set.X&&d();0==f.X&&(f.X=k.set.X-f.q);f.q+f.X>k.set.X?d():f.q+f.X= has invalid type");else if(a.b==di)null!=e.W&&(e.W==e.P?U(a,"both fixed value and upper bound not allowed"):U(a,"at most one upper bound allowed")),Y(a),e.W=zk(a),e.W.type==T&&(e.W=Z(Ui,e.W,N,0)),e.W.type!=N&&U(a,"expression following <= has invalid type");else if(a.b==ei){if(null!=e.P||null!=e.W)e.P==e.W?U(a,"at most one fixed value allowed"):null!=e.P?U(a,"both lower bound and fixed value not allowed"):U(a,"both upper bound and fixed value not allowed");f=a.h;Y(a);
+e.P=zk(a);e.P.type==T&&(e.P=Z(Ui,e.P,N,0));e.P.type!=N&&U(a,"expression following "+f+" has invalid type");e.W=e.P}else a.b==ci||a.b==gi||a.b==hi?U(a,"strict bound not allowed"):U(a,"syntax error in variable statement")}null!=e.domain&&Hk(a,e.domain);Y(a);return e}
+function al(a){function b(){U(a,"syntax error in constraint statement")}var c,d,e,f;a.Ob&&U(a,"constraint statement must precede solve statement");rk(a,"subject")?(Y(a),rk(a,"to")||U(a,"keyword subject to incomplete"),Y(a)):rk(a,"subj")?(Y(a),rk(a,"to")||U(a,"keyword subj to incomplete"),Y(a)):a.b==Th&&Y(a);a.b!=Bh&&(sk(a)?U(a,"invalid use of reserved keyword "+a.h):U(a,"symbolic name missing where expected"));null!=a.V[a.h]&&U(a,a.h+" multiply declared");var g={};g.name=a.h;g.Ib=null;g.q=0;g.domain=
+null;g.type=ch;g.code=null;g.P=null;g.W=null;g.O=null;Y(a);a.b==Eh&&(g.Ib=a.h,Y(a));a.b==ui&&(g.domain=Fk(a),g.q=Mk(g.domain));c=a.V[g.name]={};c.type=ch;c.link=g;a.b!=mi&&U(a,"colon missing where expected");Y(a);c=zk(a);c.type==T&&(c=Z(Ui,c,N,0));c.type!=N&&c.type!=jh&&U(a,"expression following colon has invalid type");a.b==li&&Y(a);switch(a.b){case di:case fi:case ei:break;case ci:case gi:case hi:U(a,"strict inequality not allowed");break;case ni:U(a,"constraint must be equality or inequality");
+break;default:b()}f=a.b;e=a.h;Y(a);d=zk(a);d.type==T&&(d=Z(Ui,d,N,0));d.type!=N&&d.type!=jh&&U(a,"expression following "+e+" has invalid type");a.b==li&&(Y(a),a.b==ni&&b());a.b==ci||a.b==di||a.b==ei||a.b==fi||a.b==gi||a.b==hi?(f!=ei&&a.b==f||U(a,"double inequality must be ... <= ... <= ... or ... >= ... >= ..."),c.type==jh&&U(a,"leftmost expression in double inequality cannot be linear form"),Y(a),e=zk(a),e.type==T&&(e=Z(Ui,d,N,0)),e.type!=N&&e.type!=jh&&U(a,"rightmost expression in double inequality constraint has invalid type"),
+e.type==jh&&U(a,"rightmost expression in double inequality cannot be linear form")):e=null;null!=g.domain&&Hk(a,g.domain);c.type!=jh&&(c=Z(Yi,c,jh,0));d.type!=jh&&(d=Z(Yi,d,jh,0));null!=e&&(e=Z(Yi,e,jh,0));if(null==e)switch(f){case di:g.code=c;g.P=null;g.W=d;break;case fi:g.code=c;g.P=d;g.W=null;break;case ei:g.code=c,g.P=d,g.W=d}else switch(f){case di:g.code=d;g.P=c;g.W=e;break;case fi:g.code=d,g.P=e,g.W=c}a.b!=ni&&b();Y(a);return g}
+function bl(a){var b,c={domain:null};c.list=b=null;Y(a);a.b==ui&&(c.domain=Fk(a));for(a.b==mi&&Y(a);;){var d={v:{}},e=function(){d.type=hh;d.v.code=Ek(a)};d.type=0;d.e=null;null==c.list?c.list=d:b.e=d;b=d;if(a.b==Bh){var f;Y(a);f=a.b;qk(a);if(f!=li&&f!=ni)e();else{e=a.V[a.h];null==e&&U(a,a.h+" not defined");d.type=e.type;switch(e.type){case kh:d.v.ya=e.link;break;case uh:d.v.set=e.link;break;case sh:d.v.S=e.link;break;case yh:d.v.t=e.link;a.Ob||U(a,"invalid reference to variable "+d.v.t.name+" above solve statement");
+break;case ch:d.v.H=e.link,a.Ob||U(a,"invalid reference to "+(d.v.H.type==ch?"constraint":"objective")+" "+d.v.H.name+" above solve statement")}Y(a)}}else e();if(a.b==li)Y(a);else break}null!=c.domain&&Hk(a,c.domain);a.b!=ni&&U(a,"syntax error in display statement");Y(a);return c}
+function cl(a){!a.nc&&rk(a,"end")||a.nc&&dl(a,"end")?(Y(a),a.b==ni?Y(a):ok(a,"no semicolon following end statement; missing semicolon inserted")):ok(a,"unexpected end of file; missing end statement inserted");a.b!=Ah&&ok(a,"some text detected beyond end statement; text ignored")}
+function el(a,b){var c={v:{}};c.bb=a.bb;c.Vc=a.Vc;c.e=null;if(rk(a,"set"))b&&U(a,"set statement not allowed here"),c.type=uh,c.v.set=Yk(a);else if(rk(a,"param"))b&&U(a,"parameter statement not allowed here"),c.type=sh,c.v.S=Zk(a);else if(rk(a,"var"))b&&U(a,"variable statement not allowed here"),c.type=yh,c.v.t=$k(a);else if(rk(a,"subject")||rk(a,"subj")||a.b==Th)b&&U(a,"constraint statement not allowed here"),c.type=ch,c.v.H=al(a);else if(rk(a,"minimize")||rk(a,"maximize")){b&&U(a,"objective statement not allowed here");
+c.type=ch;var d=c.v,e,f;rk(a,"minimize")?f=ph:rk(a,"maximize")&&(f=oh);a.Ob&&U(a,"objective statement must precede solve statement");Y(a);a.b!=Bh&&(sk(a)?U(a,"invalid use of reserved keyword "+a.h):U(a,"symbolic name missing where expected"));null!=a.V[a.h]&&U(a,a.h+" multiply declared");e={};e.name=a.h;e.Ib=null;e.q=0;e.domain=null;e.type=f;e.code=null;e.P=null;e.W=null;e.O=null;Y(a);a.b==Eh&&(e.Ib=a.h,Y(a));a.b==ui&&(e.domain=Fk(a),e.q=Mk(e.domain));f=a.V[e.name]={};f.type=ch;f.link=e;a.b!=mi&&
+U(a,"colon missing where expected");Y(a);e.code=zk(a);e.code.type==T&&(e.code=Z(Ui,e.code,N,0));e.code.type==N&&(e.code=Z(Yi,e.code,jh,0));e.code.type!=jh&&U(a,"expression following colon has invalid type");null!=e.domain&&Hk(a,e.domain);a.b!=ni&&U(a,"syntax error in objective statement");Y(a);d.H=e}else if(rk(a,"table")){b&&U(a,"table statement not allowed here");c.type=wh;var d=c.v,g,h,k;Y(a);a.b!=Bh&&(sk(a)?U(a,"invalid use of reserved keyword "+a.h):U(a,"symbolic name missing where expected"));
+null!=a.V[a.h]&&U(a,a.h+" multiply declared");e={v:{qa:{},Oc:{}}};e.name=a.h;Y(a);a.b==Eh?(e.Ib=a.h,Y(a)):e.Ib=null;a.b==ui?(e.type=rh,e.v.Oc.domain=Fk(a),rk(a,"OUT")||U(a,"keyword OUT missing where expected")):(e.type=lh,rk(a,"IN")||U(a,"keyword IN missing where expected"));Y(a);for(e.a=f=null;;)if(g={},a.b!=li&&a.b!=mi&&a.b!=ni||U(a,"argument expression missing where expected"),g.code=zk(a),g.code.type==N&&(g.code=Z(Vi,g.code,T,0)),g.code.type!=T&&U(a,"argument expression has invalid type"),g.e=
+null,null==f?e.a=g:f.e=g,f=g,a.b==li)Y(a);else if(a.b==mi||a.b==ni)break;a.b==mi?Y(a):U(a,"colon missing where expected");switch(e.type){case lh:a.b==Bh?(g=a.V[a.h],null==g&&U(a,a.h+" not defined"),g.type!=uh&&U(a,a.h+" not a set"),e.v.qa.set=g.link,null!=e.v.qa.set.assign&&U(a,a.h+" needs no data"),0!=e.v.qa.set.q&&U(a,a.h+" must be a simple set"),Y(a),a.b==yi?Y(a):U(a,"delimiter <- missing where expected")):sk(a)?U(a,"invalid use of reserved keyword "+a.h):e.v.qa.set=null;e.v.qa.We=g=null;f=0;for(a.b==
+si?Y(a):U(a,"field list missing where expected");;)if(h={},a.b!=Bh&&(sk(a)?U(a,"invalid use of reserved keyword "+a.h):U(a,"field name missing where expected")),h.name=a.h,Y(a),h.e=null,null==g?e.v.qa.We=h:g.e=h,g=h,f++,a.b==li)Y(a);else if(a.b==ti)break;else U(a,"syntax error in field list");null!=e.v.qa.set&&e.v.qa.set.X!=f&&U(a,"there must be "+e.v.qa.set.X+" field"+(1==e.v.qa.set.X?"":"s")+" rather than "+f);Y(a);for(e.v.qa.list=h=null;a.b==li;)Y(a),k={},a.b!=Bh&&(sk(a)?U(a,"invalid use of reserved keyword "+
+a.h):U(a,"parameter name missing where expected")),g=a.V[a.h],null==g&&U(a,a.h+" not defined"),g.type!=sh&&U(a,a.h+" not a parameter"),k.S=g.link,k.S.q!=f&&U(a,a.h+" must have "+f+" subscript"+(1==f?"":"s")+" rather than "+k.S.q),null!=k.S.assign&&U(a,a.h+" needs no data"),Y(a),a.b==xi?(Y(a),a.b!=Bh&&(sk(a)?U(a,"invalid use of reserved keyword "+a.h):U(a,"field name missing where expected")),g=a.h,Y(a)):g=k.S.name,k.name=g,k.e=null,null==h?e.v.qa.list=k:h.e=k,h=k;break;case rh:for(e.v.Oc.list=f=null;;)if(h=
+{},a.b!=li&&a.b!=ni||U(a,"expression missing where expected"),g=a.b==Bh?a.h:"",h.code=zk(a),a.b==xi&&(Y(a),a.b!=Bh&&(sk(a)?U(a,"invalid use of reserved keyword "+a.h):U(a,"field name missing where expected")),g=a.h,Y(a)),""==g&&U(a,"field name required"),h.name=g,h.e=null,null==f?e.v.Oc.list=h:f.e=h,f=h,a.b==li)Y(a);else if(a.b==ni)break;else U(a,"syntax error in output list");Hk(a,e.v.Oc.domain)}a.b!=ni&&U(a,"syntax error in table statement");Y(a);d.nd=e}else if(rk(a,"solve"))b&&U(a,"solve statement not allowed here"),
+c.type=vh,d=c.v,a.Ob&&U(a,"at most one solve statement allowed"),a.Ob=1,Y(a),a.b!=ni&&U(a,"syntax error in solve statement"),Y(a),d.zh=null;else if(rk(a,"check"))c.type=bh,d=c.v,e={domain:null,code:null},Y(a),a.b==ui&&(e.domain=Fk(a)),a.b==mi&&Y(a),e.code=Ek(a),e.code.type!=nh&&U(a,"expression has invalid type"),null!=e.domain&&Hk(a,e.domain),a.b!=ni&&U(a,"syntax error in check statement"),Y(a),d.Rg=e;else if(rk(a,"display"))c.type=dh,c.v.Sg=bl(a);else if(rk(a,"printf")){c.type=th;d=c.v;g={domain:null,
+zd:null};g.list=f=null;Y(a);a.b==ui&&(g.domain=Fk(a));a.b==mi&&Y(a);g.zd=zk(a);g.zd.type==N&&(g.zd=Z(Vi,g.zd,T,0));for(g.zd.type!=T&&U(a,"format expression has invalid type");a.b==li;)Y(a),e={code:null,e:null},null==g.list?g.list=e:f.e=e,f=e,e.code=Gk(a),e.code.type!=N&&e.code.type!=T&&e.code.type!=nh&&U(a,"only numeric, symbolic, or logical expression allowed");null!=g.domain&&Hk(a,g.domain);g.Ea=null;g.Mg=0;if(a.b==gi||a.b==wi)g.Mg=a.b==wi,Y(a),g.Ea=zk(a),g.Ea.type==N&&(g.Ea=Z(Vi,g.Ea,T,0)),g.Ea.type!=
+T&&U(a,"file name expression has invalid type");a.b!=ni&&U(a,"syntax error in printf statement");Y(a);d.nh=g}else if(rk(a,"for")){c.type=ih;d=c.v;g={domain:null};g.list=f=null;Y(a);a.b!=ui&&U(a,"indexing expression missing where expected");g.domain=Fk(a);a.b==mi&&Y(a);if(a.b!=ui)g.list=el(a,1);else{for(Y(a);a.b!=vi;)e=el(a,1),null==f?g.list=e:f.e=e,f=e;Y(a)}Hk(a,g.domain);d.Ug=g}else a.b==Bh?(b&&U(a,"constraint statement not allowed here"),c.type=ch,c.v.H=al(a)):sk(a)?U(a,"invalid use of reserved keyword "+
+a.h):U(a,"syntax error in model section");return c}function fl(a){var b,c;for(c=null;a.b!=Ah&&!rk(a,"data")&&!rk(a,"end");)b=el(a,0),null==c?a.uc=b:c.e=b,c=b}function gl(a,b){var c,d={};d.Y=b;d.e=null;if(null==a)a=d;else{for(c=a;null!=c.e;c=c.e);c.e=d}return a}function hl(a){for(var b=0;null!=a;a=a.e)b++;return b}function il(a){for(var b=0;null!=a;a=a.e)null==a.Y&&b++;return b}function jl(a){for(var b=null;00.999*s-c||0>b&&0>c&&b<-0.999*s-c)&&U(a,b+" + "+c+"; floating-point overflow");return b+c}function Ml(a,b,c){(0c&&b>0.999*s+c||0>b&&0c&&b>0.999*s+c&&U(a,b+" less "+c+"; floating-point overflow");return b-c}
+function Ol(a,b,c){10.999*s/Math.abs(c)&&U(a,b+" * "+c+"; floating-point overflow");return b*c}function Pl(a,b,c){Math.abs(c)Math.abs(c)&&Math.abs(b)>0.999*s*Math.abs(c)&&U(a,b+" / "+c+"; floating-point overflow");return b/c}
+function Ql(a,b,c){Math.abs(c)Math.abs(c)&&Math.abs(b)>0.999*s*Math.abs(c)&&U(a,b+" div "+c+"; floating-point overflow");b/=c;return 0b?Math.ceil(b):0}function Rl(a,b){var c;if(0==a)c=0;else if(0==b)c=a;else if(c=Math.abs(a)%Math.abs(b),0!=c&&(0>a&&(c=-c),0b||0>a&&0=c||0>b&&c!=Math.floor(c))&&U(a,b+" ** "+c+"; result undefined");0==b?a=Math.pow(b,c):((10.999*Math.log(s)/c||1>Math.abs(b)&&-1>c&&+Math.log(Math.abs(b))<0.999*Math.log(s)/c)&&U(a,b+" ** "+c+"; floating-point overflow"),a=1c&&-Math.log(Math.abs(b))<0.999*Math.log(s)/c||1>Math.abs(b)&&10.999*Math.log(s)/c?0:Math.pow(b,c));return a}
+function Tl(a,b){b>0.999*Math.log(s)&&U(a,"exp("+b+"); floating-point overflow");return Math.exp(b)}function Ul(a,b){0>=b&&U(a,"log("+b+"); non-positive argument");return Math.log(b)}function Vl(a,b){0>=b&&U(a,"log10("+b+"); non-positive argument");return Math.log(b)/Math.LN10}function Wl(a,b){0>b&&U(a,"sqrt("+b+"); negative argument");return Math.sqrt(b)}function Xl(a,b){-1E6<=b&&1E6>=b||U(a,"sin("+b+"); argument too large");return Math.sin(b)}
+function Yl(a,b){-1E6<=b&&1E6>=b||U(a,"cos("+b+"); argument too large");return Math.cos(b)}function Zl(a){return Math.atan(a)}function $l(a,b){return Math.atan2(a,b)}function am(a,b,c){c!=Math.floor(c)&&U(a,"round("+b+", "+c+"); non-integer second argument");18>=c&&(a=Math.pow(10,c),Math.abs(b)<0.999*s/a&&(b=Math.floor(b*a+0.5),0!=b&&(b/=a)));return b}
+function bm(a,b,c){c!=Math.floor(c)&&U(a,"trunc("+b+", "+c+"); non-integer second argument");18>=c&&(a=Math.pow(10,c),Math.abs(b)<0.999*s/a&&(b=0<=b?Math.floor(b*a):Math.ceil(b*a),0!=b&&(b/=a)));return b}function cm(a,b,c){var d;b>=c&&U(a,"Uniform("+b+", "+c+"); invalid range");d=dm(a.Gd)/2147483648;return d=Ll(a,b*(1-d),c*d)}function em(a){var b,c;do b=-1+2*(dm(a.Gd)/2147483648),c=-1+2*(dm(a.Gd)/2147483648),b=b*b+c*c;while(1b.Q?1:0;else if(null==a.M)c=-1;else if(null==b.M)c=1;else{c=a.M;var d=b.M;c=c==d?0:c>d?1:-1}return c}
+function rl(a){var b;if(null==a.M)b=String(a.Q);else{var c,d,e=a.M;if(ua(e[0])||"_"==e[0])for(a=!1,c=1;cd&&(b+=a,d++)};a&&f("'");for(c=0;cf&&(g+=a);f++}var d,e,f=0,g="",h="",k=xl(b);"["==a&&0b&&c>0.999*s+b?+s:0>c&&0Math.abs(d)&&Math.abs(e)>0.999*s*Math.abs(d)?e=0e&&0>d?+s:0:(e=Math.floor(e/d)+1,0>e&&(e=0));2147483646d?a.left:a.right}c=a;a=null==c?null:c.link}else for(a=b.head;null!=a&&0!=hm(a.w,c);a=a.e);return a}function Al(a,b){var c={};c.w=b;c.e=null;c.value={};a.size++;null==a.head?a.head=c:a.Xa.e=c;a.Xa=c;null!=a.V&&(qe(a.V,c.w).link=c);return c}
+function tm(a){var b;if(null!=a.Le)for(b=a.list,a=a.Le;null!=b;b=b.e,a=a.e)a:{var c=b,d=a.Y,e=void 0,f=void 0;if(null!=c.value){if(0==gm(c.value,d))break a;c.value=null}for(e=c.list;null!=e;e=e.a.index.e)for(f=e;null!=f;f=f.R)f.valid&&(f.valid=0,rm(f.type,f.value));c.value=sl(d)}}function um(a,b,c,d,e){var f,g=0;if(!vm(a,b.code,c))return 1;f=b.Le;b.Le=c;tm(b);e(a,d);b.Le=f;tm(b);return g}
+function wm(a,b){if(null!=b.Kc){var c,d,e=null,f=null;c=b.Kc;b.Kc=c.e;for(d=c.list;null!=d;d=d.e)null==e?e=f={}:f=f.e={},null==d.code?(f.Y=b.w.Y,b.w=b.w.e):f.Y=xm(a,d.code);f.e=null;um(a,c,e,b,wm)&&(b.Ve=1);for(d=c.list;null!=d;d=d.e)e=e.e}else null==b.domain.code||ym(a,b.domain.code)?b.Zd(a,b.info):b.Ve=2}function zm(a,b,c,d,e){var f={};null==b?(e(a,d),f.Ve=0):(f.domain=b,f.Kc=b.list,f.w=c,f.info=d,f.Zd=e,f.Ve=0,wm(a,f));return f.Ve}
+function Am(a,b){if(null!=b.Kc){var c,d,e;c=b.Kc;b.Kc=c.e;e=null;for(d=c.list;null!=d;d=d.e)null!=d.code&&(e=tl(e,xm(a,d.code)));if(c.code.Ta==Yj){var f,g,h,k;g=$(a,c.code.a.a.x);h=$(a,c.code.a.a.y);k=null==c.code.a.a.z?1:$(a,c.code.a.a.z);e=lm(a,g,h,k);d=tl(null,ml(0));for(f=1;f<=e&&b.Wf;f++)d.Y.Q=mm(a,g,h,k,f),um(a,c,d,b,Am)}else for(f=Bm(a,c.code).head;null!=f&&b.Wf;f=f.e){g=f.w;h=e;k=!1;for(d=c.list;null!=d;d=d.e){if(null!=d.code){if(0!=gm(g.Y,h.Y)){k=!0;break}h=h.e}g=g.e}k||um(a,c,f.w,b,Am)}b.Kc=
+c}else if(null==b.domain.code||ym(a,b.domain.code))b.Wf=!b.Zd(a,b.info)}function Cm(a,b,c,d){var e={};null==b?d(a,c):(e.domain=b,e.Kc=b.list,e.Wf=1,e.info=c,e.Zd=d,Am(a,e))}function Dm(a,b,c){U(a,b+zl("[",c)+" out of domain")}function Em(a){var b=null;if(null!=a)for(a=a.list;null!=a;a=a.e)for(var c=a.list;null!=c;c=c.e)null==c.code&&(b=tl(b,sl(c.value)));return b}
+function Fm(a,b,c,d){for(var e=b.Bf,f=1;null!=e;e=e.e,f++)for(var g=d.head;null!=g;g=g.e)if(!vm(a,e.code,g.w)){var h=zl("(",g.w);U(a,b.name+zl("[",c)+" contains "+h+" which not within specified set; see ("+f+")")}}function Gm(a,b,c){function d(){Fm(a,b,c,e);f=Al(b.O,Il(c));f.value.set=e}var e,f=yl(a,b.O,c);null!=f?e=f.value.set:null!=b.assign?(e=Bm(a,b.assign),d()):null!=b.xa?(e=Bm(a,b.xa),d()):U(a,"no value for "+b.name+zl("[",c));return e}
+function Hm(a,b){null!=b.ga?Fm(a,b.set,b.ga.w,b.ga.value.set):b.oe=Gm(a,b.set,b.w)}
+function Im(a,b){var c=b.Yc,d,e,f,g=Array(20);x("Generating "+b.name+"...");d=c.set;Cm(a,d.domain,d,Jm);for(d=c.set.O.head.value.set.head;null!=d;d=d.e){f=Il(d.w);for(e=0;e=g||h(">=");break;case Hj:d>g||h(">");break;case Ij:d==g&&h("<>")}}f=1;for(e=
+b.qa;null!=e;e=e.e,f++)h=tl(null,ml(d)),vm(a,e.code,h)||U(a,b.name+zl("[",c)+" = "+d+" not in specified set; see ("+f+")")}function Mm(a,b,c){function d(d){Lm(a,b,c,d);e=Al(b.O,Il(c));return e.value.Q=d}var e=yl(a,b.O,c);return null!=e?e.value.Q:null!=b.assign?d($(a,b.assign)):null!=b.xa?d($(a,b.xa)):null!=b.Wc?(null!=b.Wc.M&&U(a,"cannot convert "+rl(b.Wc)+" to floating-point number"),d(b.Wc.Q)):U(a,"no value for "+b.name+zl("[",c))}
+function Nm(a,b){null!=b.ga?Lm(a,b.S,b.ga.w,b.ga.value.Q):b.value=Mm(a,b.S,b.w)}function Om(a,b,c){var d={};d.S=b;d.w=c;if(1==b.data)for(c=b.O.Xa,b.data=2,d.ga=b.O.head;null!=d.ga&&(zm(a,b.domain,d.ga.w,d,Nm)&&Dm(a,b.name,d.ga.w),d.ga!=c);d.ga=d.ga.e);d.ga=null;zm(a,d.S.domain,d.w,d,Nm)&&Dm(a,b.name,d.w);return d.value}
+function Pm(a,b,c,d){var e,f=1;for(e=b.wd;null!=e;e=e.e,f++){var g;g=xm(a,e.code);switch(e.jd){case Dj:0>gm(d,g)||(g=rl(g),U(a,b.name+zl("[",c)+" = "+rl(d)+" not < "+g));break;case Ej:0>=gm(d,g)||(g=rl(g),U(a,b.name+zl("[",c)+" = "+rl(d)+" not <= "+g));break;case Fj:0!=gm(d,g)&&(g=rl(g),U(a,b.name+zl("[",c)+" = "+rl(d)+" not = "+g));break;case Gj:0<=gm(d,g)||(g=rl(g),U(a,b.name+zl("[",c)+" = "+rl(d)+" not >= "+g));break;case Hj:0 "+g));break;
+case Ij:0==gm(d,g)&&(g=rl(g),U(a,b.name+zl("[",c)+" <> "+rl(d)+" not > "+g))}}f=1;for(e=b.qa;null!=e;e=e.e,f++)g=tl(null,sl(d)),vm(a,e.code,g)||U(a,b.name,zl("[",c)+" = "+rl(d)+" not in specified set; see ("+f+")")}function Qm(a,b,c){function d(d){Pm(a,b,c,d);e=Al(b.O,Il(c));e.value.Y=sl(d);return d}var e=yl(a,b.O,c);return null!=e?sl(e.value.Y):null!=b.assign?d(xm(a,b.assign)):null!=b.xa?d(xm(a,b.xa)):null!=b.Wc?sl(b.Wc):U(a,"no value for "+b.name+zl("[",c))}
+function Rm(a,b){null!=b.ga?Pm(a,b.S,b.ga.w,b.ga.value.Y):b.value=Qm(a,b.S,b.w)}function Sm(a,b,c){var d={};d.S=b;d.w=c;if(1==b.data)for(c=b.O.Xa,b.data=2,d.ga=b.O.head;null!=d.ga&&(zm(a,b.domain,d.ga.w,d,Rm)&&Dm(a,b.name,d.ga.w),d.ga!=c);d.ga=d.ga.e);d.ga=null;zm(a,d.S.domain,d.w,d,Rm)&&Dm(a,b.name,d.w);return d.value}function Tm(a,b){var c=Em(b.domain);switch(b.type){case N:case mh:case ah:Om(a,b,c);break;case T:Sm(a,b,c)}return 0}
+function Um(a,b){var c=b.t,d=b.w,e=yl(a,c.O,d);null!=e?d=e.value.t:(e=Al(c.O,Il(d)),d=e.value.t={},d.C=0,d.t=c,d.ga=e,d.P=null==c.P?0:$(a,c.P),d.W=null==c.W?0:c.W==c.P?d.P:$(a,c.W),d.ja=0,d.m=0,d.r=d.J=0);b.oe=d}function Vm(a,b,c){var d={};d.t=b;d.w=c;zm(a,d.t.domain,d.w,d,Um)&&Dm(a,b.name,d.w);return d.oe}
+function Wm(a,b,c){var d=null,e=yl(a,b.O,c);if(null!=e)c=e.value.H;else{e=Al(b.O,Il(c));c=e.value.H={};c.ea=0;c.H=b;c.ga=e;c.form=Xm(a,b.code);if(null==b.P&&null==b.W)c.form=qm(a,c.form,function(a){d=a}),c.P=c.W=-d;else if(null!=b.P&&null==b.W)c.form=pm(a,1,c.form,-1,Xm(a,b.P)),c.form=qm(a,c.form,function(a){d=a}),c.P=-d,c.W=0;else if(null==b.P&&null!=b.W)c.form=pm(a,1,c.form,-1,Xm(a,b.W)),c.form=qm(a,c.form,function(a){d=a}),c.P=0,c.W=-d;else if(b.P==b.W)c.form=pm(a,1,c.form,-1,Xm(a,b.P)),c.form=
+qm(a,c.form,function(a){d=a}),c.P=c.W=-d;else{var f=null,g=null;c.form=qm(a,c.form,function(a){d=a});qm(a,Xm(a,b.P),function(a){f=a});qm(a,Xm(a,b.W),function(a){g=a});c.P=Ml(a,f,d);c.W=Ml(a,g,d)}c.m=0;c.r=c.J=0}return c}function Ym(a,b){b.oe=Wm(a,b.H,b.w)}function Zm(a,b,c){var d={};d.H=b;d.w=c;zm(a,d.H.domain,d.w,d,Ym)&&Dm(a,b.name,d.w);return d.oe}function $m(a,b){var c=Em(b.domain);Zm(a,b,c);return 0}
+function an(a,b){var c=$(a,b.code.a.loop.x);switch(b.code.Ta){case dk:b.value=Ll(a,b.value,c);break;case ek:b.value=Ol(a,b.value,c);break;case fk:b.value>c&&(b.value=c);break;case gk:b.valued&&(c=d);break;case ck:c=-s;for(e=b.a.list;null!=e;e=e.e)d=$(a,e.x),ce||e>c.length+1)&&U(a,"substr('...', "+e+"); substring out of range")):(e=$(a,b.a.a.y),d=$(a,b.a.a.z),e==Math.floor(e)&&d==Math.floor(d)||U(a,"substr('...', "+e+", "+d+"); non-integer second and/or third argument"),(1>e||0>d||e+d>c.length+1)&&U(a,"substr('...', "+e+", "+d+"); substring out of range"));c=nl(c.slice(e-1,e+d-1));break;case Xj:d=$(a,b.a.a.x),
+c=xm(a,b.a.a.y),c=dn(a,d,null==c.M?String(c.Q):c.M),c=nl(c)}b.valid=1;b.value.Y=sl(c);return c}function en(a,b){var c=0;switch(b.code.Ta){case hk:b.value&=ym(a,b.code.a.loop.x);b.value||(c=1);break;case ik:b.value|=ym(a,b.code.a.loop.x),b.value&&(c=1)}return c}
+function ym(a,b){var c,d;b.T&&b.valid&&(b.valid=0,rm(b.type,b.value));if(b.valid)return b.value.sg;switch(b.Ta){case Wi:c=0!=$(a,b.a.a.x);break;case aj:c=!ym(a,b.a.a.x);break;case Dj:b.a.a.x.type==N?c=$(a,b.a.a.x)<$(a,b.a.a.y):(c=xm(a,b.a.a.x),d=xm(a,b.a.a.y),c=0>gm(c,d));break;case Ej:b.a.a.x.type==N?c=$(a,b.a.a.x)<=$(a,b.a.a.y):(c=xm(a,b.a.a.x),d=xm(a,b.a.a.y),c=0>=gm(c,d));break;case Fj:b.a.a.x.type==N?c=$(a,b.a.a.x)==$(a,b.a.a.y):(c=xm(a,b.a.a.x),d=xm(a,b.a.a.y),c=0==gm(c,d));break;case Gj:b.a.a.x.type==
+N?c=$(a,b.a.a.x)>=$(a,b.a.a.y):(c=xm(a,b.a.a.x),d=xm(a,b.a.a.y),c=0<=gm(c,d));break;case Hj:b.a.a.x.type==N?c=$(a,b.a.a.x)>$(a,b.a.a.y):(c=xm(a,b.a.a.x),d=xm(a,b.a.a.y),c=0b&&!(e<=c&&c<=f)){d=0;break}d=mm(a,f,e,b,((c-f)/b+0.5|0)+1)==c;break;case Zj:d=ym(a,b.a.a.x)?vm(a,b.a.a.y,c):vm(a,b.a.a.z,c);break;case jk:U(a,"implementation restriction; in/within setof{} not allowed");break;case kk:f=im(c,b.q),d=0==zm(a,b.a.loop.domain,f,null,hn)}return d}
+function jn(a,b){switch(b.code.Ta){case dk:var c;c=Xm(a,b.code.a.loop.x);for(null==b.value?b.value=c:b.Xa.e=c;null!=c;c=c.e)b.Xa=c}return 0}
+function Xm(a,b){var c;b.T&&b.valid&&(b.valid=0,rm(b.type,b.value));if(b.valid)return om(b.value.form);switch(b.Ta){case Li:var d=null;for(c=b.a.t.list;null!=c;c=c.e)d=tl(d,xm(a,c.x));c=Vm(a,b.a.t.t,d);d={u:1};d.t=c;d.e=null;c=d;break;case Yi:c=nm($(a,b.a.a.x));break;case Zi:c=pm(a,0,nm(0),1,Xm(a,b.a.a.x));break;case $i:c=pm(a,0,nm(0),-1,Xm(a,b.a.a.x));break;case pj:c=pm(a,1,Xm(a,b.a.a.x),1,Xm(a,b.a.a.y));break;case qj:c=pm(a,1,Xm(a,b.a.a.x),-1,Xm(a,b.a.a.y));break;case sj:c=b.a.a.x.type==N?pm(a,
+$(a,b.a.a.x),Xm(a,b.a.a.y),0,nm(0)):pm(a,$(a,b.a.a.y),Xm(a,b.a.a.x),0,nm(0));break;case tj:c=pm(a,Pl(a,1,$(a,b.a.a.y)),Xm(a,b.a.a.x),0,nm(0));break;case Zj:c=ym(a,b.a.a.x)?Xm(a,b.a.a.y):null==b.a.a.z?nm(0):Xm(a,b.a.a.z);break;case dk:c={};c.code=b;c.value=nm(0);c.Xa=null;Cm(a,b.a.loop.domain,c,jn);c=c.value;for(var e,f=0,d=c;null!=d;d=d.e)null==d.t?f=Ll(a,f,d.u):d.t.ja=Ll(a,d.t.ja,d.u);e=c;c=null;for(d=e;null!=d;d=e)e=d.e,null==d.t&&0!=f?(d.u=f,f=0,d.e=c,c=d):null!=d.t&&0!=d.t.ja&&(d.u=d.t.ja,d.t.ja=
+0,d.e=c,c=d)}b.valid=1;b.value.form=om(c);return c}var kn=exports.mpl_tab_num_args=function(a){return a.ff},ln=exports.mpl_tab_get_arg=function(a,b){return a.a[b]};exports.mpl_tab_get_args=function(a){return a.a};
+var mn=exports.mpl_tab_num_flds=function(a){return a.Wa},nn=exports.mpl_tab_get_name=function(a,b){return a.name[b]},on=exports.mpl_tab_get_type=function(a,b){return a.type[b]},pn=exports.mpl_tab_get_num=function(a,b){return a.Q[b]},qn=exports.mpl_tab_get_str=function(a,b){return a.M[b]},rn=exports.mpl_tab_set_num=function(a,b,c){a.type[b]="N";a.Q[b]=c},sn=exports.mpl_tab_set_str=function(a,b,c){a.type[b]="S";a.M[b]=c};
+function tn(a,b){var c=a.Mc,d,e,f;f=0;for(d=b.v.Oc.list;null!=d;d=d.e)switch(f++,d.code.type){case N:c.type[f]="N";c.Q[f]=$(a,d.code);c.M[f][0]="\x00";break;case T:e=xm(a,d.code),null==e.M?(c.type[f]="N",c.Q[f]=e.Q,c.M[f][0]="\x00"):(c.type[f]="S",c.Q[f]=0,c.M[f]=e.M)}c=a.Mc;c.link.writeRecord(c)&&U(a,"error on writing data to table "+a.ib.v.nd.name);return 0}function un(a,b){ym(a,b.code)||U(a,"check"+zl("[",Em(b.domain))+" failed");return 0}
+function vn(a,b,c){var d=c.value.set;wn(a,b.name+zl("[",c.w)+(null==d.head?" is empty":":"));for(b=d.head;null!=b;b=b.e)wn(a," "+zl("(",b.w))}function xn(a,b,c){switch(b.type){case N:case mh:case ah:wn(a,b.name+zl("[",c.w)+" = "+c.value.Q);break;case T:wn(a,b.name+zl("[",c.w)+" = "+rl(c.value.Y))}}
+function yn(a,b,c,d){d==zi||d==Di?wn(a,b.name+zl("[",c.w)+".val = "+c.value.t.r):d==Ai?wn(a,b.name+zl("[",c.w)+".lb = "+(null==c.value.t.t.P?-s:c.value.t.P)):d==Bi?wn(a,b.name+zl("[",c.w)+".ub = "+(null==c.value.t.t.W?+s:c.value.t.W)):d==Ci?wn(a,b.name+zl("[",c.w)+".status = "+c.value.t.m):d==Ei&&wn(a,b.name+zl("[",c.w)+".dual = "+c.value.t.J)}
+function zn(a,b,c,d){d==zi||d==Di?wn(a,b.name+zl("[",c.w)+".val = "+c.value.H.r):d==Ai?wn(a,b.name+zl("[",c.w)+".lb = "+(null==c.value.H.H.P?-s:c.value.H.P)):d==Bi?wn(a,b.name+zl("[",c.w)+".ub = "+(null==c.value.H.H.W?+s:c.value.H.W)):d==Ci?wn(a,b.name+zl("[",c.w)+".status = "+c.value.H.m):d==Ei&&wn(a,b.name+zl("[",c.w)+".dual = "+c.value.H.J)}
+function An(a,b){for(var c,d=b.list;null!=d;d=d.e)if(d.type==kh)c=d.v.ya,wn(a,c.name+" = "+rl(c.value));else if(d.type==uh){var e=d.v.set;null!=e.assign?Cm(a,e.domain,e,Jm):(null!=e.Yc&&0==e.data&&Im(a,e),null!=e.O.head&&Km(a,e,e.O.head.w));null==e.O.head&&wn(a,e.name+" has empty content");for(c=e.O.head;null!=c;c=c.e)vn(a,e,c)}else if(d.type==sh)for(e=d.v.S,null!=e.assign?Cm(a,e.domain,e,Tm):null!=e.O.head&&(e.type!=T?Om(a,e,e.O.head.w):Sm(a,e,e.O.head.w)),null==e.O.head&&wn(a,e.name+" has empty content"),
+c=e.O.head;null!=c;c=c.e)xn(a,e,c);else if(d.type==yh)for(e=d.v.t,null==e.O.head&&wn(a,e.name+" has empty content"),c=e.O.head;null!=c;c=c.e)yn(a,e,c,zi);else if(d.type==ch)for(e=d.v.H,null==e.O.head&&wn(a,e.name+" has empty content"),c=e.O.head;null!=c;c=c.e)zn(a,e,c,zi);else if(d.type==hh)if(e=d.v.code,e.Ta==Ii||e.Ta==Ji||e.Ta==Ki||e.Ta==Li||e.Ta==Mi){c=a;var f={value:{}},g=void 0;f.w=null;for(g=e.a.S.list||e.a.t.list;null!=g;g=g.e)f.w=tl(f.w,xm(c,g.x));switch(e.Ta){case Ii:f.value.Q=Om(c,e.a.S.S,
+f.w);xn(c,e.a.S.S,f);break;case Ji:f.value.Y=Sm(c,e.a.S.S,f.w);xn(c,e.a.S.S,f);break;case Ki:f.value.set=Km(c,e.a.set.set,f.w);vn(c,e.a.set.set,f);break;case Li:f.value.t=Vm(c,e.a.t.t,f.w);yn(c,e.a.t.t,f,e.a.t.Ac);break;case Mi:f.value.H=Zm(c,e.a.H.H,f.w),zn(c,e.a.H.H,f,e.a.H.Ac)}}else switch(c=a,e.type){case N:e=$(c,e);wn(c,String(e));break;case T:e=xm(c,e);wn(c,rl(e));break;case nh:e=ym(c,e);wn(c,e?"true":"false");break;case xh:e=fn(c,e);wn(c,zl("(",e));break;case fh:e=Bm(c,e);0==e.head&&wn(c,"set is empty");
+for(e=e.head;null!=e;e=e.e)wn(c," "+zl("(",e.w));break;case jh:for(f=void 0,e=Xm(c,e),null==e&&wn(c,"linear form is empty"),f=e;null!=f;f=f.e)null==f.t?wn(c," "+f.u):wn(c," "+f.u+" "+f.t.t.name+zl("[",f.t.ga.w))}return 0}function Bn(a,b){null==a.Hg?"\n"==b?(a.he(a.dd,a.le),a.dd=""):a.dd+=b:a.Hg(b)}function Cn(a,b){for(var c=0;c=g||U(a,"cannot convert "+g+" to integer"),Cn(a,xa(d.slice(e,f+1),Math.floor(g+0.5)|0))):Cn(a,xa(d.slice(e,f+1),g))}else if("s"==d[f]){switch(c.code.type){case N:g=String($(a,c.code));break;case nh:g=ym(a,c.code)?"T":"F";break;case T:h=xm(a,c.code),g=null==h.M?String(h.Q):h.M}Cn(a,xa(d.slice(e,f+1),g))}else U(a,"format specifier missing or invalid");c=c.e}else"\\"==d[f]?(f++,"t"==d[f]?Bn(a,"\t"):"n"==d[f]?Bn(a,
+"\n"):"\x00"==d[f]?U(a,"invalid use of escape character \\ in format control string"):Bn(a,d[f])):Bn(a,d[f]);return 0}function En(a,b){for(var c=a.ib,d=b.list;null!=d;d=d.e)Fn(a,d);a.ib=c;return 0}
+function Fn(a,b){a.ib=b;switch(b.type){case ch:x("Generating "+b.v.H.name+"...");var c=b.v.H;Cm(a,c.domain,c,$m);break;case wh:switch(b.v.nd.type){case lh:x("Reading "+b.v.nd.name+"...");break;case rh:x("Writing "+b.v.nd.name+"...")}var c=b.v.nd,d,e,f,g;a.Mc=f={};f.id=0;f.link=null;f.ff=0;f.a=null;f.Wa=0;f.name=null;f.type=null;f.Q=null;f.M=null;for(d=c.a;null!=d;d=d.e)f.ff++;f.a=Array(1+f.ff);for(g=1;g<=f.ff;g++)f.a[g]=null;g=0;for(d=c.a;null!=d;d=d.e)g++,e=xm(a,d.code),e=null==e.M?String(e.Q):e.M,
+f.a[g]=e;switch(c.type){case lh:g=c.v.qa.set;null!=g&&(g.data&&U(a,g.name+" already provided with data"),Al(g.O,null).value.set=Bl(a,qh,g.X),g.data=1);for(e=c.v.qa.list;null!=e;e=e.e)e.S.data&&U(a,e.S.name+" already provided with data"),e.S.data=1;for(e=c.v.qa.We;null!=e;e=e.e)f.Wa++;for(e=c.v.qa.list;null!=e;e=e.e)f.Wa++;f.name=Array(1+f.Wa);f.type=Array(1+f.Wa);f.Q=new Float64Array(1+f.Wa);f.M=Array(1+f.Wa);g=0;for(e=c.v.qa.We;null!=e;e=e.e)g++,f.name[g]=e.name,f.type[g]="?",f.Q[g]=0,f.M[g]="";
+for(e=c.v.qa.list;null!=e;e=e.e)g++,f.name[g]=e.name,f.type[g]="?",f.Q[g]=0,f.M[g]="";for(Gn(a,"R");;){for(g=1;g<=f.Wa;g++)f.type[g]="?";g=a;d=g.Mc;d=d.link.readRecord(d);0 "+(" "==a.Zb[0]?"":"...")+a.Zb.join("").trim());break;case 3:d=null==a.ib?0:a.ib.bb;var e=null==a.ib?0:a.ib.Vc;c=Error(d+": "+b);c.line=d;c.column=e}a.D=4;throw c;}
+function ok(a,b){switch(a.D){case 1:case 2:x(a.bf+":"+a.bb+": warning: "+b);break;case 3:x(a.Yf+":"+(null==a.ib?0:a.ib.bb)+": warning: "+b)}}
+var Ld=exports.mpl_initialize=function(){var a={bb:0,Vc:0,l:0,b:0,Bb:0,h:"",value:0,Hf:0,Gf:0,Ff:"",If:0,Te:0,Ue:0,Mf:0,Lf:0,Kf:"",Nf:0};a.Zb=Array(zh);ha(a.Zb,0," ",zh);a.lc=0;a.nc=0;a.V={};a.uc=null;a.Pf=0;a.rg=0;a.qg=0;a.Ke=0;a.Ob=0;a.pg=null;a.Ah="";a.Bh="";a.Gd=sg();a.Of=0;a.ib=null;a.Mc=null;a.g=0;a.i=0;a.n=null;a.f=null;a.cf=null;a.bf=null;a.he=null;a.jh=null;a.Hg=null;a.le=null;a.D=0;a.Yf=null;a.xh="";return a},Nd=exports.mpl_read_model=function(a,b,c,d){function e(){x(a.bb+" line"+(1==a.bb?
+"":"s")+" were read");a.cf=null;return a.D}0!=a.D&&w("mpl_read_model: invalid call sequence");null==c&&w("mpl_read_model: no input specified");a.D=1;x("Reading model section from "+b+" ...");In(a,b,c);fl(a);null==a.uc&&U(a,"empty model section not allowed");a.Yf=a.bf;Hn(a);if(rk(a,"data")){if(d)return ok(a,"data section ignored"),e();a.nc=1;Y(a);a.b!=ni&&U(a,"semicolon missing where expected");Y(a);a.D=2;x("Reading data section from "+b+" ...");Kl(a)}cl(a);return e()},Pd=exports.mpl_read_data=function(a,
+b,c){1!=a.D&&2!=a.D&&w("mpl_read_data: invalid call sequence");null==c&&w("mpl_read_data: no input specified");a.D=2;x("Reading data section from "+b+" ...");a.nc=1;In(a,b,c);dl(a,"data")&&(Y(a),a.b!=ni&&U(a,"semicolon missing where expected"),Y(a));Kl(a);cl(a);x(a.bb+" line"+(1==a.bb?"":"s")+" were read");a.cf=null;return a.D},Rd=exports.mpl_generate=function(a,b,c,d){1!=a.D&&2!=a.D&&w("mpl_generate: invalid call sequence");a.D=3;a.ve=d;Jn(a,b,c);for(b=a.uc;null!=b&&(Fn(a,b),a.ib.type!=vh);b=b.e);
+a.ib=b;Kn(a);for(b=a.uc;null!=b;b=b.e)if(b.type==yh)for(c=b.v.t,c=c.O.head;null!=c;c=c.e);for(b=a.uc;null!=b;b=b.e)if(b.type==ch)for(c=b.v.H,c=c.O.head;null!=c;c=c.e)for(c.value.H.ea=++a.g,d=c.value.H.form;null!=d;d=d.e)d.t.ga.value.t.C=-1;for(b=a.uc;null!=b;b=b.e)if(b.type==yh)for(c=b.v.t,c=c.O.head;null!=c;c=c.e)0!=c.value.t.C&&(c.value.t.C=++a.i);a.n=Array(1+a.g);for(d=1;d<=a.g;d++)a.n[d]=null;for(b=a.uc;null!=b;b=b.e)if(b.type==ch)for(c=b.v.H,c=c.O.head;null!=c;c=c.e)d=c.value.H.ea,a.n[d]=c.value.H;
+for(d=1;d<=a.g;d++);a.f=Array(1+a.i);for(d=1;d<=a.i;d++)a.f[d]=null;for(b=a.uc;null!=b;b=b.e)if(b.type==yh)for(c=b.v.t,c=c.O.head;null!=c;c=c.e)d=c.value.t.C,0!=d&&(a.f[d]=c.value.t);for(d=1;d<=a.i;d++);x("Model has been successfully generated");return a.D},Sd=exports.mpl_get_prob_name=function(a){return a.Yf},Td=exports.mpl_get_num_rows=function(a){3!=a.D&&w("mpl_get_num_rows: invalid call sequence");return a.g},be=exports.mpl_get_num_cols=function(a){3!=a.D&&w("mpl_get_num_cols: invalid call sequence");
+return a.i},Ud=exports.mpl_get_row_name=function(a,b){3!=a.D&&w("mpl_get_row_name: invalid call sequence");1<=b&&b<=a.g||w("mpl_get_row_name: i = "+b+"; row number out of range");var c=a.n[b].H.name,c=c+zl("[",a.n[b].ga.w).slice(0,255);255==c.length&&(c=c.slice(0,252)+"...");return c},ie=exports.mpl_get_row_kind=function(a,b){var c;3!=a.D&&w("mpl_get_row_kind: invalid call sequence");1<=b&&b<=a.g||w("mpl_get_row_kind: i = "+b+"; row number out of range");switch(a.n[b].H.type){case ch:c=411;break;
+case ph:c=je;break;case oh:c=ke}return c},Vd=exports.mpl_get_row_bnds=function(a,b,c){var d;3!=a.D&&w("mpl_get_row_bnds: invalid call sequence");1<=b&&b<=a.g||w("mpl_get_row_bnds: i = "+b+"; row number out of range");d=a.n[b];a=null==d.H.P?-s:d.P;b=null==d.H.W?+s:d.W;a==-s&&b==+s?(d=Wd,a=b=0):b==+s?(d=Xd,b=0):a==-s?(d=Yd,a=0):d=d.H.P!=d.H.W?Zd:$d;c(a,b);return d},he=exports.mpl_get_mat_row=function(a,b,c,d){var e=0;3!=a.D&&w("mpl_get_mat_row: invalid call sequence");1<=b&&b<=a.g||w("mpl_get_mat_row: i = "+
+b+"; row number out of range");for(a=a.n[b].form;null!=a;a=a.e)e++,null!=c&&(c[e]=a.t.C),null!=d&&(d[e]=a.u);return e},ae=exports.mpl_get_row_c0=function(a,b){var c;3!=a.D&&w("mpl_get_row_c0: invalid call sequence");1<=b&&b<=a.g||w("mpl_get_row_c0: i = "+b+"; row number out of range");c=a.n[b];return null==c.H.P&&null==c.H.W?-c.P:0},ce=exports.mpl_get_col_name=function(a,b){3!=a.D&&w("mpl_get_col_name: invalid call sequence");1<=b&&b<=a.i||w("mpl_get_col_name: j = "+b+"; column number out of range");
+var c=a.f[b].t.name,c=c+zl("[",a.f[b].ga.w);255==c.length&&(c=c.slice(0,252)+"...");return c},de=exports.mpl_get_col_kind=function(a,b){var c;3!=a.D&&w("mpl_get_col_kind: invalid call sequence");1<=b&&b<=a.i||w("mpl_get_col_kind: j = "+b+"; column number out of range");switch(a.f[b].t.type){case N:c=421;break;case mh:c=ee;break;case ah:c=fe}return c},ge=exports.mpl_get_col_bnds=function(a,b,c){var d;3!=a.D&&w("mpl_get_col_bnds: invalid call sequence");1<=b&&b<=a.i||w("mpl_get_col_bnds: j = "+b+"; column number out of range");
+d=a.f[b];a=null==d.t.P?-s:d.P;b=null==d.t.W?+s:d.W;a==-s&&b==+s?(d=Wd,a=b=0):b==+s?(d=Xd,b=0):a==-s?(d=Yd,a=0):d=d.t.P!=d.t.W?Zd:$d;c(a,b);return d},me=exports.mpl_has_solve_stmt=function(a){3!=a.D&&w("mpl_has_solve_stmt: invalid call sequence");return a.Ob},ne=exports.mpl_put_row_soln=function(a,b,c,d,e){a.n[b].m=c;a.n[b].r=d;a.n[b].J=e},oe=exports.mpl_put_col_soln=function(a,b,c,d,e){a.f[b].m=c;a.f[b].r=d;a.f[b].J=e},pe=exports.mpl_postsolve=function(a){(3!=a.D||a.Of)&&w("mpl_postsolve: invalid call sequence");
+var b;a.Of=1;for(b=a.ib;null!=b;b=b.e)Fn(a,b);a.ib=null;Kn(a);x("Model has been successfully processed");return a.D},Ln="Monday Tuesday Wednesday Thursday Friday Saturday Sunday".split(" "),Mn="January February March April May June July August September October November December".split(" ");function Nn(a){for(var b="";0=k;k++){y=Mn[k-1];var E=!1;for(g=0;2>=g;g++)if(n[g].toUpperCase()!=y[g].toUpperCase()){E=
+!0;break}if(!E){n+=3;for(g=3;"\x00"!=y[g]&&b[n].toUpperCase()==y[g].toUpperCase();g++)n++;break}}12=b[n]||On(a,b,n,c,t,"day missing or invalid");l=b[n++]-0;"0"<=b[n]&&"9">=b[n]&&(l=10*l+(b[n++]-0));1<=l&&31>=l||On(a,b,n,c,t,"day out of range")}else if("H"==c[t]){for(0<=p&&On(a,b,n,c,t,"hour multiply specified");" "==b[n];)n++;"0"<=b[n]&&"9">=
+b[n]||On(a,b,n,c,t,"hour missing or invalid");p=b[n++]-0;"0"<=b[n]&&"9">=b[n]&&(p=10*p+(b[n++]-0));0<=p&&23>=p||On(a,b,n,c,t,"hour out of range")}else if("m"==c[t]){for(0<=k&&On(a,b,n,c,t,"month multiply specified");" "==b[n];)n++;"0"<=b[n]&&"9">=b[n]||On(a,b,n,c,t,"month missing or invalid");k=b[n++]-0;"0"<=b[n]&&"9">=b[n]&&(k=10*k+(b[n++]-0));1<=k&&12>=k||On(a,b,n,c,t,"month out of range")}else if("M"==c[t]){for(0<=m&&On(a,b,n,c,t,"minute multiply specified");" "==b[n];)n++;"0"<=b[n]&&"9">=b[n]||
+On(a,b,n,c,t,"minute missing or invalid");m=b[n++]-0;"0"<=b[n]&&"9">=b[n]&&(m=10*m+(b[n++]-0));0<=m&&59>=m||On(a,b,n,c,t,"minute out of range")}else if("S"==c[t]){for(0<=q&&On(a,b,n,c,t,"second multiply specified");" "==b[n];)n++;"0"<=b[n]&&"9">=b[n]||On(a,b,n,c,t,"second missing or invalid");q=b[n++]-0;"0"<=b[n]&&"9">=b[n]&&(q=10*q+(b[n++]-0));0<=q&&60>=q||On(a,b,n,c,t,"second out of range")}else if("y"==c[t]){for(0<=h&&On(a,b,n,c,t,"year multiply specified");" "==b[n];)n++;"0"<=b[n]&&"9">=b[n]||
+On(a,b,n,c,t,"year missing or invalid");h=b[n++]-0;"0"<=b[n]&&"9">=b[n]&&(h=10*h+(b[n++]-0));h+=69<=h?1900:2E3}else if("Y"==c[t]){for(0<=h&&On(a,b,n,c,t,"year multiply specified");" "==b[n];)n++;"0"<=b[n]&&"9">=b[n]||On(a,b,n,c,t,"year missing or invalid");h=0;for(g=1;4>=g&&"0"<=b[n]&&"9">=b[n];g++)h=10*h+(b[n++]-0);1<=h&&4E3>=h||On(a,b,n,c,t,"year out of range")}else if("z"==c[t]){var C;for(2147483647!=r&&On(a,b,n,c,t,"time zone offset multiply specified");" "==b[n];)n++;if("Z"==b[n])C=p=m=0,n++;
+else{"+"==b[n]?(C=1,n++):"-"==b[n]?(C=-1,n++):On(a,b,n,c,t,"time zone offset sign missing");p=0;for(g=1;2>=g;g++)"0"<=b[n]&&"9">=b[n]||d(),p=10*p+(b[n++]-0);23=b[n]||d());m=0;if("0"<=b[n]&&"9">=b[n]){for(g=1;2>=g;g++)"0"<=b[n]&&"9">=b[n]||d(),m=10*m+(b[n++]-0);59h&&(h=1970);0>k&&(k=1);0>l&&(l=1);0>p&&(p=0);0>m&&(m=0);0>q&&(q=0);2147483647==r&&(r=0);g=xg(l,
+k,h);return 60*(60*(24*(g-xg(1,1,1970))+p)+m)+q-60*r}function Pn(a,b,c){x("Format string passed to time2str:");x(b);x(Nn(c));U(a,"invalid conversion specifier")}function Qn(a){return(a+xg(1,1,1970))%7+1}function Rn(a){a=xg(1,1,a)-xg(1,1,1970);switch(Qn(a)){case 1:a+=0;break;case 2:a-=1;break;case 3:a-=2;break;case 4:a-=3;break;case 5:a+=3;break;case 6:a+=2;break;case 7:a+=1}Qn(a);return a}
+function dn(a,b,c){var d,e=0,f=0,g=0,h,k,l,p="",m;-62135596800<=b&&64092211199>=b||U(a,"time2str("+b+",...); argument out of range");b=Math.floor(b+0.5);h=Math.abs(b)/86400;d=Math.floor(h);0>b&&(d=h==Math.floor(h)?-d:-(d+1));yg(d+xg(1,1,1970),function(a,b,c){g=a;f=b;e=c});k=b-86400*d|0;h=k/60;k%=60;b=h/60;h%=60;for(l=0;l=b?b:b-12):"j"==c[l]?m=String(xg(g,f,e)-xg(1,1,e)+1):"k"==c[l]?m=String(b):"l"==c[l]?m=String(0==b?12:12>=b?b:b-12):"m"==c[l]?m=String(f):"M"==c[l]?m=String(h):"p"==c[l]?m=11>=b?"AM":"PM":"P"==c[l]?m=11>=b?"am":"pm":"r"==c[l]?m=
+(0==b?12:12>=b?b:b-12)+":"+h+":"+k+" "+(11>=b?"AM":"PM"):"R"==c[l]?m=b+":"+h:"S"==c[l]?m=String(k):"T"==c[l]?m=b+":"+h+":"+k:"u"==c[l]?m=String(Qn(d)):"U"==c[l]?(m=xg(1,1,e)-xg(1,1,1970),m+=7-Qn(m),m=String((d+7-m)/7)):"V"==c[l]?(m=dkn(a)&&w("csv_driver: file name not specified\n");this.Ea=ln(a,2);if("R"==b){c?(this.data=c(a.a,b),this.cursor=0):w("csv_driver: unable to open "+this.Ea);this.Eg=0;for(Vn(this);;){Vn(this);if(this.Hb==this.Ae)break;this.Hb!=this.Be&&w(this.Ea+":"+this.count+": invalid field name\n");this.Wa++;for(b=mn(a);1<=b&&nn(a,b)!=this.pb;b--);this.Oa[this.Wa]=
+b}for(b=mn(a);1<=b&&"RECNO"!=nn(a,b);b--);this.Oa[0]=b}else if("W"==b){this.data="";c=mn(a);for(b=1;b<=c;b++)this.data+=nn(a,b)+(be;e++)this.data='"'==d[e]?this.data+'""':this.data+d[e];this.data+='"'}this.data+=bkn(a)&&w("json driver: file name not specified");this.Ea=ln(a,2);if("R"==b)for(this.Oa={},c?(this.data=c(a.a,b),"string"==typeof this.data&&(this.data=JSON.parse(this.data)),this.cursor=1):w("json driver: unable to open "+this.Ea),a=0,b=this.data[0];ad||(ho(a,function(a,b){if(a.da==Zb)if(a.la[b.s]==A)a.la[b.s]=A;else if(a.la[b.s]==Na)a.la[b.s]=0<=a.Aa[b.s]?G:Ua;else return 1;return 0}).s=b.ea,c=0.5*(b.d+b.c),e=Math.floor(c+0.5),Math.abs(c-e)<=d&&(c=e),b.c=b.d=c)}
+function po(a,b){var c,d,e,f;f=1E-9+1E-12*Math.abs(b.c);if(b.d-b.c>f)return 0;c=ho(a,function(a,b){var c,d;if(a.da==Zb)if(a.ka[b.F]==A)a.ka[b.F]=A;else if(a.ka[b.F]==Na){d=b.l;for(c=b.k;null!=c;c=c.e)d-=c.j*a.Aa[c.Oa];a.ka[b.F]=0<=d?G:Ua}else return 1;return 0});c.F=b.C;c.l=b.u;c.k=null;if(a.da==Zb)for(d=b.k;null!=d;d=d.I)e={},e.Oa=d.n.ea,e.j=d.j,e.e=c.k,c.k=e;c=0.5*(b.d+b.c);d=Math.floor(c+0.5);Math.abs(c-d)<=f&&(c=d);b.c=b.d=c;return 1}
+function qo(a,b){if(0.001b.d)return 1;b.c=-s;b.d=+s;lo(a,b);return 0}function ro(a,b){function c(){e.m=G;b.d=b.c}function d(){e.m=Ua;b.c=b.d}var e;if(0.001b.u&&b.d==+s)return 1;e=ho(a,function(a,b){a.da==Zb&&(a.ka[b.F]=b.m);return 0});e.F=b.C;b.c==-s&&b.d==+s?(e.m=Ra,b.c=b.d=0):b.d==+s?c():b.c==-s?d():b.c!=b.d?2.220446049250313E-16<=b.u?c():-2.220446049250313E-16>=b.u?d():Math.abs(b.c)<=Math.abs(b.d)?c():d():e.m=Na;no(a,b);return 0}
+function so(a,b){var c;if(a.Ra)if(c=Math.floor(b+0.5),1E-5>=Math.abs(b-c))b=c;else return 2;if(a.c!=-s){c=a.Ra?1E-5:1E-5+1E-8*Math.abs(a.c);if(ba.d+c)return 1;if(b>a.d-0.001*c)return a.c=a.d,0}a.c=a.d=b;return 0}
+function to(a,b){var c,d,e;e=b.k;d=e.f;c=so(d,b.c/e.j);if(0!=c)return c;c=ho(a,function(a,b){var c,d;if(a.da==Zb){if(a.ka[b.F]!=Na)return 1;a.la[b.s]=Na;a.ka[b.F]=A}if(a.da!=Sc){d=b.l;for(c=b.k;null!=c;c=c.e)d-=c.j*a.Aa[c.Oa];a.Aa[b.s]=d/b.Da}return 0});c.s=b.ea;c.F=d.C;c.Da=e.j;c.l=d.u;c.k=null;if(a.da!=Sc)for(e=d.k;null!=e;e=e.I)e.n!=b&&(d={},d.Oa=e.n.ea,d.j=e.j,d.e=c.k,c.k=d);jo(a,b);return 0}
+function uo(a,b){var c;a.Ra&&(c=Math.floor(b+0.5),b=1E-5>=Math.abs(b-c)?c:Math.ceil(b));if(a.c!=-s&&(c=a.Ra?0.001:0.001+1E-6*Math.abs(a.c),ba.d+c)return 4;if(b>a.d-0.001*c)return a.c=a.d,3}c=a.c==-s?2:a.Ra&&b>a.c+0.5?2:b>a.c+0.3*(1+Math.abs(a.c))?2:1;a.c=b;return c}
+function vo(a,b){var c;a.Ra&&(c=Math.floor(b+0.5),b=1E-5>=Math.abs(b-c)?c:Math.floor(b));if(a.d!=+s&&(c=a.Ra?0.001:0.001+1E-6*Math.abs(a.d),b>a.d-c))return 0;if(a.c!=-s){c=a.Ra?1E-5:1E-5+1E-8*Math.abs(a.c);if(bb.Da&&b.d!=+s||!b.Vf))return a.ka[b.F]=G,c();if(-1E-7>d&&(0b.Da&&b.c!=-s||!b.gg))return a.ka[b.F]=Ua,e();if(b.c!=-s&&b.d==+s)a.la[b.s]=G;else if(b.c==-s&&b.d!=+s)a.la[b.s]=Ua;else if(b.c!=-s&&b.d!=+s)a.la[b.s]=b.Da*a.Pa[b.F]<=0.5*
+(b.c+b.d)?G:Ua;else return 1;a.ka[b.F]=A;a.Aa[b.s]=d/b.Da}else return 1}a.da==le&&(a.Aa[b.s]=2.220446049250313E-16d&&b.gg?d/b.Da:0);return 0});c.s=b.ea;c.F=d.C;c.Da=e.j;c.l=d.u;c.c=b.c;c.d=b.d;c.Vf=g;c.gg=h;c.k=null;if(a.da!=Sc)for(d=d.k;null!=d;d=d.I)d!=e&&(f={},f.Oa=d.n.ea,f.j=d.j,f.e=c.k,c.k=f);jo(a,b);return g>=h?g:h}
+function xo(a,b){var c,d,e,f;e=b.k;d=e.n;c=ho(a,function(a,b){var c,d;if(a.da==Zb){if(a.la[b.s]==A||a.la[b.s]==Ra)a.ka[b.F]=a.la[b.s];else if(a.la[b.s]==G)a.ka[b.F]=0b.d+l))return 1;b.c=-s;b.d=+s;e=ho(a,function(a,b){if(a.da==Zb)if(a.la[b.s]==A)a.la[b.s]=A;else if(a.la[b.s]==Na)a.la[b.s]=b.m;else return 1;return 0});e.s=f.ea;e.m=-1;g=b.u/g.j;if(2.220446049250313E-16g)if(f.d!=+s)d();else{if(-1E-5>g)return 2;c()}else f.d==+s?c():f.c==-s?d():Math.abs(f.c)<=Math.abs(f.d)?c():d();return 0}
+function zo(a,b,c){var d,e=null,f,g,h;d=1;for(g=b.k;null!=g;g=g.B)de&&fg.j||0!=c&&0e)||a.d!=+s&&(b=0.001+1E-6*Math.abs(a.d),a.d+bd&&(c=a.c+b<=e?c|1:c|2));a.d!=+s&&(b=1E-9+1E-12*Math.abs(a.d),a.d+b=d?c|
+16:c|32));return c}function Bo(a,b,c){a.da==Zb&&(a=ho(a,function(a,b){if(a.da!=Zb)return 1;a.la[b.s]=a.la[b.s]==A?A:b.m;return 0}),a.s=b.ea,a.m=b.d==+s?G:b.c==-s?Ua:b.c!=b.d?0==c?Ua:G:Na);0==c?b.c=-s:1==c&&(b.d=+s)}
+function Co(a){var b,c,d,e,f,g,h,k,l,p,m;k=l=p=m=0;for(d=a.td;null!=d;d=d.ca)if(d.Ra&&d.c!=d.d&&(0!=d.c||1!=d.d))if(-1E6>d.c||1E6=c;)g++,c+=c;p+=g;b=ho(a,function(a,b){var c,d,e=a.Pa[b.F];c=1;for(d=2;cd.fa&&(b-=d.fa);for(d=a;null!=d;d=d.e)if(Math.abs(d.fa)>b)return 0;e=null;for(d=a;null!=d;d=d.e)if(null==e||Math.abs(e.fa)>Math.abs(d.fa))e=d;f=null;for(d=a;null!=d;d=d.e)d!=e&&(null==f||Math.abs(f.fa)>Math.abs(d.fa))&&(f=d);if(Math.abs(e.fa)+Math.abs(f.fa)<=b+(0.001+1E-6*Math.abs(b)))return 0;b=1;for(d=a;null!=d;d=d.e)0=f;f++){if(0==f){if(b.d==+s)continue;e=Do(b,1);h=+b.d}else{if(b.c==-s)continue;e=Do(b,-1);h=-b.c}c=Eo(e,h,function(a){h=a});if(1==f&&1==c||2==c){g++;if(b.c==-s||b.d==+s)c=null;else for(c=eo(a),0==f?(c.c=b.c,c.d=+s):(c.c=-s,c.d=b.d),d=b.k;null!=d;d=d.B)go(c,d.f,d.j);io(b);b.c=-s;for(b.d=h;null!=e;e=e.e)go(b,e.jc,e.fa);null!=c&&(b=c)}}return g}
+function Go(a,b,c){var d,e;for(d=a;null!=d;d=d.e);e=0;for(d=a;null!=d;d=d.e)if(1!=d.fa)if(-1==d.fa)e++;else break;if(null==d&&b==1-e)return 1;for(d=a;null!=d;d=d.e)0>d.fa&&(b-=d.fa);if(0.001>b)return 0;e=1E-9+1E-12*Math.abs(b);for(d=a;null!=d;d=d.e)if(Math.abs(d.fa)=f;f++){if(0==f){if(b.c==-s)continue;e=Do(b,1);h=+b.c}else{if(b.d==+s)continue;e=Do(b,-1);h=-b.d}c=Go(e,h,function(a){h=a});if(1==f&&1==c||2==c){g++;if(b.c==-s||b.d==+s)c=null;else for(c=eo(a),0==f?(c.c=-s,c.d=b.d):(c.c=b.c,c.d=+s),d=b.k;null!=d;d=d.B)go(c,d.f,d.j);io(b);b.c=h;for(b.d=+s;null!=e;e=e.e)go(b,e.jc,e.fa);null!=c&&(b=c)}}return g}
+function Io(a,b,c){var d,e=0,f,g;f=0;for(d=a;null!=d;d=d.e)if(0=0.01*(1+d.fa)&&(d.fa=g,e++))):(a=f-d.fa,b=g&&g-d.fa>=0.01*(1-d.fa)&&(d.fa=g,f+=a-b,b=a,e++))));c(b);return e}
+function Jo(a,b){var c,d,e,f,g=Array(2),h;for(f=g[0]=g[1]=0;1>=f;f++){if(0==f){if(b.c==-s)continue;e=Do(b,1);h=+b.c}else{if(b.d==+s)continue;e=Do(b,-1);h=-b.d}g[f]=Io(e,h,function(a){h=a});if(0=k){co(a,e);if(2<=k)for(f=e.k;null!=f;f=f.I)$n(a,f.n);3==k&&no(a,e);return 0}if(4==k)return ac}k=Ao(b);if(51==k)return ac;if(0==(k&15))b.c!=-s&&Bo(a,
+b,0);else if(1!=(k&15)&&2==(k&15)&&0==zo(a,b,0))return d();if(0==(k&240))b.d!=+s&&Bo(a,b,1);else if(16!=(k&240)&&32==(k&240)&&0==zo(a,b,1))return d();if(b.c==-s&&b.d==+s){for(f=b.k;null!=f;f=f.B)co(a,f.f);lo(a,b);return 0}return a.da==Sc&&c&&0>Lo(a,b,1)?ac:0}
+function Lo(a,b,c){var d,e,f,g,h,k=0,l;h=!1;e=1;for(d=b.k;null!=d;d=d.B)d.f.qb.qb=-s,d.f.ub.ub=+s,ed.j&&d.f.c==-s)if(null==e)e=d;else{h=!0;break}if(!h){h=b.c;for(d=b.k;null!=d;d=d.B)d!=e&&(h=0=+g?d.f.qb.qb=d.f.d+h/d.j:d.j<=-g&&(d.f.ub.ub=d.f.c+h/d.j);else e.j>=+g?e.f.qb.qb=h/e.j:e.j<=-g&&(e.f.ub.ub=h/e.j)}}h=!1;if(b.d!=+s){e=
+null;for(d=b.k;null!=d;d=d.B)if(0d.j&&d.f.d==+s)if(null==e)e=d;else{h=!0;break}if(!h){h=b.d;for(d=b.k;null!=d;d=d.B)d!=e&&(h=0=+g?d.f.ub.ub=d.f.c+h/d.j:d.j<=-g&&(d.f.qb.qb=d.f.d+h/d.j);else e.j>=+g?e.f.ub.ub=h/e.j:e.j<=-g&&(e.f.qb.qb=h/e.j)}}for(e=b.k;null!=e;)for(d=e.f,e=e.B,g=0;1>=g;g++){f=d.c;l=d.d;if(0==g){if(d.qb.qb==-s)continue;h=uo(d,d.qb.qb)}else{if(d.ub.ub==+s)continue;h=vo(d,d.ub.ub)}if(0==h||1==h)d.c=
+f,d.d=l;else if(2==h||3==h){k++;if(c)for(f=d.k;null!=f;f=f.I)f.n!=b&&$n(a,f.n);if(3==h){no(a,d);break}}else if(4==h)return-1}return k}function Mo(a,b){var c,d,e;if(null==b.k){e=ro(a,b);if(0==e)return 0;if(1==e)return bc}if(null==b.k.I){c=b.k.n;var f=function(){xo(a,b);if(c.c==-s&&c.d==+s){for(d=c.k;null!=d;d=d.B)co(a,d.f);lo(a,c)}else $n(a,c);return 0};if(c.c==c.d){if(!b.Ra)return f()}else if(!b.Ra){e=yo(a,b);if(0==e)return f();if(1!=e&&2==e)return bc}}return 0}
+function $b(a,b){var c,d,e;for(c=a.Db;null!=c;c=d)d=c.e,c.c==-s&&c.d==+s&&lo(a,c);for(c=a.Db;null!=c;c=d)d=c.e,c.c!=-s&&c.d!=+s&&c.cLo(a,c,0))return c=ac;return 0}
+function Tc(a,b){var c,d,e,f,g;c=$b(a,1);if(0!=c)return c;b.sd&&Co(a);g=0;for(c=a.Pc;null!=c;c=d)if(d=c.ca,(c.c!=-s||c.d!=+s)&&c.c!=c.d&&null!=c.k&&null!=c.k.B){for(f=c.k;null!=f&&(e=f.f,e.Ra&&0==e.c&&1==e.d);f=f.B);null==f&&(g+=Fo(a,c))}0=c;b++,c++)a.nb[b]=a.nb[b]-a.nb[c]&2147483647;for(c=1;55>=b;b++,c++)a.nb[b]=a.nb[b]-a.nb[c]&2147483647;a.Qf=54;return a.nb[55]}function sg(){var a={},b;a.nb=Array(56);a.nb[0]=-1;for(b=1;55>=b;b++)a.nb[b]=0;a.Qf=0;Md(a,1);return a}
+function Md(a,b){var c,d,e=1;b=d=b-0&2147483647;a.nb[55]=d;for(c=21;c;c=(c+21)%55)a.nb[c]=e,e=d-e&2147483647,b=b&1?1073741824+(b>>1):b>>1,e=e-b&2147483647,d=a.nb[c];No(a);No(a);No(a);No(a);No(a)}function dm(a){return 0<=a.nb[a.Qf]?a.nb[a.Qf--]:No(a)}function bn(a){var b;do b=dm(a);while(2147483648<=b);return b%16777216}function tg(a){a=dm(a)/2147483647;return-0.3*(1-a)+0.7*a}var De=1,Fe=2,We=1,Se=2,Ce=0,Ue=1E-10;function Te(a,b,c){return(b-1)*a.K+c-b*(b-1)/2}
+function Oo(a,b,c){var d;0==b?(b=1,d=0):Math.abs(a)<=Math.abs(b)?(a=-a/b,d=1/Math.sqrt(1+a*a),b=d*a):(a=-b/a,b=1/Math.sqrt(1+a*a),d=b*a);c(b,d)}
+function Ve(a,b,c){for(var d=a.i,e=a.Nb,f=a.v,g,h,k,l,p,m;bg;l--,
+p--)m-=f[p]*k[l];k[g]=m/f[p]}for(g=1;g<=b;g++)c[h[g]+d]=k[g]}}
+var gc=exports.glp_scale_prob=function(a,b){function c(a,b){var c,d,e;d=1;for(c=a.n[b].k;null!=c;c=c.B)if(e=Math.abs(c.j),e=e*c.n.ma*c.f.va,null==c.ua||d>e)d=e;return d}function d(a,b){var c,d,e;d=1;for(c=a.n[b].k;null!=c;c=c.B)if(e=Math.abs(c.j),e=e*c.n.ma*c.f.va,null==c.ua||de)d=e;return d}function f(a,b){var c,d,e;d=1;for(c=a.f[b].k;null!=c;c=c.I)if(e=Math.abs(c.j),e=e*
+c.n.ma*c.f.va,null==c.ra||de)d=e;return d}function h(a){var b,c,e;for(b=c=1;b<=a.g;b++)if(e=d(a,b),1==b||c=e;e++)if(e==b)for(c=1;c<=a.g;c++)g=d(a,c),zb(a,c,Cb(a,c)/g);else for(c=1;c<=a.i;c++)g=f(a,c),Ab(a,c,Db(a,c)/g)}function l(a){var b,e,f;for(b=e=1;b<=a.g;b++)if(f=d(a,b)/c(a,b),1==b||ep(a);for(b=1;15>=b;b++){y=m;m=h(a)/g(a);if(10.9*y)break;y=a;for(var E=k,C=void 0,D=C=void 0,H=void 0,D=0;1>=D;D++)if(D==E)for(C=1;C<=y.g;C++)H=c(y,C)*d(y,C),zb(y,C,Cb(y,C)/Math.sqrt(H));else for(C=1;C<=y.i;C++)H=e(y,C)*f(y,C),Ab(y,C,Db(y,C)/Math.sqrt(H))}}b&~(Uc|Vc|Wc|Xc|hc)&&w("glp_scale_prob: flags = "+b+"; invalid scaling options");b&hc&&(b=Uc|Vc|Xc);(function(a,b){function c(a,b,d,e){return a+": min|aij| = "+b+" max|aij| = "+d+" ratio = "+
+e+""}var d,e;x("Scaling...");Eb(a);d=g(a);e=h(a);x(c(" A",d,e,e/d));if(0.1<=d&&10>=e&&(x("Problem data seem to be well scaled"),b&Xc))return;b&Uc&&(m(a),d=g(a),e=h(a),x(c("GM",d,e,e/d)));b&Vc&&(k(a,l(a)>p(a)),d=g(a),e=h(a),x(c("EQ",d,e,e/d)));if(b&Wc){for(d=1;d<=a.g;d++)zb(a,d,ug(Cb(a,d)));for(d=1;d<=a.i;d++)Ab(a,d,ug(Db(a,d)));d=g(a);e=h(a);x(c("2N",d,e,e/d))}})(a,b)};
+function Pb(a,b){function c(a,b,c,d){var e=a.g,f=a.Ca,g=a.Ba,h=a.Ia;a=a.head[b];if(a<=e)e=1,c[1]=a,d[1]=1;else for(b=f[a-e],e=f[a-e+1]-b,ga(c,1,g,b,e),ga(d,1,h,b,e),c=1;c<=e;c++)d[c]=-d[c];return e}function d(a){var b=od(a.U,a.g,c,a);a.valid=0==b;return b}function e(a,b,c){var d=a.g,e;if(c<=d){var f=Array(2);e=Array(2);f[1]=c;e[1]=1;b=Ne(a.U,b,1,f,0,e)}else{var g=a.Ca,f=a.Ba,h=a.Ia;e=a.jb;var k;k=g[c-d];c=g[c-d+1];g=0;for(d=k;d=-b)continue;break;case Ua:if(k<=+b)continue;break;case Ra:if(-b<=k&&k<=+b)continue;break;case Na:continue}k=k*k/f[g];l