) [node]"
- },
- {
- "pc": "0x00000000009f13ec",
- "symbol": "node::OnFatalError(char const*, char const*) [node]"
- },
- {
- "pc": "0x0000000000b5da9e",
- "symbol": "v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [node]"
- },
- {
- "pc": "0x0000000000b5de19",
- "symbol": "v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [node]"
- },
- {
- "pc": "0x0000000000d0a765",
- "symbol": " [node]"
- },
- {
- "pc": "0x0000000000d0adf6",
- "symbol": "v8::internal::Heap::RecomputeLimits(v8::internal::GarbageCollector) [node]"
- },
- {
- "pc": "0x0000000000d1760a",
- "symbol": "v8::internal::Heap::PerformGarbageCollection(v8::internal::GarbageCollector, v8::GCCallbackFlags) [node]"
- },
- {
- "pc": "0x0000000000d18515",
- "symbol": "v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [node]"
- },
- {
- "pc": "0x0000000000d1afcc",
- "symbol": "v8::internal::Heap::AllocateRawWithRetryOrFail(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [node]"
- },
- {
- "pc": "0x0000000000ce19bb",
- "symbol": "v8::internal::Factory::NewFillerObject(int, bool, v8::internal::AllocationType, v8::internal::AllocationOrigin) [node]"
- },
- {
- "pc": "0x00000000010246ce",
- "symbol": "v8::internal::Runtime_AllocateInYoungGeneration(int, unsigned long*, v8::internal::Isolate*) [node]"
- },
- {
- "pc": "0x00000000013a71b9",
- "symbol": " [node]"
- }
- ],
- "javascriptHeap": {
- "totalMemory": 2157629440,
- "totalCommittedMemory": 2151008656,
- "usedMemory": 2141505488,
- "availableMemory": 50475080,
- "memoryLimit": 2197815296,
- "heapSpaces": {
- "read_only_space": {
- "memorySize": 262144,
- "committedMemory": 33088,
- "capacity": 32808,
- "used": 32808,
- "available": 0
- },
- "new_space": {
- "memorySize": 8388608,
- "committedMemory": 2822400,
- "capacity": 4189824,
- "used": 946176,
- "available": 3243648
- },
- "old_space": {
- "memorySize": 2078949376,
- "committedMemory": 2078195528,
- "capacity": 2074693408,
- "used": 2071837656,
- "available": 2855752
- },
- "code_space": {
- "memorySize": 2527232,
- "committedMemory": 2459360,
- "capacity": 2163968,
- "used": 2163968,
- "available": 0
- },
- "map_space": {
- "memorySize": 1576960,
- "committedMemory": 1573160,
- "capacity": 1019840,
- "used": 1019840,
- "available": 0
- },
- "large_object_space": {
- "memorySize": 65605632,
- "committedMemory": 65605632,
- "capacity": 65244624,
- "used": 65244624,
- "available": 0
- },
- "code_large_object_space": {
- "memorySize": 319488,
- "committedMemory": 319488,
- "capacity": 260416,
- "used": 260416,
- "available": 0
- },
- "new_large_object_space": {
- "memorySize": 0,
- "committedMemory": 0,
- "capacity": 4189824,
- "used": 0,
- "available": 4189824
- }
- }
- },
- "resourceUsage": {
- "userCpuSeconds": 697.026,
- "kernelCpuSeconds": 30.8195,
- "cpuConsumptionPercent": 24.053,
- "maxRss": 2264485888,
- "pageFaults": {
- "IORequired": 0,
- "IONotRequired": 2769524
- },
- "fsActivity": {
- "reads": 0,
- "writes": 251088
- }
- },
- "uvthreadResourceUsage": {
- "userCpuSeconds": 279.526,
- "kernelCpuSeconds": 11.6456,
- "cpuConsumptionPercent": 9.62231,
- "fsActivity": {
- "reads": 0,
- "writes": 0
- }
- },
- "libuv": [
- ],
- "environmentVariables": {
- "GJS_DEBUG_TOPICS": "JS ERROR;JS LOG",
- "LESSOPEN": "| /usr/bin/lesspipe %s",
- "npm_package_devDependencies_rollup": "^2.3.4",
- "npm_config_cache_lock_stale": "60000",
- "npm_config_ham_it_up": "",
- "npm_config_legacy_bundling": "",
- "npm_config_sign_git_tag": "",
- "LANGUAGE": "en",
- "USER": "ledda",
- "LC_TIME": "de_DE.UTF-8",
- "npm_package_devDependencies__rollup_plugin_node_resolve": "^11.0.0",
- "npm_config_user_agent": "npm/6.14.5 node/v12.16.1 linux x64",
- "npm_config_always_auth": "",
- "npm_package_devDependencies_rollup_plugin_css_only": "^3.1.0",
- "npm_config_bin_links": "true",
- "npm_config_key": "",
- "SSH_AGENT_PID": "3025",
- "XDG_SESSION_TYPE": "x11",
- "GIT_ASKPASS": "/usr/share/code/resources/app/extensions/git/dist/askpass.sh",
- "npm_config_allow_same_version": "",
- "npm_config_description": "true",
- "npm_config_fetch_retries": "2",
- "npm_config_heading": "npm",
- "npm_config_if_present": "",
- "npm_config_init_version": "1.0.0",
- "npm_config_user": "1000",
- "npm_node_execpath": "/home/ledda/.nvm/versions/node/v12.16.1/bin/node",
- "SHLVL": "1",
- "npm_config_prefer_online": "",
- "npm_config_noproxy": "",
- "HOME": "/home/ledda",
- "CHROME_DESKTOP": "code-url-handler.desktop",
- "npm_config_force": "",
- "TERM_PROGRAM_VERSION": "1.52.0",
- "DESKTOP_SESSION": "ubuntu",
- "NVM_BIN": "/home/ledda/.nvm/versions/node/v12.16.1/bin",
- "npm_config_only": "",
- "npm_config_read_only": "",
- "NVM_INC": "/home/ledda/.nvm/versions/node/v12.16.1/include/node",
- "GIO_LAUNCHED_DESKTOP_FILE": "/usr/share/applications/code.desktop",
- "npm_package_devDependencies_svelte_preprocess": "^4.0.0",
- "npm_package_dependencies_three": "^0.128.0",
- "npm_config_cache_min": "10",
- "npm_config_init_license": "ISC",
- "GNOME_SHELL_SESSION_MODE": "ubuntu",
- "GTK_MODULES": "gail:atk-bridge",
- "VSCODE_GIT_ASKPASS_MAIN": "/usr/share/code/resources/app/extensions/git/dist/askpass-main.js",
- "npm_package_devDependencies_svelte_check": "^1.0.0",
- "npm_config_editor": "vi",
- "npm_config_rollback": "true",
- "npm_config_tag_version_prefix": "v",
- "LC_MONETARY": "de_DE.UTF-8",
- "VSCODE_GIT_ASKPASS_NODE": "/usr/share/code/code",
- "MANAGERPID": "2840",
- "npm_config_cache_max": "Infinity",
- "npm_config_timing": "",
- "npm_config_userconfig": "/home/ledda/.npmrc",
- "DBUS_SESSION_BUS_ADDRESS": "unix:path=/run/user/1000/bus",
- "npm_config_engine_strict": "",
- "npm_config_init_author_name": "",
- "npm_config_init_author_url": "",
- "npm_config_preid": "",
- "npm_config_tmp": "/tmp",
- "COLORTERM": "truecolor",
- "GIO_LAUNCHED_DESKTOP_FILE_PID": "77123",
- "npm_package_description": "Custom Somaesque cube solver webapp",
- "npm_package_devDependencies_typescript": "^4.0.0",
- "npm_config_depth": "Infinity",
- "npm_config_package_lock_only": "",
- "npm_config_save_dev": "",
- "npm_config_usage": "",
- "NVM_DIR": "/home/ledda/.nvm",
- "npm_config_metrics_registry": "https://registry.npmjs.org/",
- "npm_config_cafile": "",
- "npm_config_otp": "",
- "npm_config_package_lock": "true",
- "npm_config_progress": "true",
- "npm_config_https_proxy": "",
- "npm_config_save_prod": "",
- "MANDATORY_PATH": "/usr/share/gconf/ubuntu.mandatory.path",
- "IM_CONFIG_PHASE": "1",
- "npm_package_scripts_dev": "rollup -c -w",
- "npm_config_audit": "true",
- "npm_config_cidr": "",
- "npm_config_onload_script": "",
- "npm_config_sso_type": "oauth",
- "LOGNAME": "ledda",
- "npm_config_rebuild_bundle": "true",
- "npm_config_save_bundle": "",
- "npm_config_shell": "/bin/bash",
- "JOURNAL_STREAM": "9:51482",
- "_": "/home/ledda/.nvm/versions/node/v12.16.1/bin/npm",
- "npm_package_devDependencies__rollup_plugin_commonjs": "^17.0.0",
- "npm_config_dry_run": "",
- "npm_config_format_package_lock": "true",
- "npm_config_prefix": "/home/ledda/.nvm/versions/node/v12.16.1",
- "XDG_SESSION_CLASS": "user",
- "DEFAULTS_PATH": "/usr/share/gconf/ubuntu.default.path",
- "npm_config_scope": "",
- "npm_config_browser": "",
- "npm_config_cache_lock_wait": "10000",
- "npm_config_ignore_prepublish": "",
- "npm_config_registry": "https://registry.npmjs.org/",
- "npm_config_save_optional": "",
- "npm_config_searchopts": "",
- "npm_config_versions": "",
- "USERNAME": "ledda",
- "TERM": "xterm-256color",
- "npm_config_cache": "/home/ledda/.npm",
- "npm_config_proxy": "",
- "npm_config_send_metrics": "",
- "GNOME_DESKTOP_SESSION_ID": "this-is-deprecated",
- "npm_package_scripts_start": "sirv public --no-clear",
- "npm_package_dependencies_sirv_cli": "^1.0.0",
- "npm_config_global_style": "",
- "npm_config_ignore_scripts": "",
- "npm_config_version": "",
- "WINDOWPATH": "2",
- "npm_config_local_address": "",
- "npm_config_viewer": "man",
- "npm_config_node_gyp": "/home/ledda/.nvm/versions/node/v12.16.1/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js",
- "PATH": "/home/ledda/.nvm/versions/node/v12.16.1/lib/node_modules/npm/node_modules/npm-lifecycle/node-gyp-bin:/home/ledda/Documents/Projects/soma/node_modules/.bin:/home/ledda/.local/bin:/home/ledda/.nvm/versions/node/v12.16.1/bin:/usr/lib/jvm/java-1.11.0-openjdk-amd64/bin:/home/ledda/.local/bin:/usr/lib/jvm/java-1.11.0-openjdk-amd64/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/usr/local/texlive/2020/bin/x86_64-linux:/home/ledda/.local/bin:/usr/local/texlive/2020/bin/x86_64-linux:/home/ledda/.local/bin:/usr/local/texlive/2020/bin/i386-linux:/home/ledda/.local/bin:/home/ledda/.local/bin:/usr/local/texlive/2020/bin/i386-linux",
- "SESSION_MANAGER": "local/ledda-desktop:@/tmp/.ICE-unix/3074,unix/ledda-desktop:/tmp/.ICE-unix/3074",
- "INVOCATION_ID": "930ec6cef1e44490b3814c04c1c8d766",
- "PAPERSIZE": "a4",
- "npm_package_name": "soma",
- "npm_config_audit_level": "low",
- "npm_config_prefer_offline": "",
- "NODE": "/home/ledda/.nvm/versions/node/v12.16.1/bin/node",
- "XDG_MENU_PREFIX": "gnome-",
- "LC_ADDRESS": "de_DE.UTF-8",
- "XDG_RUNTIME_DIR": "/run/user/1000",
- "npm_package_devDependencies_rollup_plugin_svelte": "^7.0.0",
- "npm_config_color": "true",
- "npm_config_sign_git_commit": "",
- "DISPLAY": ":0",
- "npm_package_devDependencies__rollup_plugin_typescript": "^8.0.0",
- "npm_config_fetch_retry_mintimeout": "10000",
- "npm_config_maxsockets": "50",
- "npm_config_offline": "",
- "npm_config_sso_poll_frequency": "500",
- "LANG": "en_US.UTF-8",
- "XDG_CURRENT_DESKTOP": "Unity",
- "LC_TELEPHONE": "de_DE.UTF-8",
- "npm_package_devDependencies_rollup_plugin_terser": "^7.0.0",
- "npm_config_umask": "0002",
- "XMODIFIERS": "@im=ibus",
- "XDG_SESSION_DESKTOP": "ubuntu",
- "XAUTHORITY": "/run/user/1000/gdm/Xauthority",
- "LS_COLORS": "rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:",
- "VSCODE_GIT_IPC_HANDLE": "/run/user/1000/vscode-git-06d741d5d2.sock",
- "TERM_PROGRAM": "vscode",
- "npm_package_gitHead": "1c60c4f0d48d9df6f9ba4cf9014dd437e2eb2ec5",
- "npm_config_fund": "true",
- "npm_config_fetch_retry_maxtimeout": "60000",
- "npm_config_loglevel": "notice",
- "npm_config_logs_max": "10",
- "npm_config_message": "%s",
- "npm_lifecycle_script": "rollup -c -w",
- "SSH_AUTH_SOCK": "/run/user/1000/keyring/ssh",
- "ORIGINAL_XDG_CURRENT_DESKTOP": "ubuntu:GNOME",
- "npm_package_devDependencies__tsconfig_svelte": "^1.0.0",
- "npm_config_ca": "",
- "npm_config_cert": "",
- "npm_config_global": "",
- "npm_config_link": "",
- "SHELL": "/bin/bash",
- "LC_NAME": "de_DE.UTF-8",
- "npm_package_version": "1.0.0",
- "npm_config_access": "",
- "npm_config_also": "",
- "npm_config_save": "true",
- "npm_config_unicode": "true",
- "npm_lifecycle_event": "dev",
- "QT_ACCESSIBILITY": "1",
- "GDMSESSION": "ubuntu",
- "npm_package_scripts_build": "rollup -c",
- "npm_package_devDependencies_svelte": "^3.0.0",
- "npm_package_devDependencies_tslib": "^2.0.0",
- "npm_config_argv": "{\"remain\":[],\"cooked\":[\"run\",\"dev\"],\"original\":[\"run\",\"dev\"]}",
- "npm_config_before": "",
- "npm_config_long": "",
- "npm_config_production": "",
- "npm_config_searchlimit": "20",
- "npm_config_unsafe_perm": "true",
- "npm_config_update_notifier": "true",
- "LESSCLOSE": "/usr/bin/lesspipe %s %s",
- "npm_config_auth_type": "legacy",
- "npm_config_node_version": "12.16.1",
- "npm_config_tag": "latest",
- "LC_MEASUREMENT": "de_DE.UTF-8",
- "npm_package_scripts_validate": "svelte-check",
- "npm_config_git_tag_version": "true",
- "npm_config_commit_hooks": "true",
- "npm_config_script_shell": "",
- "npm_config_shrinkwrap": "true",
- "GPG_AGENT_INFO": "/run/user/1000/gnupg/S.gpg-agent:0:1",
- "GJS_DEBUG_OUTPUT": "stderr",
- "LC_IDENTIFICATION": "de_DE.UTF-8",
- "npm_package_license": "ISC",
- "npm_config_fetch_retry_factor": "10",
- "npm_config_save_exact": "",
- "npm_config_strict_ssl": "true",
- "QT_IM_MODULE": "ibus",
- "npm_config_dev": "",
- "npm_config_globalconfig": "/home/ledda/.nvm/versions/node/v12.16.1/etc/npmrc",
- "npm_config_init_module": "/home/ledda/.npm-init.js",
- "npm_config_parseable": "",
- "JAVA_HOME": "/usr/lib/jvm/java-1.11.0-openjdk-amd64",
- "PWD": "/home/ledda/Documents/Projects/soma",
- "npm_config_globalignorefile": "/home/ledda/.nvm/versions/node/v12.16.1/etc/npmignore",
- "npm_execpath": "/home/ledda/.nvm/versions/node/v12.16.1/lib/node_modules/npm/bin/npm-cli.js",
- "XDG_CONFIG_DIRS": "/etc/xdg/xdg-ubuntu:/etc/xdg",
- "NVM_CD_FLAGS": "",
- "XDG_DATA_DIRS": "/usr/share/ubuntu:/usr/local/share/:/usr/share/:/var/lib/snapd/desktop",
- "npm_package_author_name": "Daniel Ledda",
- "npm_config_cache_lock_retries": "10",
- "npm_config_searchstaleness": "900",
- "LC_NUMERIC": "de_DE.UTF-8",
- "npm_config_node_options": "",
- "npm_config_save_prefix": "^",
- "npm_config_scripts_prepend_node_path": "warn-only",
- "BOOST_LIBRARYDIR": "/usr/lib",
- "LC_PAPER": "de_DE.UTF-8",
- "npm_package_devDependencies__types_three": "^0.128.0",
- "npm_package_devDependencies_rollup_plugin_livereload": "^2.0.0",
- "npm_config_group": "1000",
- "npm_config_init_author_email": "",
- "npm_config_searchexclude": "",
- "npm_config_git": "git",
- "npm_config_optional": "true",
- "npm_config_json": "",
- "INIT_CWD": "/home/ledda/Documents/Projects/soma",
- "ROLLUP_WATCH": "true"
- },
- "userLimits": {
- "core_file_size_blocks": {
- "soft": 0,
- "hard": "unlimited"
- },
- "data_seg_size_kbytes": {
- "soft": "unlimited",
- "hard": "unlimited"
- },
- "file_size_blocks": {
- "soft": "unlimited",
- "hard": "unlimited"
- },
- "max_locked_memory_bytes": {
- "soft": 67108864,
- "hard": 67108864
- },
- "max_memory_size_kbytes": {
- "soft": "unlimited",
- "hard": "unlimited"
- },
- "open_files": {
- "soft": 1048576,
- "hard": 1048576
- },
- "stack_size_bytes": {
- "soft": 8388608,
- "hard": "unlimited"
- },
- "cpu_time_seconds": {
- "soft": "unlimited",
- "hard": "unlimited"
- },
- "max_user_processes": {
- "soft": 47649,
- "hard": 47649
- },
- "virtual_memory_kbytes": {
- "soft": "unlimited",
- "hard": "unlimited"
- }
- },
- "sharedObjects": [
- "linux-vdso.so.1",
- "/lib/x86_64-linux-gnu/libdl.so.2",
- "/usr/lib/x86_64-linux-gnu/libstdc++.so.6",
- "/lib/x86_64-linux-gnu/libm.so.6",
- "/lib/x86_64-linux-gnu/libgcc_s.so.1",
- "/lib/x86_64-linux-gnu/libpthread.so.0",
- "/lib/x86_64-linux-gnu/libc.so.6",
- "/lib64/ld-linux-x86-64.so.2"
- ]
-}
\ No newline at end of file
diff --git a/src/App.svelte b/src/App.svelte
index 4b85a7a..1852a6e 100644
--- a/src/App.svelte
+++ b/src/App.svelte
@@ -3,6 +3,15 @@
import SolutionInteractor from "./SolutionInteractor.svelte";
+
+
+
+
+
+
+
-
-
-
-
-
-
-
diff --git a/src/CubeInput.svelte b/src/CubeInput.svelte
index 43e8dd1..5f9e004 100644
--- a/src/CubeInput.svelte
+++ b/src/CubeInput.svelte
@@ -1,5 +1,5 @@
selectedCube.set(cubeNo)}
+ on:mousedown={onClickCube}
>
Cube: {cubeNo + 1}
{#each {length: dimension} as _, x}
@@ -92,8 +97,16 @@
font-size: 1em;
text-align: center;
}
+ .cube:hover:not(.active) {
+ transform: scale(1.03);
+ filter: brightness(1.1);
+ }
.cube {
- padding: 1em;
+ border-radius: 1em;
+ background-color: #666666;
+ cursor: pointer;
+ transition: transform 200ms;
+ padding: 1em 2em 1em 2em;
user-select: none;
}
.cell {
diff --git a/src/OBJLoader.js b/src/OBJLoader.js
new file mode 100644
index 0000000..768a1ff
--- /dev/null
+++ b/src/OBJLoader.js
@@ -0,0 +1,911 @@
+import {
+ BufferGeometry,
+ FileLoader,
+ Float32BufferAttribute,
+ Group,
+ LineBasicMaterial,
+ LineSegments,
+ Loader,
+ Material,
+ Mesh,
+ MeshPhongMaterial,
+ Points,
+ PointsMaterial,
+ Vector3
+} from 'three';
+
+// o object_name | g group_name
+const _object_pattern = /^[og]\s*(.+)?/;
+// mtllib file_reference
+const _material_library_pattern = /^mtllib /;
+// usemtl material_name
+const _material_use_pattern = /^usemtl /;
+// usemap map_name
+const _map_use_pattern = /^usemap /;
+
+const _vA = new Vector3();
+const _vB = new Vector3();
+const _vC = new Vector3();
+
+const _ab = new Vector3();
+const _cb = new Vector3();
+
+function ParserState() {
+
+ const state = {
+ objects: [],
+ object: {},
+
+ vertices: [],
+ normals: [],
+ colors: [],
+ uvs: [],
+
+ materials: {},
+ materialLibraries: [],
+
+ startObject: function ( name, fromDeclaration ) {
+
+ // If the current object (initial from reset) is not from a g/o declaration in the parsed
+ // file. We need to use it for the first parsed g/o to keep things in sync.
+ if ( this.object && this.object.fromDeclaration === false ) {
+
+ this.object.name = name;
+ this.object.fromDeclaration = ( fromDeclaration !== false );
+ return;
+
+ }
+
+ const previousMaterial = ( this.object && typeof this.object.currentMaterial === 'function' ? this.object.currentMaterial() : undefined );
+
+ if ( this.object && typeof this.object._finalize === 'function' ) {
+
+ this.object._finalize( true );
+
+ }
+
+ this.object = {
+ name: name || '',
+ fromDeclaration: ( fromDeclaration !== false ),
+
+ geometry: {
+ vertices: [],
+ normals: [],
+ colors: [],
+ uvs: [],
+ hasUVIndices: false
+ },
+ materials: [],
+ smooth: true,
+
+ startMaterial: function ( name, libraries ) {
+
+ const previous = this._finalize( false );
+
+ // New usemtl declaration overwrites an inherited material, except if faces were declared
+ // after the material, then it must be preserved for proper MultiMaterial continuation.
+ if ( previous && ( previous.inherited || previous.groupCount <= 0 ) ) {
+
+ this.materials.splice( previous.index, 1 );
+
+ }
+
+ const material = {
+ index: this.materials.length,
+ name: name || '',
+ mtllib: ( Array.isArray( libraries ) && libraries.length > 0 ? libraries[ libraries.length - 1 ] : '' ),
+ smooth: ( previous !== undefined ? previous.smooth : this.smooth ),
+ groupStart: ( previous !== undefined ? previous.groupEnd : 0 ),
+ groupEnd: - 1,
+ groupCount: - 1,
+ inherited: false,
+
+ clone: function ( index ) {
+
+ const cloned = {
+ index: ( typeof index === 'number' ? index : this.index ),
+ name: this.name,
+ mtllib: this.mtllib,
+ smooth: this.smooth,
+ groupStart: 0,
+ groupEnd: - 1,
+ groupCount: - 1,
+ inherited: false
+ };
+ cloned.clone = this.clone.bind( cloned );
+ return cloned;
+
+ }
+ };
+
+ this.materials.push( material );
+
+ return material;
+
+ },
+
+ currentMaterial: function () {
+
+ if ( this.materials.length > 0 ) {
+
+ return this.materials[ this.materials.length - 1 ];
+
+ }
+
+ return undefined;
+
+ },
+
+ _finalize: function ( end ) {
+
+ const lastMultiMaterial = this.currentMaterial();
+ if ( lastMultiMaterial && lastMultiMaterial.groupEnd === - 1 ) {
+
+ lastMultiMaterial.groupEnd = this.geometry.vertices.length / 3;
+ lastMultiMaterial.groupCount = lastMultiMaterial.groupEnd - lastMultiMaterial.groupStart;
+ lastMultiMaterial.inherited = false;
+
+ }
+
+ // Ignore objects tail materials if no face declarations followed them before a new o/g started.
+ if ( end && this.materials.length > 1 ) {
+
+ for ( let mi = this.materials.length - 1; mi >= 0; mi -- ) {
+
+ if ( this.materials[ mi ].groupCount <= 0 ) {
+
+ this.materials.splice( mi, 1 );
+
+ }
+
+ }
+
+ }
+
+ // Guarantee at least one empty material, this makes the creation later more straight forward.
+ if ( end && this.materials.length === 0 ) {
+
+ this.materials.push( {
+ name: '',
+ smooth: this.smooth
+ } );
+
+ }
+
+ return lastMultiMaterial;
+
+ }
+ };
+
+ // Inherit previous objects material.
+ // Spec tells us that a declared material must be set to all objects until a new material is declared.
+ // If a usemtl declaration is encountered while this new object is being parsed, it will
+ // overwrite the inherited material. Exception being that there was already face declarations
+ // to the inherited material, then it will be preserved for proper MultiMaterial continuation.
+
+ if ( previousMaterial && previousMaterial.name && typeof previousMaterial.clone === 'function' ) {
+
+ const declared = previousMaterial.clone( 0 );
+ declared.inherited = true;
+ this.object.materials.push( declared );
+
+ }
+
+ this.objects.push( this.object );
+
+ },
+
+ finalize: function () {
+
+ if ( this.object && typeof this.object._finalize === 'function' ) {
+
+ this.object._finalize( true );
+
+ }
+
+ },
+
+ parseVertexIndex: function ( value, len ) {
+
+ const index = parseInt( value, 10 );
+ return ( index >= 0 ? index - 1 : index + len / 3 ) * 3;
+
+ },
+
+ parseNormalIndex: function ( value, len ) {
+
+ const index = parseInt( value, 10 );
+ return ( index >= 0 ? index - 1 : index + len / 3 ) * 3;
+
+ },
+
+ parseUVIndex: function ( value, len ) {
+
+ const index = parseInt( value, 10 );
+ return ( index >= 0 ? index - 1 : index + len / 2 ) * 2;
+
+ },
+
+ addVertex: function ( a, b, c ) {
+
+ const src = this.vertices;
+ const dst = this.object.geometry.vertices;
+
+ dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
+ dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] );
+ dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] );
+
+ },
+
+ addVertexPoint: function ( a ) {
+
+ const src = this.vertices;
+ const dst = this.object.geometry.vertices;
+
+ dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
+
+ },
+
+ addVertexLine: function ( a ) {
+
+ const src = this.vertices;
+ const dst = this.object.geometry.vertices;
+
+ dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
+
+ },
+
+ addNormal: function ( a, b, c ) {
+
+ const src = this.normals;
+ const dst = this.object.geometry.normals;
+
+ dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
+ dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] );
+ dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] );
+
+ },
+
+ addFaceNormal: function ( a, b, c ) {
+
+ const src = this.vertices;
+ const dst = this.object.geometry.normals;
+
+ _vA.fromArray( src, a );
+ _vB.fromArray( src, b );
+ _vC.fromArray( src, c );
+
+ _cb.subVectors( _vC, _vB );
+ _ab.subVectors( _vA, _vB );
+ _cb.cross( _ab );
+
+ _cb.normalize();
+
+ dst.push( _cb.x, _cb.y, _cb.z );
+ dst.push( _cb.x, _cb.y, _cb.z );
+ dst.push( _cb.x, _cb.y, _cb.z );
+
+ },
+
+ addColor: function ( a, b, c ) {
+
+ const src = this.colors;
+ const dst = this.object.geometry.colors;
+
+ if ( src[ a ] !== undefined ) dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
+ if ( src[ b ] !== undefined ) dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] );
+ if ( src[ c ] !== undefined ) dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] );
+
+ },
+
+ addUV: function ( a, b, c ) {
+
+ const src = this.uvs;
+ const dst = this.object.geometry.uvs;
+
+ dst.push( src[ a + 0 ], src[ a + 1 ] );
+ dst.push( src[ b + 0 ], src[ b + 1 ] );
+ dst.push( src[ c + 0 ], src[ c + 1 ] );
+
+ },
+
+ addDefaultUV: function () {
+
+ const dst = this.object.geometry.uvs;
+
+ dst.push( 0, 0 );
+ dst.push( 0, 0 );
+ dst.push( 0, 0 );
+
+ },
+
+ addUVLine: function ( a ) {
+
+ const src = this.uvs;
+ const dst = this.object.geometry.uvs;
+
+ dst.push( src[ a + 0 ], src[ a + 1 ] );
+
+ },
+
+ addFace: function ( a, b, c, ua, ub, uc, na, nb, nc ) {
+
+ const vLen = this.vertices.length;
+
+ let ia = this.parseVertexIndex( a, vLen );
+ let ib = this.parseVertexIndex( b, vLen );
+ let ic = this.parseVertexIndex( c, vLen );
+
+ this.addVertex( ia, ib, ic );
+ this.addColor( ia, ib, ic );
+
+ // normals
+
+ if ( na !== undefined && na !== '' ) {
+
+ const nLen = this.normals.length;
+
+ ia = this.parseNormalIndex( na, nLen );
+ ib = this.parseNormalIndex( nb, nLen );
+ ic = this.parseNormalIndex( nc, nLen );
+
+ this.addNormal( ia, ib, ic );
+
+ } else {
+
+ this.addFaceNormal( ia, ib, ic );
+
+ }
+
+ // uvs
+
+ if ( ua !== undefined && ua !== '' ) {
+
+ const uvLen = this.uvs.length;
+
+ ia = this.parseUVIndex( ua, uvLen );
+ ib = this.parseUVIndex( ub, uvLen );
+ ic = this.parseUVIndex( uc, uvLen );
+
+ this.addUV( ia, ib, ic );
+
+ this.object.geometry.hasUVIndices = true;
+
+ } else {
+
+ // add placeholder values (for inconsistent face definitions)
+
+ this.addDefaultUV();
+
+ }
+
+ },
+
+ addPointGeometry: function ( vertices ) {
+
+ this.object.geometry.type = 'Points';
+
+ const vLen = this.vertices.length;
+
+ for ( let vi = 0, l = vertices.length; vi < l; vi ++ ) {
+
+ const index = this.parseVertexIndex( vertices[ vi ], vLen );
+
+ this.addVertexPoint( index );
+ this.addColor( index );
+
+ }
+
+ },
+
+ addLineGeometry: function ( vertices, uvs ) {
+
+ this.object.geometry.type = 'Line';
+
+ const vLen = this.vertices.length;
+ const uvLen = this.uvs.length;
+
+ for ( let vi = 0, l = vertices.length; vi < l; vi ++ ) {
+
+ this.addVertexLine( this.parseVertexIndex( vertices[ vi ], vLen ) );
+
+ }
+
+ for ( let uvi = 0, l = uvs.length; uvi < l; uvi ++ ) {
+
+ this.addUVLine( this.parseUVIndex( uvs[ uvi ], uvLen ) );
+
+ }
+
+ }
+
+ };
+
+ state.startObject( '', false );
+
+ return state;
+
+}
+
+//
+
+class OBJLoader extends Loader {
+
+ constructor( manager ) {
+
+ super( manager );
+
+ this.materials = null;
+
+ }
+
+ load( url, onLoad, onProgress, onError ) {
+
+ const scope = this;
+
+ const loader = new FileLoader( this.manager );
+ loader.setPath( this.path );
+ loader.setRequestHeader( this.requestHeader );
+ loader.setWithCredentials( this.withCredentials );
+ loader.load( url, function ( text ) {
+
+ try {
+
+ onLoad( scope.parse( text ) );
+
+ } catch ( e ) {
+
+ if ( onError ) {
+
+ onError( e );
+
+ } else {
+
+ console.error( e );
+
+ }
+
+ scope.manager.itemError( url );
+
+ }
+
+ }, onProgress, onError );
+
+ }
+
+ setMaterials( materials ) {
+
+ this.materials = materials;
+
+ return this;
+
+ }
+
+ parse( text ) {
+
+ const state = new ParserState();
+
+ if ( text.indexOf( '\r\n' ) !== - 1 ) {
+
+ // This is faster than String.split with regex that splits on both
+ text = text.replace( /\r\n/g, '\n' );
+
+ }
+
+ if ( text.indexOf( '\\\n' ) !== - 1 ) {
+
+ // join lines separated by a line continuation character (\)
+ text = text.replace( /\\\n/g, '' );
+
+ }
+
+ const lines = text.split( '\n' );
+ let line = '', lineFirstChar = '';
+ let lineLength = 0;
+ let result = [];
+
+ // Faster to just trim left side of the line. Use if available.
+ const trimLeft = ( typeof ''.trimLeft === 'function' );
+
+ for ( let i = 0, l = lines.length; i < l; i ++ ) {
+
+ line = lines[ i ];
+
+ line = trimLeft ? line.trimLeft() : line.trim();
+
+ lineLength = line.length;
+
+ if ( lineLength === 0 ) continue;
+
+ lineFirstChar = line.charAt( 0 );
+
+ // @todo invoke passed in handler if any
+ if ( lineFirstChar === '#' ) continue;
+
+ if ( lineFirstChar === 'v' ) {
+
+ const data = line.split( /\s+/ );
+
+ switch ( data[ 0 ] ) {
+
+ case 'v':
+ state.vertices.push(
+ parseFloat( data[ 1 ] ),
+ parseFloat( data[ 2 ] ),
+ parseFloat( data[ 3 ] )
+ );
+ if ( data.length >= 7 ) {
+
+ state.colors.push(
+ parseFloat( data[ 4 ] ),
+ parseFloat( data[ 5 ] ),
+ parseFloat( data[ 6 ] )
+
+ );
+
+ } else {
+
+ // if no colors are defined, add placeholders so color and vertex indices match
+
+ state.colors.push( undefined, undefined, undefined );
+
+ }
+
+ break;
+ case 'vn':
+ state.normals.push(
+ parseFloat( data[ 1 ] ),
+ parseFloat( data[ 2 ] ),
+ parseFloat( data[ 3 ] )
+ );
+ break;
+ case 'vt':
+ state.uvs.push(
+ parseFloat( data[ 1 ] ),
+ parseFloat( data[ 2 ] )
+ );
+ break;
+
+ }
+
+ } else if ( lineFirstChar === 'f' ) {
+
+ const lineData = line.substr( 1 ).trim();
+ const vertexData = lineData.split( /\s+/ );
+ const faceVertices = [];
+
+ // Parse the face vertex data into an easy to work with format
+
+ for ( let j = 0, jl = vertexData.length; j < jl; j ++ ) {
+
+ const vertex = vertexData[ j ];
+
+ if ( vertex.length > 0 ) {
+
+ const vertexParts = vertex.split( '/' );
+ faceVertices.push( vertexParts );
+
+ }
+
+ }
+
+ // Draw an edge between the first vertex and all subsequent vertices to form an n-gon
+
+ const v1 = faceVertices[ 0 ];
+
+ for ( let j = 1, jl = faceVertices.length - 1; j < jl; j ++ ) {
+
+ const v2 = faceVertices[ j ];
+ const v3 = faceVertices[ j + 1 ];
+
+ state.addFace(
+ v1[ 0 ], v2[ 0 ], v3[ 0 ],
+ v1[ 1 ], v2[ 1 ], v3[ 1 ],
+ v1[ 2 ], v2[ 2 ], v3[ 2 ]
+ );
+
+ }
+
+ } else if ( lineFirstChar === 'l' ) {
+
+ const lineParts = line.substring( 1 ).trim().split( ' ' );
+ let lineVertices = [];
+ const lineUVs = [];
+
+ if ( line.indexOf( '/' ) === - 1 ) {
+
+ lineVertices = lineParts;
+
+ } else {
+
+ for ( let li = 0, llen = lineParts.length; li < llen; li ++ ) {
+
+ const parts = lineParts[ li ].split( '/' );
+
+ if ( parts[ 0 ] !== '' ) lineVertices.push( parts[ 0 ] );
+ if ( parts[ 1 ] !== '' ) lineUVs.push( parts[ 1 ] );
+
+ }
+
+ }
+
+ state.addLineGeometry( lineVertices, lineUVs );
+
+ } else if ( lineFirstChar === 'p' ) {
+
+ const lineData = line.substr( 1 ).trim();
+ const pointData = lineData.split( ' ' );
+
+ state.addPointGeometry( pointData );
+
+ } else if ( ( result = _object_pattern.exec( line ) ) !== null ) {
+
+ // o object_name
+ // or
+ // g group_name
+
+ // WORKAROUND: https://bugs.chromium.org/p/v8/issues/detail?id=2869
+ // let name = result[ 0 ].substr( 1 ).trim();
+ const name = ( ' ' + result[ 0 ].substr( 1 ).trim() ).substr( 1 );
+
+ state.startObject( name );
+
+ } else if ( _material_use_pattern.test( line ) ) {
+
+ // material
+
+ state.object.startMaterial( line.substring( 7 ).trim(), state.materialLibraries );
+
+ } else if ( _material_library_pattern.test( line ) ) {
+
+ // mtl file
+
+ state.materialLibraries.push( line.substring( 7 ).trim() );
+
+ } else if ( _map_use_pattern.test( line ) ) {
+
+ // the line is parsed but ignored since the loader assumes textures are defined MTL files
+ // (according to https://www.okino.com/conv/imp_wave.htm, 'usemap' is the old-style Wavefront texture reference method)
+
+ console.warn( 'THREE.OBJLoader: Rendering identifier "usemap" not supported. Textures must be defined in MTL files.' );
+
+ } else if ( lineFirstChar === 's' ) {
+
+ result = line.split( ' ' );
+
+ // smooth shading
+
+ // @todo Handle files that have varying smooth values for a set of faces inside one geometry,
+ // but does not define a usemtl for each face set.
+ // This should be detected and a dummy material created (later MultiMaterial and geometry groups).
+ // This requires some care to not create extra material on each smooth value for "normal" obj files.
+ // where explicit usemtl defines geometry groups.
+ // Example asset: examples/models/obj/cerberus/Cerberus.obj
+
+ /*
+ * http://paulbourke.net/dataformats/obj/
+ * or
+ * http://www.cs.utah.edu/~boulos/cs3505/obj_spec.pdf
+ *
+ * From chapter "Grouping" Syntax explanation "s group_number":
+ * "group_number is the smoothing group number. To turn off smoothing groups, use a value of 0 or off.
+ * Polygonal elements use group numbers to put elements in different smoothing groups. For free-form
+ * surfaces, smoothing groups are either turned on or off; there is no difference between values greater
+ * than 0."
+ */
+ if ( result.length > 1 ) {
+
+ const value = result[ 1 ].trim().toLowerCase();
+ state.object.smooth = ( value !== '0' && value !== 'off' );
+
+ } else {
+
+ // ZBrush can produce "s" lines #11707
+ state.object.smooth = true;
+
+ }
+
+ const material = state.object.currentMaterial();
+ if ( material ) material.smooth = state.object.smooth;
+
+ } else {
+
+ // Handle null terminated files without exception
+ if ( line === '\0' ) continue;
+
+ console.warn( 'THREE.OBJLoader: Unexpected line: "' + line + '"' );
+
+ }
+
+ }
+
+ state.finalize();
+
+ const container = new Group();
+ container.materialLibraries = [].concat( state.materialLibraries );
+
+ const hasPrimitives = ! ( state.objects.length === 1 && state.objects[ 0 ].geometry.vertices.length === 0 );
+
+ if ( hasPrimitives === true ) {
+
+ for ( let i = 0, l = state.objects.length; i < l; i ++ ) {
+
+ const object = state.objects[ i ];
+ const geometry = object.geometry;
+ const materials = object.materials;
+ const isLine = ( geometry.type === 'Line' );
+ const isPoints = ( geometry.type === 'Points' );
+ let hasVertexColors = false;
+
+ // Skip o/g line declarations that did not follow with any faces
+ if ( geometry.vertices.length === 0 ) continue;
+
+ const buffergeometry = new BufferGeometry();
+
+ buffergeometry.setAttribute( 'position', new Float32BufferAttribute( geometry.vertices, 3 ) );
+
+ if ( geometry.normals.length > 0 ) {
+
+ buffergeometry.setAttribute( 'normal', new Float32BufferAttribute( geometry.normals, 3 ) );
+
+ }
+
+ if ( geometry.colors.length > 0 ) {
+
+ hasVertexColors = true;
+ buffergeometry.setAttribute( 'color', new Float32BufferAttribute( geometry.colors, 3 ) );
+
+ }
+
+ if ( geometry.hasUVIndices === true ) {
+
+ buffergeometry.setAttribute( 'uv', new Float32BufferAttribute( geometry.uvs, 2 ) );
+
+ }
+
+ // Create materials
+
+ const createdMaterials = [];
+
+ for ( let mi = 0, miLen = materials.length; mi < miLen; mi ++ ) {
+
+ const sourceMaterial = materials[ mi ];
+ const materialHash = sourceMaterial.name + '_' + sourceMaterial.smooth + '_' + hasVertexColors;
+ let material = state.materials[ materialHash ];
+
+ if ( this.materials !== null ) {
+
+ material = this.materials.create( sourceMaterial.name );
+
+ // mtl etc. loaders probably can't create line materials correctly, copy properties to a line material.
+ if ( isLine && material && ! ( material instanceof LineBasicMaterial ) ) {
+
+ const materialLine = new LineBasicMaterial();
+ Material.prototype.copy.call( materialLine, material );
+ materialLine.color.copy( material.color );
+ material = materialLine;
+
+ } else if ( isPoints && material && ! ( material instanceof PointsMaterial ) ) {
+
+ const materialPoints = new PointsMaterial( { size: 10, sizeAttenuation: false } );
+ Material.prototype.copy.call( materialPoints, material );
+ materialPoints.color.copy( material.color );
+ materialPoints.map = material.map;
+ material = materialPoints;
+
+ }
+
+ }
+
+ if ( material === undefined ) {
+
+ if ( isLine ) {
+
+ material = new LineBasicMaterial();
+
+ } else if ( isPoints ) {
+
+ material = new PointsMaterial( { size: 1, sizeAttenuation: false } );
+
+ } else {
+
+ material = new MeshPhongMaterial();
+
+ }
+
+ material.name = sourceMaterial.name;
+ material.flatShading = sourceMaterial.smooth ? false : true;
+ material.vertexColors = hasVertexColors;
+
+ state.materials[ materialHash ] = material;
+
+ }
+
+ createdMaterials.push( material );
+
+ }
+
+ // Create mesh
+
+ let mesh;
+
+ if ( createdMaterials.length > 1 ) {
+
+ for ( let mi = 0, miLen = materials.length; mi < miLen; mi ++ ) {
+
+ const sourceMaterial = materials[ mi ];
+ buffergeometry.addGroup( sourceMaterial.groupStart, sourceMaterial.groupCount, mi );
+
+ }
+
+ if ( isLine ) {
+
+ mesh = new LineSegments( buffergeometry, createdMaterials );
+
+ } else if ( isPoints ) {
+
+ mesh = new Points( buffergeometry, createdMaterials );
+
+ } else {
+
+ mesh = new Mesh( buffergeometry, createdMaterials );
+
+ }
+
+ } else {
+
+ if ( isLine ) {
+
+ mesh = new LineSegments( buffergeometry, createdMaterials[ 0 ] );
+
+ } else if ( isPoints ) {
+
+ mesh = new Points( buffergeometry, createdMaterials[ 0 ] );
+
+ } else {
+
+ mesh = new Mesh( buffergeometry, createdMaterials[ 0 ] );
+
+ }
+
+ }
+
+ mesh.name = object.name;
+
+ container.add( mesh );
+
+ }
+
+ } else {
+
+ // if there is only the default parser state object with no geometry data, interpret data as point cloud
+
+ if ( state.vertices.length > 0 ) {
+
+ const material = new PointsMaterial( { size: 1, sizeAttenuation: false } );
+
+ const buffergeometry = new BufferGeometry();
+
+ buffergeometry.setAttribute( 'position', new Float32BufferAttribute( state.vertices, 3 ) );
+
+ if ( state.colors.length > 0 && state.colors[ 0 ] !== undefined ) {
+
+ buffergeometry.setAttribute( 'color', new Float32BufferAttribute( state.colors, 3 ) );
+ material.vertexColors = true;
+
+ }
+
+ const points = new Points( buffergeometry, material );
+ container.add( points );
+
+ }
+
+ }
+
+ return container;
+
+ }
+
+}
+
+export { OBJLoader };
diff --git a/src/OrbitControls.js b/src/OrbitControls.js
deleted file mode 100644
index 5c4f668..0000000
--- a/src/OrbitControls.js
+++ /dev/null
@@ -1,795 +0,0 @@
-import {
- EventDispatcher,
- MOUSE,
- Quaternion,
- Spherical,
- TOUCH,
- Vector2,
- Vector3
-} from 'three';
-
-// 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;
-
- // 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.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( 'wheel', onMouseWheel );
- scope.domElement.removeEventListener( 'touchstart', onTouchStart );
- scope.domElement.removeEventListener( 'touchend', onTouchEnd );
- scope.domElement.removeEventListener( 'touchmove', onTouchMove );
- scope.domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove );
- scope.domElement.ownerDocument.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();
-
- 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( event ) {
- if ( event.touches.length == 1 ) {
- rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
- } else {
- const x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
- const y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );
- rotateStart.set( x, y );
- }
-
- }
-
- function handleTouchStartPan( event ) {
- if ( event.touches.length == 1 ) {
- panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
- } else {
- const x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
- const y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );
- panStart.set( x, y );
- }
-
- }
-
- function handleTouchStartDolly( event ) {
- const dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
- const dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
- const distance = Math.sqrt( dx * dx + dy * dy );
- dollyStart.set( 0, distance );
- }
-
- function handleTouchStartDollyPan( event ) {
- if ( scope.enableZoom ) handleTouchStartDolly( event );
- if ( scope.enablePan ) handleTouchStartPan( event );
- }
-
- function handleTouchStartDollyRotate( event ) {
- if ( scope.enableZoom ) handleTouchStartDolly( event );
- if ( scope.enableRotate ) handleTouchStartRotate( event );
- }
-
- function handleTouchMoveRotate( event ) {
- if ( event.touches.length == 1 ) {
- rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
- } else {
- const x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
- const y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );
- 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 ( event.touches.length == 1 ) {
- panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
- } else {
- const x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
- const y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );
- panEnd.set( x, y );
- }
- panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
- pan( panDelta.x, panDelta.y );
- panStart.copy( panEnd );
- }
-
- function handleTouchMoveDolly( event ) {
- const dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
- const dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
- 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;
- switch ( event.pointerType ) {
- case 'mouse':
- case 'pen':
- onMouseDown( event );
- break;
- // TODO touch
- }
- }
-
- function onPointerMove( event ) {
- if ( scope.enabled === false ) return;
- switch ( event.pointerType ) {
- case 'mouse':
- case 'pen':
- onMouseMove( event );
- break;
- // TODO touch
- }
-
- }
-
- function onPointerUp( event ) {
- switch ( event.pointerType ) {
- case 'mouse':
- case 'pen':
- onMouseUp( event );
- break;
- // TODO touch
- }
- }
-
- function onMouseDown( event ) {
- // Prevent the browser from scrolling.
- event.preventDefault();
- // Manually set the focus since calling preventDefault above
- // prevents the browser from setting it automatically.
- scope.domElement.focus ? scope.domElement.focus() : window.focus();
- 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.domElement.ownerDocument.addEventListener( 'pointermove', onPointerMove );
- scope.domElement.ownerDocument.addEventListener( 'pointerup', onPointerUp );
- scope.dispatchEvent( _startEvent );
- }
- }
-
- function onMouseMove( event ) {
- if ( scope.enabled === false ) return;
- event.preventDefault();
- 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 ) {
- scope.domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove );
- scope.domElement.ownerDocument.removeEventListener( 'pointerup', onPointerUp );
- if ( scope.enabled === false ) return;
- 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 ) {
- if ( scope.enabled === false ) return;
- event.preventDefault(); // prevent scrolling
- switch ( event.touches.length ) {
- case 1:
- switch ( scope.touches.ONE ) {
- case TOUCH.ROTATE:
- if ( scope.enableRotate === false ) return;
- handleTouchStartRotate( event );
- state = STATE.TOUCH_ROTATE;
- break;
- case TOUCH.PAN:
- if ( scope.enablePan === false ) return;
- handleTouchStartPan( event );
- 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( event );
- state = STATE.TOUCH_DOLLY_PAN;
- break;
- case TOUCH.DOLLY_ROTATE:
- if ( scope.enableZoom === false && scope.enableRotate === false ) return;
- handleTouchStartDollyRotate( event );
- 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 ) {
- if ( scope.enabled === false ) return;
- event.preventDefault(); // prevent scrolling
- 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 ) {
- if ( scope.enabled === false ) return;
- handleTouchEnd( event );
- scope.dispatchEvent( _endEvent );
- state = STATE.NONE;
- }
-
- function onContextMenu( event ) {
- if ( scope.enabled === false ) return;
- event.preventDefault();
- }
-
- scope.domElement.addEventListener( 'contextmenu', onContextMenu );
- scope.domElement.addEventListener( 'pointerdown', onPointerDown );
- scope.domElement.addEventListener( 'wheel', onMouseWheel, { passive: false } );
- scope.domElement.addEventListener( 'touchstart', onTouchStart, { passive: false } );
- scope.domElement.addEventListener( 'touchend', onTouchEnd );
- scope.domElement.addEventListener( 'touchmove', onTouchMove, { 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.mouseButtons.LEFT = MOUSE.ROTATE;
- this.mouseButtons.RIGHT = null;
- this.mouseButtons.MIDDLE = null;
- }
-}
-
-export { OrbitControls, MapControls };
\ No newline at end of file
diff --git a/src/Polycube3D.svelte b/src/Polycube3D.svelte
index 2eda31a..35848c6 100644
--- a/src/Polycube3D.svelte
+++ b/src/Polycube3D.svelte
@@ -1,22 +1,39 @@
\ No newline at end of file
+>
+
+
\ No newline at end of file
diff --git a/src/PolycubeScene.ts b/src/PolycubeScene.ts
new file mode 100644
index 0000000..dc279ea
--- /dev/null
+++ b/src/PolycubeScene.ts
@@ -0,0 +1,165 @@
+import * as THREE from 'three';
+import { OBJLoader } from './OBJLoader.js';
+import VoxelSpace from './solver/VoxelSpace';
+import type SomaSolution from "./solver/SomaSolution";
+import RotationControl from "./RotationControl";
+
+export default class PolycubeScene {
+ private renderer: THREE.WebGLRenderer;
+ private camera: THREE.Camera;
+ private mainScene: THREE.Scene;
+ private polycubeMeshes: THREE.Mesh[] = [];
+ private controls: RotationControl;
+ private light: THREE.Light;
+ private lastDims: number = 0;
+ private lastColor: string = "#FF0000";
+ private lastPolycube: bigint = 0n;
+ private cubeMaterial: THREE.MeshPhongMaterial;
+ private materials: Record = {};
+ private cubeGeometry: THREE.BufferGeometry;
+ private cubeScene: THREE.Scene;
+
+ constructor(el: HTMLCanvasElement, onReady: () => any, onError: (err: Error) => any) {
+ this.init(el).then(onReady).catch(onError);
+ }
+
+ private async init(el: HTMLCanvasElement) {
+ this.renderer = new THREE.WebGLRenderer({canvas: el});
+ this.setupCamera(el.clientWidth / el.clientHeight);
+ this.setupLight();
+ try {
+ await this.createCubeGeometry();
+ } catch (err) {
+ throw new Error(err);
+ }
+ this.createCubeMaterial("red");
+ this.mainScene = new THREE.Scene();
+ this.cubeScene = new THREE.Scene();
+ this.mainScene.add(this.cubeScene, this.camera);
+ this.camera.add(this.light);
+ this.cubeScene.rotateX(Math.PI/4);
+ this.cubeScene.rotateY(Math.PI/4);
+ this.controls = new RotationControl(this.cubeScene, this.camera, el);
+ requestAnimationFrame((timestamp) => this.render(timestamp));
+ }
+
+ private setupCamera(aspect: number) {
+ const fov = 60;
+ const near = 0.1;
+ const far = 15;
+ this.camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
+ this.camera.position.z = 6;
+ this.camera.lookAt(0, 0, 0);
+ }
+
+ private setPolycube(polycube: bigint, dims: number, color: string) {
+ if (dims !== this.lastDims) {
+ this.updateCubesFromDims(dims);
+ }
+
+ if (polycube !== this.lastPolycube) {
+ let i = 0;
+ const voxelSpace = new VoxelSpace(0, [dims, dims, dims], polycube, true);
+ const newDims = voxelSpace.getDims();
+ this.polycubeMeshes.forEach(mesh => {
+ mesh.position.set(1000, 1000, 1000);
+ mesh.material = this.cubeMaterial;
+ });
+ voxelSpace.forEachCell((val: boolean, x: number, y: number, z: number) => {
+ if (val) {
+ this.polycubeMeshes[i].position.set(
+ -((newDims[2] - 1)/2) + z,
+ ((newDims[0] - 1)/2) - x,
+ -((newDims[1] - 1)/2) + y,
+ );
+ }
+ i++;
+ });
+ this.lastPolycube = polycube;
+ }
+
+ if (color !== this.lastColor) {
+ this.cubeMaterial.color.set(color);
+ this.lastColor = color;
+ }
+ }
+
+ private updateCubesFromDims(newDims: number) {
+ const requiredCubes = newDims**3;
+ if (this.polycubeMeshes.length < requiredCubes) {
+ for (let i = this.polycubeMeshes.length; i < requiredCubes; i++) {
+ const newCube = new THREE.Mesh(this.cubeGeometry, this.cubeMaterial);
+ this.cubeScene.add(newCube);
+ this.polycubeMeshes.push(newCube);
+ }
+ }
+ if (newDims < this.lastDims || this.lastDims === 0) {
+ this.polycubeMeshes.forEach(mesh => mesh.position.set(1000, 1000, 1000));
+ }
+ this.lastDims = newDims;
+ }
+
+ private setSolution(solution: SomaSolution, colorMap: Record) {
+ const dims = solution.getDims();
+ if (dims[0] !== this.lastDims) {
+ this.updateCubesFromDims(dims[0]);
+ }
+
+ let i = 0;
+ this.polycubeMeshes.forEach(mesh => mesh.position.set(1000, 1000, 1000));
+ Object.keys(colorMap).forEach(key => {
+ if (!this.materials[key]) {
+ this.materials[key] = this.newCubeMaterial(colorMap[key]);
+ }
+ })
+ solution.forEachCell((val: number, x: number, y: number, z: number) => {
+ this.polycubeMeshes[i].position.set(
+ -((dims[2] - 1)/2) + z,
+ ((dims[0] - 1)/2) - x,
+ -((dims[1] - 1)/2) + y,
+ );
+ this.polycubeMeshes[i].material = this.materials[val];
+ i++;
+ });
+ }
+
+ private setupLight() {
+ const color = 0xFFFFFF;
+ const intensity = 1;
+ this.light = new THREE.DirectionalLight(color, intensity);
+ this.light.position.set(-1, 2, 4);
+ }
+
+ private render(time: number) {
+ this.renderer.render(this.mainScene, this.camera);
+ requestAnimationFrame((time: number) => this.render(time));
+ }
+
+ private async createCubeGeometry(): Promise {
+ const onLoaded = (obj: THREE.Mesh, resolve: () => any) => {
+ this.cubeGeometry = (obj.children[0] as THREE.Mesh).geometry;
+ this.cubeGeometry.computeVertexNormals();
+ this.cubeGeometry.computeBoundingSphere();
+ this.cubeGeometry.scale(1/this.cubeGeometry.boundingSphere.radius, 1/this.cubeGeometry.boundingSphere.radius, 1/this.cubeGeometry.boundingSphere.radius);
+ resolve();
+ };
+ const load = (resolve: () => any, reject: (err: string) => any) => {
+ const loader = new OBJLoader();
+ loader.load(
+ '../resources/bevel_cube.obj',
+ obj => onLoaded(obj, resolve),
+ () => {},
+ (err) => reject(`Error loading OBJ file: ${err}`),
+ );
+ };
+ return new Promise(load);
+ }
+
+ private newCubeMaterial(color: string) {
+ return new THREE.MeshPhongMaterial({color});
+ }
+
+ private createCubeMaterial(color: string) {
+ this.cubeMaterial = this.newCubeMaterial(color);
+ }
+}
diff --git a/src/RotationControl.ts b/src/RotationControl.ts
new file mode 100644
index 0000000..b2d9343
--- /dev/null
+++ b/src/RotationControl.ts
@@ -0,0 +1,45 @@
+import type * as THREE from 'three';
+
+export default class RotationControls {
+ private static ROTATION_FACTOR = 1/200;
+ private object: THREE.Object3D;
+ private element: HTMLCanvasElement;
+ private respondToMovement: boolean = false;
+ private lastX: number = 0;
+ private lastY: number = 0;
+ private yAxis: THREE.Vector3;
+ private xAxis: THREE.Vector3;
+ private start: THREE.Euler;
+
+ constructor(object: THREE.Object3D, camera: THREE.Camera, element: HTMLCanvasElement) {
+ this.object = object;
+ this.element = element;
+ this.yAxis = object.worldToLocal(camera.up);
+ this.xAxis = object.position.sub(camera.position);
+ this.xAxis.divideScalar(Math.sqrt(this.xAxis.getComponent(0)**2 + this.xAxis.getComponent(1)**2 + this.xAxis.getComponent(2)**2));
+ this.xAxis = this.xAxis.clone().cross(this.yAxis.clone());
+ this.start = this.object.rotation.clone();
+
+ this.element.addEventListener('mousedown', (event) => {
+ if (event.button === 1) {
+ this.object.setRotationFromEuler(this.start);
+ }
+ if (!this.respondToMovement) {
+ this.lastX = event.x;
+ this.lastY = event.y;
+ this.respondToMovement = true;
+ }
+ });
+ window.addEventListener('mousemove', (ev) => this.handleMove(ev));
+ window.addEventListener('mouseup', () => this.respondToMovement = false);
+ }
+
+ private handleMove(event: MouseEvent) {
+ if (this.respondToMovement) {
+ const xDiff = event.movementX * RotationControls.ROTATION_FACTOR;
+ const yDiff = event.movementY * RotationControls.ROTATION_FACTOR;
+ this.object.rotateOnAxis(this.yAxis, xDiff);
+ this.object.rotateOnWorldAxis(this.xAxis, yDiff);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Sidebar.svelte b/src/Sidebar.svelte
index 3d64697..4dca502 100644
--- a/src/Sidebar.svelte
+++ b/src/Sidebar.svelte
@@ -1,10 +1,28 @@
@@ -25,8 +43,10 @@
-
+
+
+
\ No newline at end of file
diff --git a/src/solver/SomaSolution.ts b/src/solver/SomaSolution.ts
index f53edac..71f38aa 100644
--- a/src/solver/SomaSolution.ts
+++ b/src/solver/SomaSolution.ts
@@ -96,4 +96,18 @@ export default class SomaSolution {
clone.solutionSpaces = this.solutionSpaces.slice();
return clone;
}
+
+ getDims() {
+ return [this.dim, this.dim, this.dim];
+ }
+
+ forEachCell(cb: (val: number, x: number, y: number, z: number) => any) {
+ loopStart: for (let x = 0; x < this.dim; x++) {
+ for (let y = 0; y < this.dim; y++) {
+ for (let z = 0; z < this.dim; z++) {
+ cb(this.at(x, y, z), x, y, z);
+ }
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/src/solver/SomaSolver.ts b/src/solver/SomaSolver.ts
index 953926d..3aa0fc8 100644
--- a/src/solver/SomaSolver.ts
+++ b/src/solver/SomaSolver.ts
@@ -14,7 +14,7 @@ export default class SomaSolver {
this.solutionCube = new VoxelSpace(0, [dimension, dimension, dimension], Array(dimension**3).fill(0));
}
- solve(polycubes: VoxelSpace[]) {
+ async solve(polycubes: VoxelSpace[]) {
if (polycubes.length === 0) {
throw new Error("You must pass at least one polycube to solve the puzzle.");
}
@@ -22,11 +22,15 @@ export default class SomaSolver {
if (cumulativeSize !== this.dim**3) {
throw new Error(`The polycubes passed do not add up to exactly enough units to form a cube of dimension ${this.dim}! Got: ${cumulativeSize}, need: ${this.dim**3}`);
}
- const combosWithRots = polycubes.slice(1).map(polycube => polycube.getUniqueRotations().map(rot => rot.getAllPositionsInCube(this.dim)).flat());
+ this.solutions = [];
+ const combosWithRots = polycubes.slice(1).map(polycube => polycube.getUniqueRotations().map((rot: VoxelSpace) => rot.getAllPositionsInCube(this.dim)).flat());
const combos = [polycubes[0].getAllPositionsInCube(this.dim), ...combosWithRots];
this.backtrackSolve(this.solutionCube, combos, new SomaSolution(this.dim));
this.solutions = SomaSolution.filterUnique(this.solutions);
- this.solutions.forEach(sol => sol.print());
+ }
+
+ getSolutions() {
+ return this.solutions.slice();
}
private backtrackSolve(workingSolution: VoxelSpace, polycubes: VoxelSpace[][], currentSoln: SomaSolution, depth = 0) {
diff --git a/src/solver/VoxelSpace.ts b/src/solver/VoxelSpace.ts
index 5bf241b..8f78c9e 100644
--- a/src/solver/VoxelSpace.ts
+++ b/src/solver/VoxelSpace.ts
@@ -37,7 +37,7 @@ export default class VoxelSpace {
return this.space.toString(2);
}
- private cullEmptySpace() {
+ getExtrema() {
const extrema = {
xMax: -Infinity,
xMin: Infinity,
@@ -46,7 +46,6 @@ export default class VoxelSpace {
zMax: -Infinity,
zMin: Infinity,
};
- let newSpace = 0n;
this.forEachCell((val, x, y, z) => {
if (val) {
extrema.xMax = Math.max(extrema.xMax, x);
@@ -57,7 +56,13 @@ export default class VoxelSpace {
extrema.zMin = Math.min(extrema.zMin, z);
}
});
+ return extrema;
+ }
+
+ private cullEmptySpace() {
+ const extrema = this.getExtrema();
let index = 0n;
+ let newSpace = 0n;
for (let x = extrema.xMin; x <= extrema.xMax; x++) {
for (let y = extrema.yMin; y <= extrema.yMax; y++) {
for (let z = extrema.zMin; z <= extrema.zMax; z++) {
diff --git a/src/solver/main.js b/src/solver/main.js
deleted file mode 100644
index d6cacbb..0000000
--- a/src/solver/main.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import App from './App.svelte';
-
-const app = new App({
- target: document.body,
- props: {
- name: 'world'
- }
-});
-
-export default app;
\ No newline at end of file
diff --git a/src/solver/main.ts b/src/solver/main.ts
index 7fbe282..d7f0d73 100644
--- a/src/solver/main.ts
+++ b/src/solver/main.ts
@@ -1,109 +1,12 @@
import SomaSolver from "./SomaSolver";
import VoxelSpace from "./VoxelSpace";
-const tetromino1 = new VoxelSpace(1, [3, 3, 3], [
- true, true, true,
- false, true, false,
- false, false, false,
+type SolveStartMessageData = {polycubes: bigint[], dims: number};
- false, false, false,
- false, false, false,
- false, false, false,
+self.addEventListener('message', (event) => {
+ const {polycubes, dims} = event.data as SolveStartMessageData;
+ const solver = new SomaSolver(event.data.dims);
+ solver.solve(polycubes.map((cubeRep, i) => new VoxelSpace(i, [dims, dims, dims], cubeRep)));
+ (self as unknown as Worker).postMessage(solver.getSolutions());
+});
- false, false, false,
- false, false, false,
- false, false, false,
-], true);
-
-const tetromino2 = new VoxelSpace(2, [3, 3, 3], [
- false, false, false,
- false, false, false,
- false, true, false,
-
- false, true, false,
- false, true, false,
- false, true, false,
-
- false, false, false,
- false, false, false,
- false, false, false,
-], true);
-
-const tetromino3 = new VoxelSpace(3, [3, 3, 3], [
- true, false, false,
- true, true, false,
- false, true, false,
-
- false, false, false,
- false, false, false,
- false, false, false,
-
- false, false, false,
- false, false, false,
- false, false, false,
-], true);
-
-const tetromino4 = new VoxelSpace(4, [3, 3, 3], [
- true, true, false,
- false, false, false,
- false, false, false,
-
- true, false, false,
- true, false, false,
- false, false, false,
-
- false, false, false,
- false, false, false,
- false, false, false,
-], true);
-
-const tetromino5 = new VoxelSpace(5, [3, 3, 3], [
- true, true, false,
- false, false, false,
- false, false, false,
-
- false, true, false,
- false, true, false,
- false, false, false,
-
- false, false, false,
- false, false, false,
- false, false, false,
-], true);
-
-const tetromino6 = new VoxelSpace(6, [3, 3, 3], [
- false, false, false,
- false, false, false,
- false, true, false,
-
- false, false, false,
- false, true, false,
- false, true, true,
-
- false, false, false,
- false, false, false,
- false, false, false,
-], true);
-
-const triomino1 = new VoxelSpace(7, [3, 3, 3], [
- false, false, false,
- false, false, false,
- false, true, false,
-
- false, false, false,
- false, true, false,
- false, true, false,
-
- false, false, false,
- false, false, false,
- false, false, false,
-], true);
-
-
-
-// const cube = new VoxelSpace([3, 3, 3], Array(3**3).fill(0));
-// cube.plus(triomino1)?.plus(tetromino2, {x: 1, y: 0, z: 1})?.print();
-
-const solver = new SomaSolver(3);
-console.log("solving");
-solver.solve([triomino1, tetromino2, tetromino3, tetromino1, tetromino4, tetromino5, tetromino6]);
diff --git a/src/store.ts b/src/store.ts
index ed7255f..d26ec26 100644
--- a/src/store.ts
+++ b/src/store.ts
@@ -1,5 +1,6 @@
import { derived, writable } from 'svelte/store';
import { get } from 'svelte/store';
+import type SomaSolution from "./solver/SomaSolution";
type PolycubeInput = {
color: string,
@@ -15,16 +16,21 @@ const store = {
};
export const selectedCube = writable(0);
-export const isMaxDimension = derived(store.somaDimension, $somaDimension => $somaDimension >= MAX_DIMS);
-export const isMinDimension = derived(store.somaDimension, $somaDimension => $somaDimension <= MIN_DIMS);
-export const isMaxPolycubes = derived([store.polycubes, store.somaDimension], ([$polycubes, $somaDimension]) => $polycubes.length >= $somaDimension ** 3);
-export const isMinPolycubes = derived(store.polycubes, ($polycubes) => $polycubes.length <= 1);
+export const isMaxDimension = derived(store.somaDimension, ($somaDimension: number) => $somaDimension >= MAX_DIMS);
+export const isMinDimension = derived(store.somaDimension, ($somaDimension: number) => $somaDimension <= MIN_DIMS);
+export const isMaxPolycubes = derived(
+ [store.polycubes, store.somaDimension],
+ ([$polycubes, $somaDimension]: [PolycubeInput[], number]) => $polycubes.length >= $somaDimension ** 3);
+export const isMinPolycubes = derived(store.polycubes, ($polycubes: PolycubeInput[]) => $polycubes.length <= 1);
+export const solutions = writable([] as SomaSolution[]);
+export const activeSolution = writable(0);
+export const showingSolution = writable(false);
export const somaDimension = {
subscribe: store.somaDimension.subscribe,
inc() {
if (!get(isMaxDimension)) {
- store.somaDimension.update(dims => {
+ store.somaDimension.update((dims: number) => {
polycubes.reset(dims + 1);
return dims + 1;
});
@@ -32,7 +38,7 @@ export const somaDimension = {
},
dec() {
if (!get(isMinDimension)) {
- store.somaDimension.update(dims => {
+ store.somaDimension.update((dims: number) => {
polycubes.reset(dims - 1);
return dims - 1;
});
@@ -45,7 +51,7 @@ export const polycubes = {
addCube() {
const isMaxPolycubes = get(store.polycubes).length >= get(store.somaDimension) ** 3;
if (!isMaxPolycubes) {
- store.polycubes.update(polycubes => polycubes.concat({
+ store.polycubes.update((polycubes: PolycubeInput[]) => polycubes.concat({
rep: BigInt(0),
color: colorFromIndex(polycubes.length),
}));
@@ -54,7 +60,11 @@ export const polycubes = {
removeCube() {
const isMinPolycubes = get(store.polycubes).length <= 1;
if (!isMinPolycubes) {
- store.polycubes.update(polycubes => polycubes.splice(0, polycubes.length - 1));
+ store.polycubes.update((polycubes: PolycubeInput[]) => polycubes.splice(0, polycubes.length - 1));
+ }
+ const newLength = get(store.polycubes).length;
+ if (newLength <= get(selectedCube)) {
+ selectedCube.set(newLength - 1);
}
},
toggle(cubeIndex: number, x: number, y: number, z: number) {
@@ -76,7 +86,7 @@ export const polycubes = {
store.polycubes.set(cubes);
},
reset(dims: number) {
- store.polycubes.update(polycubes => {
+ store.polycubes.update((polycubes: PolycubeInput[]) => {
const result: PolycubeInput[] = [];
for (let i = 0; i < Math.min(polycubes.length, dims**3); i++) {
result.push({
diff --git a/src/threeTest.ts b/src/threeTest.ts
deleted file mode 100644
index 0383207..0000000
--- a/src/threeTest.ts
+++ /dev/null
@@ -1,134 +0,0 @@
-import * as THREE from 'three';
-import { MapControls } from './OrbitControls.js';
-import VoxelSpace from './solver/VoxelSpace.js';
-import {somaDimension, polycubes} from './store';
-import {get} from 'svelte/store';
-import type { MeshPhongMaterial } from 'three';
-
-export default class PolycubeScene {
- private renderer: THREE.WebGLRenderer;
- private camera: THREE.Camera;
- private mainScene: THREE.Scene;
- private polycubeMeshes: THREE.Mesh[] = [];
- private controls: typeof MapControls;
- private light: THREE.Light;
- private cameraLightScene: THREE.Group;
- private lastDims: number = 0;
- private currentPolycubeId: number = 0;
- private lastColor: string = "#FF0000";
- private lastPolycube: bigint = 0n;
-
- constructor(el: HTMLCanvasElement) {
- this.renderer = new THREE.WebGLRenderer({canvas: el});
- const fov = 75;
- const aspect = el.clientWidth / el.clientHeight;
- const near = 0.1;
- const far = 10;
- this.camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
- this.camera.position.z = 5;
- this.camera.lookAt(0, 0, 0);
- this.mainScene = new THREE.Scene();
- this.light = this.setupLight();
- this.mainScene.add(this.light);
- this.mainScene.rotateX(Math.PI/4);
- this.mainScene.rotateY(Math.PI/4);
- this.cameraLightScene = new THREE.Group();
- this.controls = new MapControls(this.camera, el);
- requestAnimationFrame((timestamp) => this.render(timestamp));
- }
-
- private setPolycube(polycube: bigint, dims: number, color: string) {
- if (dims !== this.lastDims) {
- this.mainScene.remove(...this.polycubeMeshes);
- this.polycubeMeshes = [];
- this.polycubeMeshes = Array.from(Array(dims ** 3).keys()).map(() => {
- const cube = this.newRoundedCube(0.2, 3, color);
- cube.position.set(1000, 1000, 1000);
- this.mainScene.add(cube);
- return cube;
- });
- this.lastDims = dims;
- }
-
- if (polycube !== this.lastPolycube) {
- let i = 0;
- const voxelSpace = new VoxelSpace(0, [dims, dims, dims], polycube);
- voxelSpace.forEachCell((val, x, y, z) => {
- if (val) {
- this.polycubeMeshes[i].position.set(
- -((dims - 1)/2) + z,
- ((dims - 1)/2) - y,
- -((dims - 1)/2) + x,
- );
- } else {
- this.polycubeMeshes[i].position.set(1000, 1000, 1000);
- }
- i++;
- });
- this.lastPolycube = polycube;
- }
-
- if (color !== this.lastColor) {
- this.polycubeMeshes.forEach(mesh => (mesh.material as MeshPhongMaterial).color.set(color));
- this.lastColor = color;
- }
- }
-
- private updateFromCurrentPolycube() {
- const {color: cubeColor, rep: cubeRep} = get(polycubes)[this.currentPolycubeId];
- const dims = get(somaDimension);
- const voxelSpace = new VoxelSpace(this.currentPolycubeId, [dims, dims, dims], cubeRep);
- this.mainScene.remove(...this.polycubeMeshes);
- voxelSpace.forEachCell((val, x, y, z) => {
- if (val) {
- const cube = this.newRoundedCube(0.2, 3, cubeColor);
- cube.position.set(
- -((dims - 1)/2) + z,
- ((dims - 1)/2) - y,
- -((dims - 1)/2) + x,
- );
- this.mainScene.add(cube);
- this.polycubeMeshes.push(cube);
- }
- });
- }
-
- private setupLight() {
- const color = 0xFFFFFF;
- const intensity = 1;
- const light = new THREE.DirectionalLight(color, intensity);
- light.position.set(-1, 2, 4);
- return light;
- }
-
- private render(time: number) {
- this.renderer.render(this.mainScene, this.camera);
- requestAnimationFrame((timestamp) => this.render(timestamp));
- }
-
- private newRoundedCube(radius: number, smoothness: number, color: string) {
- const width = 1;
- const height = 1;
- const depth = 1;
- const shape = new THREE.Shape();
- const eps = 0.00001;
- const radius0 = radius - eps;
- shape.absarc(eps, eps, eps, -Math.PI / 2, -Math.PI, true);
- shape.absarc(eps, height - radius0 * 2, eps, Math.PI, Math.PI / 2, true);
- shape.absarc(width - radius0 * 2, height - radius0 * 2, eps, Math.PI / 2, 0, true);
- shape.absarc(width - radius0 * 2, eps, eps, 0, -Math.PI / 2, true );
- const geometry = new THREE.ExtrudeBufferGeometry(shape, {
- depth: depth - radius0 * 2,
- bevelEnabled: true,
- bevelSegments: smoothness * 2,
- steps: 1,
- bevelSize: radius0,
- bevelThickness: radius0,
- curveSegments: smoothness
- });
- geometry.center();
- const material = new THREE.MeshPhongMaterial({color});
- const cube = new THREE.Mesh(geometry, material);
- return cube;
- }
-}
diff --git a/tsconfig.json b/tsconfig.json
index 29f6d75..24ad1b3 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,9 +1,9 @@
{
"extends": "@tsconfig/svelte/tsconfig.json",
"compilerOptions": {
- "lib": ["es2020", "dom"],
- "target": "ES2020"
+ "lib": ["dom", "ESNext"],
+ "target": "ESNext",
},
- "include": ["src/**/*"],
- "exclude": ["node_modules/*", "__sapper__/*", "public/*"]
+ "include": ["./src/**/*"],
+ "exclude": ["./node_modules/*", "./__sapper__/*", "./public/*"]
}
\ No newline at end of file