添加关照、全局等高线、修改图层问题

This commit is contained in:
2025-07-17 18:54:05 +08:00
parent c781d38c0c
commit b274b62671
4594 changed files with 791769 additions and 4921 deletions

2692
.idea/workspace.xml generated

File diff suppressed because it is too large Load Diff

View File

@ -15,3 +15,203 @@ x64:
nodeModuleFilePatterns:
- '**/*'
- dist/electron/**/*
nsis:
script: |-
!include "G:\YUANJIE\4.0\node_modules\app-builder-lib\templates\nsis\include\StdUtils.nsh"
!addincludedir "G:\YUANJIE\4.0\node_modules\app-builder-lib\templates\nsis\include"
!macro _isUpdated _a _b _t _f
${StdUtils.TestParameter} $R9 "updated"
StrCmp "$R9" "true" `${_t}` `${_f}`
!macroend
!define isUpdated `"" isUpdated ""`
!macro _isForceRun _a _b _t _f
${StdUtils.TestParameter} $R9 "force-run"
StrCmp "$R9" "true" `${_t}` `${_f}`
!macroend
!define isForceRun `"" isForceRun ""`
!macro _isKeepShortcuts _a _b _t _f
${StdUtils.TestParameter} $R9 "keep-shortcuts"
StrCmp "$R9" "true" `${_t}` `${_f}`
!macroend
!define isKeepShortcuts `"" isKeepShortcuts ""`
!macro _isNoDesktopShortcut _a _b _t _f
${StdUtils.TestParameter} $R9 "no-desktop-shortcut"
StrCmp "$R9" "true" `${_t}` `${_f}`
!macroend
!define isNoDesktopShortcut `"" isNoDesktopShortcut ""`
!macro _isDeleteAppData _a _b _t _f
${StdUtils.TestParameter} $R9 "delete-app-data"
StrCmp "$R9" "true" `${_t}` `${_f}`
!macroend
!define isDeleteAppData `"" isDeleteAppData ""`
!macro _isForAllUsers _a _b _t _f
${StdUtils.TestParameter} $R9 "allusers"
StrCmp "$R9" "true" `${_t}` `${_f}`
!macroend
!define isForAllUsers `"" isForAllUsers ""`
!macro _isForCurrentUser _a _b _t _f
${StdUtils.TestParameter} $R9 "currentuser"
StrCmp "$R9" "true" `${_t}` `${_f}`
!macroend
!define isForCurrentUser `"" isForCurrentUser ""`
!macro addLangs
!insertmacro MUI_LANGUAGE "English"
!insertmacro MUI_LANGUAGE "German"
!insertmacro MUI_LANGUAGE "French"
!insertmacro MUI_LANGUAGE "SpanishInternational"
!insertmacro MUI_LANGUAGE "SimpChinese"
!insertmacro MUI_LANGUAGE "TradChinese"
!insertmacro MUI_LANGUAGE "Japanese"
!insertmacro MUI_LANGUAGE "Korean"
!insertmacro MUI_LANGUAGE "Italian"
!insertmacro MUI_LANGUAGE "Dutch"
!insertmacro MUI_LANGUAGE "Danish"
!insertmacro MUI_LANGUAGE "Swedish"
!insertmacro MUI_LANGUAGE "Norwegian"
!insertmacro MUI_LANGUAGE "Finnish"
!insertmacro MUI_LANGUAGE "Russian"
!insertmacro MUI_LANGUAGE "Portuguese"
!insertmacro MUI_LANGUAGE "PortugueseBR"
!insertmacro MUI_LANGUAGE "Polish"
!insertmacro MUI_LANGUAGE "Ukrainian"
!insertmacro MUI_LANGUAGE "Czech"
!insertmacro MUI_LANGUAGE "Slovak"
!insertmacro MUI_LANGUAGE "Hungarian"
!insertmacro MUI_LANGUAGE "Arabic"
!insertmacro MUI_LANGUAGE "Turkish"
!insertmacro MUI_LANGUAGE "Thai"
!insertmacro MUI_LANGUAGE "Vietnamese"
!macroend
!addincludedir "G:\YUANJIE\4.0\build"
!include "G:\YUANJIE\4.0\installer.nsh"
!addplugindir /x86-unicode "C:\Users\YC\AppData\Local\electron-builder\Cache\nsis\nsis-resources-3.4.1\plugins\x86-unicode"
!include "C:\Users\YC\AppData\Local\Temp\t-aKLuVX\0-messages.nsh"
!include "C:\Users\YC\AppData\Local\Temp\t-aKLuVX\1-messages.nsh"
Var newStartMenuLink
Var oldStartMenuLink
Var newDesktopLink
Var oldDesktopLink
Var oldShortcutName
Var oldMenuDirectory
!include "common.nsh"
!include "MUI2.nsh"
!include "multiUser.nsh"
!include "allowOnlyOneInstallerInstance.nsh"
!ifdef INSTALL_MODE_PER_ALL_USERS
!ifdef BUILD_UNINSTALLER
RequestExecutionLevel user
!else
RequestExecutionLevel admin
!endif
!else
RequestExecutionLevel user
!endif
!ifdef BUILD_UNINSTALLER
SilentInstall silent
!else
Var appExe
Var launchLink
!endif
!ifdef ONE_CLICK
!include "oneClick.nsh"
!else
!include "assistedInstaller.nsh"
!endif
!insertmacro addLangs
!ifmacrodef customHeader
!insertmacro customHeader
!endif
Function .onInit
SetOutPath $INSTDIR
${LogSet} on
!ifmacrodef preInit
!insertmacro preInit
!endif
!ifdef DISPLAY_LANG_SELECTOR
!insertmacro MUI_LANGDLL_DISPLAY
!endif
!ifdef BUILD_UNINSTALLER
WriteUninstaller "${UNINSTALLER_OUT_FILE}"
!insertmacro quitSuccess
!else
!insertmacro check64BitAndSetRegView
!ifdef ONE_CLICK
!insertmacro ALLOW_ONLY_ONE_INSTALLER_INSTANCE
!else
${IfNot} ${UAC_IsInnerInstance}
!insertmacro ALLOW_ONLY_ONE_INSTALLER_INSTANCE
${EndIf}
!endif
!insertmacro initMultiUser
!ifmacrodef customInit
!insertmacro customInit
!endif
!ifmacrodef addLicenseFiles
InitPluginsDir
!insertmacro addLicenseFiles
!endif
!endif
FunctionEnd
!ifndef BUILD_UNINSTALLER
!include "installUtil.nsh"
!endif
Section "install"
!ifndef BUILD_UNINSTALLER
# If we're running a silent upgrade of a per-machine installation, elevate so extracting the new app will succeed.
# For a non-silent install, the elevation will be triggered when the install mode is selected in the UI,
# but that won't be executed when silent.
!ifndef INSTALL_MODE_PER_ALL_USERS
!ifndef ONE_CLICK
${if} $hasPerMachineInstallation == "1" # set in onInit by initMultiUser
${andIf} ${Silent}
${ifNot} ${UAC_IsAdmin}
ShowWindow $HWNDPARENT ${SW_HIDE}
!insertmacro UAC_RunElevated
${Switch} $0
${Case} 0
${Break}
${Case} 1223 ;user aborted
${Break}
${Default}
MessageBox mb_IconStop|mb_TopMost|mb_SetForeground "Unable to elevate, error $0"
${Break}
${EndSwitch}
Quit
${else}
!insertmacro setInstallModePerAllUsers
${endIf}
${endIf}
!endif
!endif
!include "installSection.nsh"
!endif
SectionEnd
!ifdef BUILD_UNINSTALLER
!include "uninstaller.nsh"
!endif

2
dist/electron/0.js vendored

File diff suppressed because one or more lines are too long

2
dist/electron/1.js vendored

File diff suppressed because one or more lines are too long

2
dist/electron/2.js vendored

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 20 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 27 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 44 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 18 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 766 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 18 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 24 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

File diff suppressed because one or more lines are too long

BIN
dist/electron/static/icon/flyLine.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 B

Binary file not shown.

View File

@ -926,8 +926,14 @@
get_current_frame: function() { return player.current_frame() },
load_url: function(src,callback){
if (!load_setup(callback)) return;
var h = new XMLHttpRequest();
h.onreadystatechange = function() {
if (h.readyState == 4) {
if (h.status == 404) {
callback(404)
}
}
};
// new browsers (XMLHttpRequest2-compliant)
h.open('GET', src, true);

Binary file not shown.

File diff suppressed because one or more lines are too long

16078
dist/electron/static/sdk/Cesium/index.cjs vendored Normal file

File diff suppressed because one or more lines are too long

16078
dist/electron/static/sdk/Cesium/index.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1515,6 +1515,7 @@
display: flex;
align-items: center;
height: 32px;
line-height: 32px;
}
.YJ-custom-base-dialog>.content .cy_datalist dl dd:hover {
@ -1599,6 +1600,10 @@
width: 23px;
height: 26px;
cursor: pointer;
border: 1px solid rgba(var(--color-sdk-base-rgb), 0.2);
display: flex;
align-items: center;
justify-content: center;
}
.YJ-custom-base-dialog.billboard-object>.content div .image {
@ -1780,6 +1785,89 @@
.YJ-custom-base-dialog.water-surface>.content>div .row .label {
flex: 0 0 60px;
}
/* 流光飞线 */
.YJ-custom-base-dialog.flow-line-surface>.content {
width: 586px;
}
.YJ-custom-base-dialog.flow-line-surface>.content>div .row .label {
flex: 0 0 60px;
}
/* 光照 */
.YJ-custom-base-dialog.sun-shine-surface>.content {
width: 586px;
}
.YJ-custom-base-dialog.sun-shine-surface>.content>div .row .label {
flex: 0 0 60px;
}
.YJ-custom-base-dialog.sun-shine-surface>.content>div .timeline-container {
width: 100%;
padding: 20px 0;
position: relative;
}
.YJ-custom-base-dialog.sun-shine-surface>.content>div .timeline {
height: 8px;
background: #f0f0f0;
border-radius: 15px;
position: relative;
cursor: pointer;
}
.YJ-custom-base-dialog.sun-shine-surface>.content>div .progress {
height: 100%;
width: 0;
background: rgba(var(--color-sdk-base-rgb), 1);
border-radius: 15px;
position: relative;
}
.YJ-custom-base-dialog.sun-shine-surface>.content>div .handle {
width: 16px;
height: 16px;
background: white;
/* border: 3px solid #4285f4; */
background: rgba(var(--color-sdk-base-rgb), 1);
border-radius: 50%;
position: absolute;
right: -8px;
top: 50%;
transform: translateY(-50%);
cursor: grab;
}
.YJ-custom-base-dialog.sun-shine-surface>.content>div .time-marks {
display: flex;
justify-content: space-between;
margin-top: 5px;
}
.YJ-custom-base-dialog.sun-shine-surface>.content>div .time-mark {
font-size: 12px;
color: #fff;
}
.YJ-custom-base-dialog.sun-shine-surface>.content>div .controls {
margin: 15px 0;
}
.YJ-custom-base-dialog.sun-shine-surface>.content>div .current-time {
font-size: 12px;
position: absolute;
width: 50px;
text-align: center;
right: -25px;
top: -200%;
transform: translateY(-50%);
}
.YJ-custom-base-dialog.sun-shine-surface>.content>div #timePause {
margin-top: 10px;
}
/* 电子围墙 */
.YJ-custom-base-dialog.wall-stereoscopic>.content {
@ -2511,7 +2599,9 @@
.YJ-custom-base-dialog.polyline>.content {
width: 580px;
}
.YJ-custom-base-dialog.polyline>.content>div #dashTextureDom {
display: none;
}
.YJ-custom-base-dialog.polyline>.content>div .row .col {
margin: 0 10px;
}
@ -2560,6 +2650,117 @@
max-height: 185px;
}
.YJ-custom-base-dialog.polyline>.content>div .input-select-unit-box textarea {
border-radius: unset!important;
}
.YJ-custom-base-dialog.polyline>.content>div .input-select-unit .datalist {
background-color:rgba(var(--color-sdk-base-rgb), 0.1)!important;
border-radius: 4px 0px 0px 4px !important;
}
.YJ-custom-base-dialog.polyline>.content>div .input-select-unit:nth-of-type(1) .datalist {
background-color:rgba(var(--color-sdk-base-rgb), 0.1)!important;
border-radius: 4px 0px, 0px, 4px!important;
}
.YJ-custom-base-dialog.polyline>.content>div .input-select-unit:nth-of-type(2) .datalist {
background-color:rgba(var(--color-sdk-base-rgb), 0.1)!important;
border-radius: 0px 4px 4px 0px!important;
}
.YJ-custom-base-dialog.polyline>.content>div .input-select-unit:nth-of-type(1) input {
border: 1px solid rgba(var(--color-sdk-base-rgb), 0.5)!important;
}
.YJ-custom-base-dialog.polyline>.content>div .input-select-unit:nth-of-type(2) input {
border: 1px solid rgba(var(--color-sdk-base-rgb), 0.5)!important;
}
.YJ-custom-base-dialog.polyline>.content input.input-text{
background-color: rgba(0, 0, 0, 0.5)!important;
border-radius: unset!important;
border-top: 1px solid rgba(var(--color-sdk-base-rgb), 0.5)!important;
border-bottom: 1px solid rgba(var(--color-sdk-base-rgb), 0.5) !important;
}
.YJ-custom-base-dialog.polyline>.content>div .input-select-line-type-box .cy_datalist input {
padding-left: 35px;
}
.YJ-custom-base-dialog.polyline>.content>div .input-select-line-type-box .icon-active {
position: absolute;
top: 11px;
left: 10px;
-webkit-pointer-events: none;
-moz-pointer-events: none;
-ms-pointer-events: none;
-o-pointer-events: none;
pointer-events: none;
}
.YJ-custom-base-dialog.polyline>.content>div .input-select-line-type-box .cy_datalist i.icon {
display: inline-block;
width: 22px;
height: 10px;
margin-right: 5px;
}
.YJ-custom-base-dialog.polyline>.content>div .input-select-line-type-box .cy_datalist i.line {
border: 1px solid rgba(255, 255, 255, 1);
height: 0px;
margin-top: 4px;
}
.YJ-custom-base-dialog.polyline>.content>div .input-select-line-type-box .cy_datalist i.dash-line {
border: 1px dashed rgba(255, 255, 255, 1);
height: 0px;
margin-top: 4px;
}
.YJ-custom-base-dialog.polyline>.content>div .input-select-line-type-box .cy_datalist i.light-line {
border: 1px solid rgba(255, 255, 255, 1);
height: 0px;
margin-top: 4px;
box-shadow: 0 0 3px #fff
}
.YJ-custom-base-dialog.polyline>.content>div .input-select-line-type-box .cy_datalist i.tail-line {
background: url(../../img/arrow/tail.png) 100% 100% no-repeat;
background-size: 100% 100%;
}
.YJ-custom-base-dialog.polyline>.content>div .input-select-line-type-box .cy_datalist i.mult-tail-line {
background: url(../../img/arrow/tail.png) 100% 100% no-repeat;
background-size: 100% 100%;
}
.YJ-custom-base-dialog.polyline>.content>div .input-select-line-type-box .cy_datalist i.flow-dash-line1 {
border: 1px dashed rgba(255, 255, 255, 1);
height: 0px;
margin-top: 4px;
}
.YJ-custom-base-dialog.polyline>.content>div .input-select-line-type-box .cy_datalist i.flow-dash-line2 {
border: 1px dashed rgba(255, 255, 255, 1);
height: 0px;
margin-top: 4px;
}
.YJ-custom-base-dialog.polyline>.content>div .input-select-line-type-box .cy_datalist i.pic-line1 {
background: url(../../img/arrow/1.png) 100% 100% no-repeat;
background-size: 100% 100%;
}
.YJ-custom-base-dialog.polyline>.content>div .input-select-line-type-box .cy_datalist i.pic-line2 {
background: url(../../img/arrow/2.png) 100% 100% no-repeat;
background-size: 100% 100%;
}
.YJ-custom-base-dialog.polyline>.content>div .input-select-line-type-box .cy_datalist i.pic-line3 {
background: url(../../img/arrow/3.png) 100% 100% no-repeat;
background-size: 100% 100%;
}
.YJ-custom-base-dialog.polyline>.content>div .input-select-line-type-box .cy_datalist i.pic-line4 {
background: url(../../img/arrow/4.png) 100% 100% no-repeat;
background-size: 100% 100%;
}
.YJ-custom-base-dialog.polyline>.content>div .input-select-line-type-box .cy_datalist i.pic-line5 {
background: url(../../img/arrow/5.png) 100% 100% no-repeat;
background-size: 100% 100%;
}
.YJ-custom-base-dialog.polyline>.content>div .input-select-line-type-box .cy_datalist i.pic-line6 {
background: url(../../img/arrow/6.png) 100% 100% no-repeat;
background-size: 100% 100%;
}
/* 贴地图片 */
.YJ-custom-base-dialog.ground-image>.content {
width: 500px;
@ -2978,9 +3179,9 @@
/* 等高线 */
.YJ-custom-base-dialog.contour>.content {
width: 350px;
width: 525px;
}
.YJ-custom-base-dialog.contour>.content .label {
flex: 0 0 56px;
}
flex: unset;
}

View File

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" >
<path d="M11.0843 0.555313L11.0843 0.555313C10.6784 0.149383 10.0203 0.149383 9.61434 0.555313L5.81934 4.35031L2.02434 0.555313C1.61841 0.149383 0.960265 0.149383 0.554336 0.555313L0.554336 0.555313C0.148407 0.961242 0.148407 1.61938 0.554336 2.02531L4.34934 5.82031L0.554336 9.61531C0.148407 10.0212 0.148407 10.6794 0.554336 11.0853L0.554336 11.0853C0.960265 11.4912 1.61841 11.4912 2.02434 11.0853L5.81934 7.29031L9.61434 11.0853C10.0203 11.4912 10.6784 11.4912 11.0843 11.0853L11.0843 11.0853C11.4903 10.6794 11.4903 10.0212 11.0843 9.61531L7.28934 5.82031L11.0843 2.02531C11.4903 1.61938 11.4903 0.961242 11.0843 0.555313Z">
</path>
<path fill="rgba(0, 0, 0, 0.5)" d="M10.9076 0.732089M10.9076 0.732089Q10.6763 0.500866 10.3493 0.500866Q10.0223 0.500866 9.79111 0.732089L5.81934 4.70387L1.84756 0.732089C1.53926 0.423791 1.03941 0.423791 0.731113 0.732089C0.422814 1.04039 0.422814 1.54024 0.731113 1.84854L4.70289 5.82031L0.731113 9.79209Q0.499889 10.0233 0.499889 10.3503Q0.499889 10.6773 0.731113 10.9085Q0.962336 11.1398 1.28934 11.1398Q1.61634 11.1398 1.84756 10.9085L5.81934 6.93676L9.79111 10.9085Q10.0223 11.1398 10.3493 11.1398Q10.6763 11.1398 10.9076 10.9085Q11.1388 10.6773 11.1388 10.3503Q11.1388 10.0233 10.9076 9.79209L6.93578 5.82031L10.9076 1.84854Q11.1388 1.61731 11.1388 1.29031Q11.1388 0.963312 10.9076 0.732089ZM11.2611 0.378536Q11.6388 0.756205 11.6388 1.29031Q11.6388 1.82442 11.2611 2.20209L7.46611 5.99709L7.28934 5.82031L7.46611 5.64354L11.2611 9.43854Q11.6388 9.8162 11.6388 10.3503Q11.6388 10.8844 11.2611 11.2621Q10.8834 11.6398 10.3493 11.6398Q9.81523 11.6398 9.43756 11.2621L5.64256 7.46709L5.81934 7.29031L5.99611 7.46709L2.20111 11.2621Q1.82344 11.6398 1.28934 11.6398Q0.755229 11.6398 0.377559 11.2621Q-0.000110999 10.8844 -0.000111014 10.3503Q-0.000110984 9.81621 0.377559 9.43854L4.17256 5.64354L4.34934 5.82031L4.17256 5.99709L0.377559 2.20209C-0.126001 1.69853 -0.126001 0.882096 0.377559 0.378536C0.88112 -0.125024 1.69755 -0.125025 2.20111 0.378536L5.99611 4.17354L5.81934 4.35031L5.64256 4.17354L9.43756 0.378536Q9.81523 0.000865549 10.3493 0.000865549Q10.8834 0.000865579 11.2611 0.378536L11.2611 0.378536Z">
</path>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 16 16" >
<path d="M15.5219 2.5188L13.4796 0.476487C13.1732 0.170059 12.7657 0 12.3309 0C11.8977 0 11.4886 0.168455 11.1822 0.476487L1.60273 10.0864L1.57866 10.1105L0 16L5.92801 14.4117L15.5219 4.81781C15.8299 4.50978 16 4.10228 16 3.66911C15.9984 3.23433 15.8299 2.82683 15.5219 2.5188ZM2.79154 14.2818L1.71824 13.2085L2.24125 11.2576L4.74401 13.7604L2.79154 14.2818ZM5.66169 13.3529L2.65998 10.3511L9.72546 3.2584L12.7416 6.27294L5.66169 13.3529ZM14.8593 4.15361L13.9208 5.09215L10.9046 2.07761L11.8448 1.13747C11.9667 1.01394 12.1448 0.943347 12.3293 0.943347C12.5154 0.943347 12.6935 1.01394 12.8154 1.13747L14.8577 3.18139C14.9877 3.31134 15.0599 3.48461 15.0599 3.6675C15.0615 3.8504 14.9909 4.02366 14.8593 4.15361Z" >
</path>
</svg>

After

Width:  |  Height:  |  Size: 837 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
dist/electron/static/sdk/img/arrow/1.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

BIN
dist/electron/static/sdk/img/arrow/2.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
dist/electron/static/sdk/img/arrow/3.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
dist/electron/static/sdk/img/arrow/4.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
dist/electron/static/sdk/img/arrow/5.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

BIN
dist/electron/static/sdk/img/arrow/6.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 511 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
dist/electron/static/sdk/img/bubble.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 883 B

BIN
dist/electron/static/sdk/img/bubble.zip vendored Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 B

BIN
dist/electron/static/sdk/img/cluster.zip vendored Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,295 @@
export * from './animation/AnimationClipCreator.js';
export * from './animation/CCDIKSolver.js';
export * from './animation/MMDAnimationHelper.js';
export * from './animation/MMDPhysics.js';
export * from './cameras/CinematicCamera.js';
export { default as WebGL } from './capabilities/WebGL.js';
export * from './controls/ArcballControls.js';
export * from './controls/DragControls.js';
export * from './controls/FirstPersonControls.js';
export * from './controls/FlyControls.js';
export * from './controls/MapControls.js';
export * from './controls/OrbitControls.js';
export * from './controls/PointerLockControls.js';
export * from './controls/TrackballControls.js';
export * from './controls/TransformControls.js';
export * from './csm/CSM.js';
export * from './csm/CSMFrustum.js';
export * from './csm/CSMHelper.js';
export * from './csm/CSMShader.js';
export * as Curves from './curves/CurveExtras.js';
export * from './curves/NURBSCurve.js';
export * from './curves/NURBSSurface.js';
export * from './curves/NURBSVolume.js';
export * as NURBSUtils from './curves/NURBSUtils.js';
export * from './effects/AnaglyphEffect.js';
export * from './effects/AsciiEffect.js';
export * from './effects/OutlineEffect.js';
export * from './effects/ParallaxBarrierEffect.js';
export * from './effects/PeppersGhostEffect.js';
export * from './effects/StereoEffect.js';
export * from './environments/DebugEnvironment.js';
export * from './environments/RoomEnvironment.js';
export * from './exporters/DRACOExporter.js';
export * from './exporters/EXRExporter.js';
export * from './exporters/GLTFExporter.js';
export * from './exporters/KTX2Exporter.js';
export * from './exporters/MMDExporter.js';
export * from './exporters/OBJExporter.js';
export * from './exporters/PLYExporter.js';
export * from './exporters/STLExporter.js';
export * from './exporters/USDZExporter.js';
export * from './geometries/BoxLineGeometry.js';
export * from './geometries/ConvexGeometry.js';
export * from './geometries/DecalGeometry.js';
export * from './geometries/ParametricGeometries.js';
export * from './geometries/ParametricGeometry.js';
export * from './geometries/RoundedBoxGeometry.js';
export * from './geometries/TeapotGeometry.js';
export * from './geometries/TextGeometry.js';
export * from './helpers/LightProbeHelper.js';
export * from './helpers/OctreeHelper.js';
export * from './helpers/PositionalAudioHelper.js';
export * from './helpers/RectAreaLightHelper.js';
export * from './helpers/TextureHelper.js';
export * from './helpers/VertexNormalsHelper.js';
export * from './helpers/VertexTangentsHelper.js';
export * from './helpers/ViewHelper.js';
export * from './interactive/HTMLMesh.js';
export * from './interactive/InteractiveGroup.js';
export * from './interactive/SelectionBox.js';
export * from './interactive/SelectionHelper.js';
export { default as IESSpotLight } from './lights/IESSpotLight.js';
export * from './lights/LightProbeGenerator.js';
export * from './lights/RectAreaLightUniformsLib.js';
export * from './lines/Line2.js';
export * from './lines/LineGeometry.js';
export * from './lines/LineMaterial.js';
export * from './lines/LineSegments2.js';
export * from './lines/LineSegmentsGeometry.js';
export * from './lines/Wireframe.js';
export * from './lines/WireframeGeometry2.js';
export * from './loaders/3DMLoader.js';
export * from './loaders/3MFLoader.js';
export * from './loaders/AMFLoader.js';
export * from './loaders/BVHLoader.js';
export * from './loaders/ColladaLoader.js';
export * from './loaders/DDSLoader.js';
export * from './loaders/DRACOLoader.js';
export * from './loaders/EXRLoader.js';
export * from './loaders/FBXLoader.js';
export * from './loaders/FontLoader.js';
export * from './loaders/GCodeLoader.js';
export * from './loaders/GLTFLoader.js';
export * from './loaders/HDRCubeTextureLoader.js';
export * from './loaders/IESLoader.js';
export * from './loaders/KMZLoader.js';
export * from './loaders/KTX2Loader.js';
export * from './loaders/KTXLoader.js';
export * from './loaders/LDrawLoader.js';
export * from './loaders/LUT3dlLoader.js';
export * from './loaders/LUTCubeLoader.js';
export * from './loaders/LWOLoader.js';
export * from './loaders/LogLuvLoader.js';
export * from './loaders/LottieLoader.js';
export * from './loaders/MD2Loader.js';
export * from './loaders/MDDLoader.js';
export * from './loaders/MMDLoader.js';
export * from './loaders/MTLLoader.js';
export * from './loaders/NRRDLoader.js';
export * from './loaders/OBJLoader.js';
export * from './loaders/PCDLoader.js';
export * from './loaders/PDBLoader.js';
export * from './loaders/PLYLoader.js';
export * from './loaders/PVRLoader.js';
export * from './loaders/RGBELoader.js';
export * from './loaders/RGBMLoader.js';
export * from './loaders/STLLoader.js';
export * from './loaders/SVGLoader.js';
export * from './loaders/TDSLoader.js';
export * from './loaders/TGALoader.js';
export * from './loaders/TIFFLoader.js';
export * from './loaders/TTFLoader.js';
export * from './loaders/TiltLoader.js';
export * from './loaders/USDZLoader.js';
export * from './loaders/VOXLoader.js';
export * from './loaders/VRMLLoader.js';
export * from './loaders/VTKLoader.js';
export * from './loaders/XYZLoader.js';
export * from './materials/MeshGouraudMaterial.js';
export * from './math/Capsule.js';
export * from './math/ColorConverter.js';
export * from './math/ConvexHull.js';
export * from './math/ImprovedNoise.js';
export * from './math/Lut.js';
export * from './math/MeshSurfaceSampler.js';
export * from './math/OBB.js';
export * from './math/Octree.js';
export * from './math/SimplexNoise.js';
export * from './misc/ConvexObjectBreaker.js';
export * from './misc/GPUComputationRenderer.js';
export * from './misc/Gyroscope.js';
export * from './misc/MD2Character.js';
export * from './misc/MD2CharacterComplex.js';
export * from './misc/MorphAnimMesh.js';
export * from './misc/MorphBlendMesh.js';
export * from './misc/ProgressiveLightMap.js';
export * from './misc/RollerCoaster.js';
export * from './misc/Timer.js';
export * from './misc/TubePainter.js';
export * from './misc/Volume.js';
export * from './misc/VolumeSlice.js';
export * from './modifiers/CurveModifier.js';
export * from './modifiers/EdgeSplitModifier.js';
export * from './modifiers/SimplifyModifier.js';
export * from './modifiers/TessellateModifier.js';
export * from './objects/GroundedSkybox.js';
export * from './objects/Lensflare.js';
export * from './objects/MarchingCubes.js';
export * from './objects/Reflector.js';
export * from './objects/ReflectorForSSRPass.js';
export * from './objects/Refractor.js';
export * from './objects/ShadowMesh.js';
export * from './objects/Sky.js';
export * from './objects/Water.js';
export { Water as Water2 } from './objects/Water2.js';
export * from './physics/AmmoPhysics.js';
export * from './physics/RapierPhysics.js';
export * from './postprocessing/AfterimagePass.js';
export * from './postprocessing/BloomPass.js';
export * from './postprocessing/BokehPass.js';
export * from './postprocessing/ClearPass.js';
export * from './postprocessing/CubeTexturePass.js';
export * from './postprocessing/DotScreenPass.js';
export * from './postprocessing/EffectComposer.js';
export * from './postprocessing/FilmPass.js';
export * from './postprocessing/GlitchPass.js';
export * from './postprocessing/GTAOPass.js';
export * from './postprocessing/HalftonePass.js';
export * from './postprocessing/LUTPass.js';
export * from './postprocessing/MaskPass.js';
export * from './postprocessing/OutlinePass.js';
export * from './postprocessing/OutputPass.js';
export * from './postprocessing/Pass.js';
export * from './postprocessing/RenderPass.js';
export * from './postprocessing/RenderPixelatedPass.js';
export * from './postprocessing/SAOPass.js';
export * from './postprocessing/SMAAPass.js';
export * from './postprocessing/SSAARenderPass.js';
export * from './postprocessing/SSAOPass.js';
export * from './postprocessing/SSRPass.js';
export * from './postprocessing/SavePass.js';
export * from './postprocessing/ShaderPass.js';
export * from './postprocessing/TAARenderPass.js';
export * from './postprocessing/TexturePass.js';
export * from './postprocessing/UnrealBloomPass.js';
export * from './renderers/CSS2DRenderer.js';
export * from './renderers/CSS3DRenderer.js';
export * from './renderers/Projector.js';
export * from './renderers/SVGRenderer.js';
export * from './shaders/ACESFilmicToneMappingShader.js';
export * from './shaders/AfterimageShader.js';
export * from './shaders/BasicShader.js';
export * from './shaders/BleachBypassShader.js';
export * from './shaders/BlendShader.js';
export * from './shaders/BokehShader.js';
export { BokehShader as BokehShader2 } from './shaders/BokehShader2.js';
export * from './shaders/BrightnessContrastShader.js';
export * from './shaders/ColorCorrectionShader.js';
export * from './shaders/ColorifyShader.js';
export * from './shaders/ConvolutionShader.js';
export * from './shaders/CopyShader.js';
export * from './shaders/DOFMipMapShader.js';
export * from './shaders/DepthLimitedBlurShader.js';
export * from './shaders/DigitalGlitch.js';
export * from './shaders/DotScreenShader.js';
export * from './shaders/ExposureShader.js';
export * from './shaders/FXAAShader.js';
export * from './shaders/FilmShader.js';
export * from './shaders/FocusShader.js';
export * from './shaders/FreiChenShader.js';
export * from './shaders/GammaCorrectionShader.js';
export * from './shaders/GodRaysShader.js';
export * from './shaders/GTAOShader.js';
export * from './shaders/HalftoneShader.js';
export * from './shaders/HorizontalBlurShader.js';
export * from './shaders/HorizontalTiltShiftShader.js';
export * from './shaders/HueSaturationShader.js';
export * from './shaders/KaleidoShader.js';
export * from './shaders/LuminosityHighPassShader.js';
export * from './shaders/LuminosityShader.js';
export * from './shaders/MMDToonShader.js';
export * from './shaders/MirrorShader.js';
export * from './shaders/NormalMapShader.js';
export * from './shaders/OutputShader.js';
export * from './shaders/RGBShiftShader.js';
export * from './shaders/SAOShader.js';
export * from './shaders/SMAAShader.js';
export * from './shaders/SSAOShader.js';
export * from './shaders/SSRShader.js';
export * from './shaders/SepiaShader.js';
export * from './shaders/SobelOperatorShader.js';
export * from './shaders/SubsurfaceScatteringShader.js';
export * from './shaders/TechnicolorShader.js';
export * from './shaders/ToonShader.js';
export * from './shaders/TriangleBlurShader.js';
export * from './shaders/UnpackDepthRGBAShader.js';
export * from './shaders/VelocityShader.js';
export * from './shaders/VerticalBlurShader.js';
export * from './shaders/VerticalTiltShiftShader.js';
export * from './shaders/VignetteShader.js';
export * from './shaders/VolumeShader.js';
export * from './shaders/WaterRefractionShader.js';
export * from './textures/FlakesTexture.js';
export * as BufferGeometryUtils from './utils/BufferGeometryUtils.js';
export * as CameraUtils from './utils/CameraUtils.js';
export * from './utils/GPUStatsPanel.js';
export * as GeometryCompressionUtils from './utils/GeometryCompressionUtils.js';
export * as GeometryUtils from './utils/GeometryUtils.js';
export * from './utils/LDrawUtils.js';
export * from './utils/PackedPhongMaterial.js';
export * as SceneUtils from './utils/SceneUtils.js';
export * from './utils/ShadowMapViewer.js';
export * as SkeletonUtils from './utils/SkeletonUtils.js';
export * as SortUtils from './utils/SortUtils.js';
export * from './utils/TextureUtils.js';
export * from './utils/UVsDebug.js';
export * from './utils/WorkerPool.js';
export * from './webxr/ARButton.js';
export * from './webxr/OculusHandModel.js';
export * from './webxr/OculusHandPointerModel.js';
export * from './webxr/Text2D.js';
export * from './webxr/VRButton.js';
export * from './webxr/XRButton.js';
export * from './webxr/XRControllerModelFactory.js';
export * from './webxr/XREstimatedLight.js';
export * from './webxr/XRHandMeshModel.js';
export * from './webxr/XRHandModelFactory.js';
export * from './webxr/XRHandPrimitiveModel.js';
export * from './webxr/XRPlanes.js';

View File

@ -0,0 +1,116 @@
import {
AnimationClip,
BooleanKeyframeTrack,
ColorKeyframeTrack,
NumberKeyframeTrack,
Vector3,
VectorKeyframeTrack
} from 'three';
class AnimationClipCreator {
static CreateRotationAnimation( period, axis = 'x' ) {
const times = [ 0, period ], values = [ 0, 360 ];
const trackName = '.rotation[' + axis + ']';
const track = new NumberKeyframeTrack( trackName, times, values );
return new AnimationClip( null, period, [ track ] );
}
static CreateScaleAxisAnimation( period, axis = 'x' ) {
const times = [ 0, period ], values = [ 0, 1 ];
const trackName = '.scale[' + axis + ']';
const track = new NumberKeyframeTrack( trackName, times, values );
return new AnimationClip( null, period, [ track ] );
}
static CreateShakeAnimation( duration, shakeScale ) {
const times = [], values = [], tmp = new Vector3();
for ( let i = 0; i < duration * 10; i ++ ) {
times.push( i / 10 );
tmp.set( Math.random() * 2.0 - 1.0, Math.random() * 2.0 - 1.0, Math.random() * 2.0 - 1.0 ).
multiply( shakeScale ).
toArray( values, values.length );
}
const trackName = '.position';
const track = new VectorKeyframeTrack( trackName, times, values );
return new AnimationClip( null, duration, [ track ] );
}
static CreatePulsationAnimation( duration, pulseScale ) {
const times = [], values = [], tmp = new Vector3();
for ( let i = 0; i < duration * 10; i ++ ) {
times.push( i / 10 );
const scaleFactor = Math.random() * pulseScale;
tmp.set( scaleFactor, scaleFactor, scaleFactor ).
toArray( values, values.length );
}
const trackName = '.scale';
const track = new VectorKeyframeTrack( trackName, times, values );
return new AnimationClip( null, duration, [ track ] );
}
static CreateVisibilityAnimation( duration ) {
const times = [ 0, duration / 2, duration ], values = [ true, false, true ];
const trackName = '.visible';
const track = new BooleanKeyframeTrack( trackName, times, values );
return new AnimationClip( null, duration, [ track ] );
}
static CreateMaterialColorAnimation( duration, colors ) {
const times = [], values = [],
timeStep = duration / colors.length;
for ( let i = 0; i < colors.length; i ++ ) {
times.push( i * timeStep );
const color = colors[ i ];
values.push( color.r, color.g, color.b );
}
const trackName = '.material.color';
const track = new ColorKeyframeTrack( trackName, times, values );
return new AnimationClip( null, duration, [ track ] );
}
}
export { AnimationClipCreator };

View File

@ -0,0 +1,484 @@
import {
BufferAttribute,
BufferGeometry,
Color,
Line,
LineBasicMaterial,
Matrix4,
Mesh,
MeshBasicMaterial,
Object3D,
Quaternion,
SphereGeometry,
Vector3
} from 'three';
const _q = new Quaternion();
const _targetPos = new Vector3();
const _targetVec = new Vector3();
const _effectorPos = new Vector3();
const _effectorVec = new Vector3();
const _linkPos = new Vector3();
const _invLinkQ = new Quaternion();
const _linkScale = new Vector3();
const _axis = new Vector3();
const _vector = new Vector3();
const _matrix = new Matrix4();
/**
* CCD Algorithm
* - https://sites.google.com/site/auraliusproject/ccd-algorithm
*
* // ik parameter example
* //
* // target, effector, index in links are bone index in skeleton.bones.
* // the bones relation should be
* // <-- parent child -->
* // links[ n ], links[ n - 1 ], ..., links[ 0 ], effector
* iks = [ {
* target: 1,
* effector: 2,
* links: [ { index: 5, limitation: new Vector3( 1, 0, 0 ) }, { index: 4, enabled: false }, { index : 3 } ],
* iteration: 10,
* minAngle: 0.0,
* maxAngle: 1.0,
* } ];
*/
class CCDIKSolver {
/**
* @param {THREE.SkinnedMesh} mesh
* @param {Array<Object>} iks
*/
constructor( mesh, iks = [] ) {
this.mesh = mesh;
this.iks = iks;
this._valid();
}
/**
* Update all IK bones.
*
* @return {CCDIKSolver}
*/
update() {
const iks = this.iks;
for ( let i = 0, il = iks.length; i < il; i ++ ) {
this.updateOne( iks[ i ] );
}
return this;
}
/**
* Update one IK bone
*
* @param {Object} ik parameter
* @return {CCDIKSolver}
*/
updateOne( ik ) {
const bones = this.mesh.skeleton.bones;
// for reference overhead reduction in loop
const math = Math;
const effector = bones[ ik.effector ];
const target = bones[ ik.target ];
// don't use getWorldPosition() here for the performance
// because it calls updateMatrixWorld( true ) inside.
_targetPos.setFromMatrixPosition( target.matrixWorld );
const links = ik.links;
const iteration = ik.iteration !== undefined ? ik.iteration : 1;
for ( let i = 0; i < iteration; i ++ ) {
let rotated = false;
for ( let j = 0, jl = links.length; j < jl; j ++ ) {
const link = bones[ links[ j ].index ];
// skip this link and following links.
// this skip is used for MMD performance optimization.
if ( links[ j ].enabled === false ) break;
const limitation = links[ j ].limitation;
const rotationMin = links[ j ].rotationMin;
const rotationMax = links[ j ].rotationMax;
// don't use getWorldPosition/Quaternion() here for the performance
// because they call updateMatrixWorld( true ) inside.
link.matrixWorld.decompose( _linkPos, _invLinkQ, _linkScale );
_invLinkQ.invert();
_effectorPos.setFromMatrixPosition( effector.matrixWorld );
// work in link world
_effectorVec.subVectors( _effectorPos, _linkPos );
_effectorVec.applyQuaternion( _invLinkQ );
_effectorVec.normalize();
_targetVec.subVectors( _targetPos, _linkPos );
_targetVec.applyQuaternion( _invLinkQ );
_targetVec.normalize();
let angle = _targetVec.dot( _effectorVec );
if ( angle > 1.0 ) {
angle = 1.0;
} else if ( angle < - 1.0 ) {
angle = - 1.0;
}
angle = math.acos( angle );
// skip if changing angle is too small to prevent vibration of bone
if ( angle < 1e-5 ) continue;
if ( ik.minAngle !== undefined && angle < ik.minAngle ) {
angle = ik.minAngle;
}
if ( ik.maxAngle !== undefined && angle > ik.maxAngle ) {
angle = ik.maxAngle;
}
_axis.crossVectors( _effectorVec, _targetVec );
_axis.normalize();
_q.setFromAxisAngle( _axis, angle );
link.quaternion.multiply( _q );
// TODO: re-consider the limitation specification
if ( limitation !== undefined ) {
let c = link.quaternion.w;
if ( c > 1.0 ) c = 1.0;
const c2 = math.sqrt( 1 - c * c );
link.quaternion.set( limitation.x * c2,
limitation.y * c2,
limitation.z * c2,
c );
}
if ( rotationMin !== undefined ) {
link.rotation.setFromVector3( _vector.setFromEuler( link.rotation ).max( rotationMin ) );
}
if ( rotationMax !== undefined ) {
link.rotation.setFromVector3( _vector.setFromEuler( link.rotation ).min( rotationMax ) );
}
link.updateMatrixWorld( true );
rotated = true;
}
if ( ! rotated ) break;
}
return this;
}
/**
* Creates Helper
*
* @param {number} sphereSize
* @return {CCDIKHelper}
*/
createHelper( sphereSize ) {
return new CCDIKHelper( this.mesh, this.iks, sphereSize );
}
// private methods
_valid() {
const iks = this.iks;
const bones = this.mesh.skeleton.bones;
for ( let i = 0, il = iks.length; i < il; i ++ ) {
const ik = iks[ i ];
const effector = bones[ ik.effector ];
const links = ik.links;
let link0, link1;
link0 = effector;
for ( let j = 0, jl = links.length; j < jl; j ++ ) {
link1 = bones[ links[ j ].index ];
if ( link0.parent !== link1 ) {
console.warn( 'THREE.CCDIKSolver: bone ' + link0.name + ' is not the child of bone ' + link1.name );
}
link0 = link1;
}
}
}
}
function getPosition( bone, matrixWorldInv ) {
return _vector
.setFromMatrixPosition( bone.matrixWorld )
.applyMatrix4( matrixWorldInv );
}
function setPositionOfBoneToAttributeArray( array, index, bone, matrixWorldInv ) {
const v = getPosition( bone, matrixWorldInv );
array[ index * 3 + 0 ] = v.x;
array[ index * 3 + 1 ] = v.y;
array[ index * 3 + 2 ] = v.z;
}
/**
* Visualize IK bones
*
* @param {SkinnedMesh} mesh
* @param {Array<Object>} iks
* @param {number} sphereSize
*/
class CCDIKHelper extends Object3D {
constructor( mesh, iks = [], sphereSize = 0.25 ) {
super();
this.root = mesh;
this.iks = iks;
this.matrix.copy( mesh.matrixWorld );
this.matrixAutoUpdate = false;
this.sphereGeometry = new SphereGeometry( sphereSize, 16, 8 );
this.targetSphereMaterial = new MeshBasicMaterial( {
color: new Color( 0xff8888 ),
depthTest: false,
depthWrite: false,
transparent: true
} );
this.effectorSphereMaterial = new MeshBasicMaterial( {
color: new Color( 0x88ff88 ),
depthTest: false,
depthWrite: false,
transparent: true
} );
this.linkSphereMaterial = new MeshBasicMaterial( {
color: new Color( 0x8888ff ),
depthTest: false,
depthWrite: false,
transparent: true
} );
this.lineMaterial = new LineBasicMaterial( {
color: new Color( 0xff0000 ),
depthTest: false,
depthWrite: false,
transparent: true
} );
this._init();
}
/**
* Updates IK bones visualization.
*/
updateMatrixWorld( force ) {
const mesh = this.root;
if ( this.visible ) {
let offset = 0;
const iks = this.iks;
const bones = mesh.skeleton.bones;
_matrix.copy( mesh.matrixWorld ).invert();
for ( let i = 0, il = iks.length; i < il; i ++ ) {
const ik = iks[ i ];
const targetBone = bones[ ik.target ];
const effectorBone = bones[ ik.effector ];
const targetMesh = this.children[ offset ++ ];
const effectorMesh = this.children[ offset ++ ];
targetMesh.position.copy( getPosition( targetBone, _matrix ) );
effectorMesh.position.copy( getPosition( effectorBone, _matrix ) );
for ( let j = 0, jl = ik.links.length; j < jl; j ++ ) {
const link = ik.links[ j ];
const linkBone = bones[ link.index ];
const linkMesh = this.children[ offset ++ ];
linkMesh.position.copy( getPosition( linkBone, _matrix ) );
}
const line = this.children[ offset ++ ];
const array = line.geometry.attributes.position.array;
setPositionOfBoneToAttributeArray( array, 0, targetBone, _matrix );
setPositionOfBoneToAttributeArray( array, 1, effectorBone, _matrix );
for ( let j = 0, jl = ik.links.length; j < jl; j ++ ) {
const link = ik.links[ j ];
const linkBone = bones[ link.index ];
setPositionOfBoneToAttributeArray( array, j + 2, linkBone, _matrix );
}
line.geometry.attributes.position.needsUpdate = true;
}
}
this.matrix.copy( mesh.matrixWorld );
super.updateMatrixWorld( force );
}
/**
* Frees the GPU-related resources allocated by this instance. Call this method whenever this instance is no longer used in your app.
*/
dispose() {
this.sphereGeometry.dispose();
this.targetSphereMaterial.dispose();
this.effectorSphereMaterial.dispose();
this.linkSphereMaterial.dispose();
this.lineMaterial.dispose();
const children = this.children;
for ( let i = 0; i < children.length; i ++ ) {
const child = children[ i ];
if ( child.isLine ) child.geometry.dispose();
}
}
// private method
_init() {
const scope = this;
const iks = this.iks;
function createLineGeometry( ik ) {
const geometry = new BufferGeometry();
const vertices = new Float32Array( ( 2 + ik.links.length ) * 3 );
geometry.setAttribute( 'position', new BufferAttribute( vertices, 3 ) );
return geometry;
}
function createTargetMesh() {
return new Mesh( scope.sphereGeometry, scope.targetSphereMaterial );
}
function createEffectorMesh() {
return new Mesh( scope.sphereGeometry, scope.effectorSphereMaterial );
}
function createLinkMesh() {
return new Mesh( scope.sphereGeometry, scope.linkSphereMaterial );
}
function createLine( ik ) {
return new Line( createLineGeometry( ik ), scope.lineMaterial );
}
for ( let i = 0, il = iks.length; i < il; i ++ ) {
const ik = iks[ i ];
this.add( createTargetMesh() );
this.add( createEffectorMesh() );
for ( let j = 0, jl = ik.links.length; j < jl; j ++ ) {
this.add( createLinkMesh() );
}
this.add( createLine( ik ) );
}
}
}
export { CCDIKSolver, CCDIKHelper };

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,208 @@
import {
Mesh,
OrthographicCamera,
PerspectiveCamera,
PlaneGeometry,
Scene,
ShaderMaterial,
UniformsUtils,
WebGLRenderTarget
} from 'three';
import { BokehShader, BokehDepthShader } from '../shaders/BokehShader2.js';
class CinematicCamera extends PerspectiveCamera {
constructor( fov, aspect, near, far ) {
super( fov, aspect, near, far );
this.type = 'CinematicCamera';
this.postprocessing = { enabled: true };
this.shaderSettings = {
rings: 3,
samples: 4
};
const depthShader = BokehDepthShader;
this.materialDepth = new ShaderMaterial( {
uniforms: depthShader.uniforms,
vertexShader: depthShader.vertexShader,
fragmentShader: depthShader.fragmentShader
} );
this.materialDepth.uniforms[ 'mNear' ].value = near;
this.materialDepth.uniforms[ 'mFar' ].value = far;
// In case of cinematicCamera, having a default lens set is important
this.setLens();
this.initPostProcessing();
}
// providing fnumber and coc(Circle of Confusion) as extra arguments
// In case of cinematicCamera, having a default lens set is important
// if fnumber and coc are not provided, cinematicCamera tries to act as a basic PerspectiveCamera
setLens( focalLength = 35, filmGauge = 35, fNumber = 8, coc = 0.019 ) {
this.filmGauge = filmGauge;
this.setFocalLength( focalLength );
this.fNumber = fNumber;
this.coc = coc;
// fNumber is focalLength by aperture
this.aperture = focalLength / this.fNumber;
// hyperFocal is required to calculate depthOfField when a lens tries to focus at a distance with given fNumber and focalLength
this.hyperFocal = ( focalLength * focalLength ) / ( this.aperture * this.coc );
}
linearize( depth ) {
const zfar = this.far;
const znear = this.near;
return - zfar * znear / ( depth * ( zfar - znear ) - zfar );
}
smoothstep( near, far, depth ) {
const x = this.saturate( ( depth - near ) / ( far - near ) );
return x * x * ( 3 - 2 * x );
}
saturate( x ) {
return Math.max( 0, Math.min( 1, x ) );
}
// function for focusing at a distance from the camera
focusAt( focusDistance = 20 ) {
const focalLength = this.getFocalLength();
// distance from the camera (normal to frustrum) to focus on
this.focus = focusDistance;
// the nearest point from the camera which is in focus (unused)
this.nearPoint = ( this.hyperFocal * this.focus ) / ( this.hyperFocal + ( this.focus - focalLength ) );
// the farthest point from the camera which is in focus (unused)
this.farPoint = ( this.hyperFocal * this.focus ) / ( this.hyperFocal - ( this.focus - focalLength ) );
// the gap or width of the space in which is everything is in focus (unused)
this.depthOfField = this.farPoint - this.nearPoint;
// Considering minimum distance of focus for a standard lens (unused)
if ( this.depthOfField < 0 ) this.depthOfField = 0;
this.sdistance = this.smoothstep( this.near, this.far, this.focus );
this.ldistance = this.linearize( 1 - this.sdistance );
this.postprocessing.bokeh_uniforms[ 'focalDepth' ].value = this.ldistance;
}
initPostProcessing() {
if ( this.postprocessing.enabled ) {
this.postprocessing.scene = new Scene();
this.postprocessing.camera = new OrthographicCamera( window.innerWidth / - 2, window.innerWidth / 2, window.innerHeight / 2, window.innerHeight / - 2, - 10000, 10000 );
this.postprocessing.scene.add( this.postprocessing.camera );
this.postprocessing.rtTextureDepth = new WebGLRenderTarget( window.innerWidth, window.innerHeight );
this.postprocessing.rtTextureColor = new WebGLRenderTarget( window.innerWidth, window.innerHeight );
const bokeh_shader = BokehShader;
this.postprocessing.bokeh_uniforms = UniformsUtils.clone( bokeh_shader.uniforms );
this.postprocessing.bokeh_uniforms[ 'tColor' ].value = this.postprocessing.rtTextureColor.texture;
this.postprocessing.bokeh_uniforms[ 'tDepth' ].value = this.postprocessing.rtTextureDepth.texture;
this.postprocessing.bokeh_uniforms[ 'manualdof' ].value = 0;
this.postprocessing.bokeh_uniforms[ 'shaderFocus' ].value = 0;
this.postprocessing.bokeh_uniforms[ 'fstop' ].value = 2.8;
this.postprocessing.bokeh_uniforms[ 'showFocus' ].value = 1;
this.postprocessing.bokeh_uniforms[ 'focalDepth' ].value = 0.1;
//console.log( this.postprocessing.bokeh_uniforms[ "focalDepth" ].value );
this.postprocessing.bokeh_uniforms[ 'znear' ].value = this.near;
this.postprocessing.bokeh_uniforms[ 'zfar' ].value = this.near;
this.postprocessing.bokeh_uniforms[ 'textureWidth' ].value = window.innerWidth;
this.postprocessing.bokeh_uniforms[ 'textureHeight' ].value = window.innerHeight;
this.postprocessing.materialBokeh = new ShaderMaterial( {
uniforms: this.postprocessing.bokeh_uniforms,
vertexShader: bokeh_shader.vertexShader,
fragmentShader: bokeh_shader.fragmentShader,
defines: {
RINGS: this.shaderSettings.rings,
SAMPLES: this.shaderSettings.samples,
DEPTH_PACKING: 1
}
} );
this.postprocessing.quad = new Mesh( new PlaneGeometry( window.innerWidth, window.innerHeight ), this.postprocessing.materialBokeh );
this.postprocessing.quad.position.z = - 500;
this.postprocessing.scene.add( this.postprocessing.quad );
}
}
renderCinematic( scene, renderer ) {
if ( this.postprocessing.enabled ) {
const currentRenderTarget = renderer.getRenderTarget();
renderer.clear();
// Render scene into texture
scene.overrideMaterial = null;
renderer.setRenderTarget( this.postprocessing.rtTextureColor );
renderer.clear();
renderer.render( scene, this );
// Render depth into texture
scene.overrideMaterial = this.materialDepth;
renderer.setRenderTarget( this.postprocessing.rtTextureDepth );
renderer.clear();
renderer.render( scene, this );
// Render bokeh composite
renderer.setRenderTarget( null );
renderer.render( this.postprocessing.scene, this.postprocessing.camera );
renderer.setRenderTarget( currentRenderTarget );
}
}
}
export { CinematicCamera };

View File

@ -0,0 +1,108 @@
class WebGL {
static isWebGLAvailable() {
try {
const canvas = document.createElement( 'canvas' );
return !! ( window.WebGLRenderingContext && ( canvas.getContext( 'webgl' ) || canvas.getContext( 'experimental-webgl' ) ) );
} catch ( e ) {
return false;
}
}
static isWebGL2Available() {
try {
const canvas = document.createElement( 'canvas' );
return !! ( window.WebGL2RenderingContext && canvas.getContext( 'webgl2' ) );
} catch ( e ) {
return false;
}
}
static isColorSpaceAvailable( colorSpace ) {
try {
const canvas = document.createElement( 'canvas' );
const ctx = window.WebGL2RenderingContext && canvas.getContext( 'webgl2' );
ctx.drawingBufferColorSpace = colorSpace;
return ctx.drawingBufferColorSpace === colorSpace; // deepscan-disable-line SAME_OPERAND_VALUE
} catch ( e ) {
return false;
}
}
static getWebGLErrorMessage() {
return this.getErrorMessage( 1 );
}
static getWebGL2ErrorMessage() {
return this.getErrorMessage( 2 );
}
static getErrorMessage( version ) {
const names = {
1: 'WebGL',
2: 'WebGL 2'
};
const contexts = {
1: window.WebGLRenderingContext,
2: window.WebGL2RenderingContext
};
let message = 'Your $0 does not seem to support <a href="http://khronos.org/webgl/wiki/Getting_a_WebGL_Implementation" style="color:#000">$1</a>';
const element = document.createElement( 'div' );
element.id = 'webglmessage';
element.style.fontFamily = 'monospace';
element.style.fontSize = '13px';
element.style.fontWeight = 'normal';
element.style.textAlign = 'center';
element.style.background = '#fff';
element.style.color = '#000';
element.style.padding = '1.5em';
element.style.width = '400px';
element.style.margin = '5em auto 0';
if ( contexts[ version ] ) {
message = message.replace( '$0', 'graphics card' );
} else {
message = message.replace( '$0', 'browser' );
}
message = message.replace( '$1', names[ version ] );
element.innerHTML = message;
return element;
}
}
export default WebGL;

View File

@ -0,0 +1,57 @@
if ( self.GPUShaderStage === undefined ) {
self.GPUShaderStage = { VERTEX: 1, FRAGMENT: 2, COMPUTE: 4 };
}
// statics
let isAvailable = navigator.gpu !== undefined;
if ( typeof window !== 'undefined' && isAvailable ) {
isAvailable = await navigator.gpu.requestAdapter();
}
class WebGPU {
static isAvailable() {
return Boolean( isAvailable );
}
static getStaticAdapter() {
return isAvailable;
}
static getErrorMessage() {
const message = 'Your browser does not support <a href="https://gpuweb.github.io/gpuweb/" style="color:blue">WebGPU</a> yet';
const element = document.createElement( 'div' );
element.id = 'webgpumessage';
element.style.fontFamily = 'monospace';
element.style.fontSize = '13px';
element.style.fontWeight = 'normal';
element.style.textAlign = 'center';
element.style.background = '#fff';
element.style.color = '#000';
element.style.padding = '1.5em';
element.style.maxWidth = '400px';
element.style.margin = '5em auto 0';
element.innerHTML = message;
return element;
}
}
export default WebGPU;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,282 @@
import {
EventDispatcher,
Matrix4,
Plane,
Raycaster,
Vector2,
Vector3
} from 'three';
const _plane = new Plane();
const _raycaster = new Raycaster();
const _pointer = new Vector2();
const _offset = new Vector3();
const _diff = new Vector2();
const _previousPointer = new Vector2();
const _intersection = new Vector3();
const _worldPosition = new Vector3();
const _inverseMatrix = new Matrix4();
const _up = new Vector3();
const _right = new Vector3();
class DragControls extends EventDispatcher {
constructor( _objects, _camera, _domElement ) {
super();
_domElement.style.touchAction = 'none'; // disable touch scroll
let _selected = null, _hovered = null;
const _intersections = [];
this.mode = 'translate';
this.rotateSpeed = 1;
//
const scope = this;
function activate() {
_domElement.addEventListener( 'pointermove', onPointerMove );
_domElement.addEventListener( 'pointerdown', onPointerDown );
_domElement.addEventListener( 'pointerup', onPointerCancel );
_domElement.addEventListener( 'pointerleave', onPointerCancel );
}
function deactivate() {
_domElement.removeEventListener( 'pointermove', onPointerMove );
_domElement.removeEventListener( 'pointerdown', onPointerDown );
_domElement.removeEventListener( 'pointerup', onPointerCancel );
_domElement.removeEventListener( 'pointerleave', onPointerCancel );
_domElement.style.cursor = '';
}
function dispose() {
deactivate();
}
function getObjects() {
return _objects;
}
function setObjects( objects ) {
_objects = objects;
}
function getRaycaster() {
return _raycaster;
}
function onPointerMove( event ) {
if ( scope.enabled === false ) return;
updatePointer( event );
_raycaster.setFromCamera( _pointer, _camera );
if ( _selected ) {
if ( scope.mode === 'translate' ) {
if ( _raycaster.ray.intersectPlane( _plane, _intersection ) ) {
_selected.position.copy( _intersection.sub( _offset ).applyMatrix4( _inverseMatrix ) );
}
} else if ( scope.mode === 'rotate' ) {
_diff.subVectors( _pointer, _previousPointer ).multiplyScalar( scope.rotateSpeed );
_selected.rotateOnWorldAxis( _up, _diff.x );
_selected.rotateOnWorldAxis( _right.normalize(), - _diff.y );
}
scope.dispatchEvent( { type: 'drag', object: _selected } );
_previousPointer.copy( _pointer );
} else {
// hover support
if ( event.pointerType === 'mouse' || event.pointerType === 'pen' ) {
_intersections.length = 0;
_raycaster.setFromCamera( _pointer, _camera );
_raycaster.intersectObjects( _objects, scope.recursive, _intersections );
if ( _intersections.length > 0 ) {
const object = _intersections[ 0 ].object;
_plane.setFromNormalAndCoplanarPoint( _camera.getWorldDirection( _plane.normal ), _worldPosition.setFromMatrixPosition( object.matrixWorld ) );
if ( _hovered !== object && _hovered !== null ) {
scope.dispatchEvent( { type: 'hoveroff', object: _hovered } );
_domElement.style.cursor = 'auto';
_hovered = null;
}
if ( _hovered !== object ) {
scope.dispatchEvent( { type: 'hoveron', object: object } );
_domElement.style.cursor = 'pointer';
_hovered = object;
}
} else {
if ( _hovered !== null ) {
scope.dispatchEvent( { type: 'hoveroff', object: _hovered } );
_domElement.style.cursor = 'auto';
_hovered = null;
}
}
}
}
_previousPointer.copy( _pointer );
}
function onPointerDown( event ) {
if ( scope.enabled === false ) return;
updatePointer( event );
_intersections.length = 0;
_raycaster.setFromCamera( _pointer, _camera );
_raycaster.intersectObjects( _objects, scope.recursive, _intersections );
if ( _intersections.length > 0 ) {
if ( scope.transformGroup === true ) {
// look for the outermost group in the object's upper hierarchy
_selected = findGroup( _intersections[ 0 ].object );
} else {
_selected = _intersections[ 0 ].object;
}
_plane.setFromNormalAndCoplanarPoint( _camera.getWorldDirection( _plane.normal ), _worldPosition.setFromMatrixPosition( _selected.matrixWorld ) );
if ( _raycaster.ray.intersectPlane( _plane, _intersection ) ) {
if ( scope.mode === 'translate' ) {
_inverseMatrix.copy( _selected.parent.matrixWorld ).invert();
_offset.copy( _intersection ).sub( _worldPosition.setFromMatrixPosition( _selected.matrixWorld ) );
} else if ( scope.mode === 'rotate' ) {
// the controls only support Y+ up
_up.set( 0, 1, 0 ).applyQuaternion( _camera.quaternion ).normalize();
_right.set( 1, 0, 0 ).applyQuaternion( _camera.quaternion ).normalize();
}
}
_domElement.style.cursor = 'move';
scope.dispatchEvent( { type: 'dragstart', object: _selected } );
}
_previousPointer.copy( _pointer );
}
function onPointerCancel() {
if ( scope.enabled === false ) return;
if ( _selected ) {
scope.dispatchEvent( { type: 'dragend', object: _selected } );
_selected = null;
}
_domElement.style.cursor = _hovered ? 'pointer' : 'auto';
}
function updatePointer( event ) {
const rect = _domElement.getBoundingClientRect();
_pointer.x = ( event.clientX - rect.left ) / rect.width * 2 - 1;
_pointer.y = - ( event.clientY - rect.top ) / rect.height * 2 + 1;
}
function findGroup( obj, group = null ) {
if ( obj.isGroup ) group = obj;
if ( obj.parent === null ) return group;
return findGroup( obj.parent, group );
}
activate();
// API
this.enabled = true;
this.recursive = true;
this.transformGroup = false;
this.activate = activate;
this.deactivate = deactivate;
this.dispose = dispose;
this.getObjects = getObjects;
this.getRaycaster = getRaycaster;
this.setObjects = setObjects;
}
}
export { DragControls };

View File

@ -0,0 +1,325 @@
import {
MathUtils,
Spherical,
Vector3
} from 'three';
const _lookDirection = new Vector3();
const _spherical = new Spherical();
const _target = new Vector3();
class FirstPersonControls {
constructor( object, domElement ) {
this.object = object;
this.domElement = domElement;
// API
this.enabled = true;
this.movementSpeed = 1.0;
this.lookSpeed = 0.005;
this.lookVertical = true;
this.autoForward = false;
this.activeLook = true;
this.heightSpeed = false;
this.heightCoef = 1.0;
this.heightMin = 0.0;
this.heightMax = 1.0;
this.constrainVertical = false;
this.verticalMin = 0;
this.verticalMax = Math.PI;
this.mouseDragOn = false;
// internals
this.autoSpeedFactor = 0.0;
this.pointerX = 0;
this.pointerY = 0;
this.moveForward = false;
this.moveBackward = false;
this.moveLeft = false;
this.moveRight = false;
this.viewHalfX = 0;
this.viewHalfY = 0;
// private variables
let lat = 0;
let lon = 0;
//
this.handleResize = function () {
if ( this.domElement === document ) {
this.viewHalfX = window.innerWidth / 2;
this.viewHalfY = window.innerHeight / 2;
} else {
this.viewHalfX = this.domElement.offsetWidth / 2;
this.viewHalfY = this.domElement.offsetHeight / 2;
}
};
this.onPointerDown = function ( event ) {
if ( this.domElement !== document ) {
this.domElement.focus();
}
if ( this.activeLook ) {
switch ( event.button ) {
case 0: this.moveForward = true; break;
case 2: this.moveBackward = true; break;
}
}
this.mouseDragOn = true;
};
this.onPointerUp = function ( event ) {
if ( this.activeLook ) {
switch ( event.button ) {
case 0: this.moveForward = false; break;
case 2: this.moveBackward = false; break;
}
}
this.mouseDragOn = false;
};
this.onPointerMove = function ( event ) {
if ( this.domElement === document ) {
this.pointerX = event.pageX - this.viewHalfX;
this.pointerY = event.pageY - this.viewHalfY;
} else {
this.pointerX = event.pageX - this.domElement.offsetLeft - this.viewHalfX;
this.pointerY = event.pageY - this.domElement.offsetTop - this.viewHalfY;
}
};
this.onKeyDown = function ( event ) {
switch ( event.code ) {
case 'ArrowUp':
case 'KeyW': this.moveForward = true; break;
case 'ArrowLeft':
case 'KeyA': this.moveLeft = true; break;
case 'ArrowDown':
case 'KeyS': this.moveBackward = true; break;
case 'ArrowRight':
case 'KeyD': this.moveRight = true; break;
case 'KeyR': this.moveUp = true; break;
case 'KeyF': this.moveDown = true; break;
}
};
this.onKeyUp = function ( event ) {
switch ( event.code ) {
case 'ArrowUp':
case 'KeyW': this.moveForward = false; break;
case 'ArrowLeft':
case 'KeyA': this.moveLeft = false; break;
case 'ArrowDown':
case 'KeyS': this.moveBackward = false; break;
case 'ArrowRight':
case 'KeyD': this.moveRight = false; break;
case 'KeyR': this.moveUp = false; break;
case 'KeyF': this.moveDown = false; break;
}
};
this.lookAt = function ( x, y, z ) {
if ( x.isVector3 ) {
_target.copy( x );
} else {
_target.set( x, y, z );
}
this.object.lookAt( _target );
setOrientation( this );
return this;
};
this.update = function () {
const targetPosition = new Vector3();
return function update( delta ) {
if ( this.enabled === false ) return;
if ( this.heightSpeed ) {
const y = MathUtils.clamp( this.object.position.y, this.heightMin, this.heightMax );
const heightDelta = y - this.heightMin;
this.autoSpeedFactor = delta * ( heightDelta * this.heightCoef );
} else {
this.autoSpeedFactor = 0.0;
}
const actualMoveSpeed = delta * this.movementSpeed;
if ( this.moveForward || ( this.autoForward && ! this.moveBackward ) ) this.object.translateZ( - ( actualMoveSpeed + this.autoSpeedFactor ) );
if ( this.moveBackward ) this.object.translateZ( actualMoveSpeed );
if ( this.moveLeft ) this.object.translateX( - actualMoveSpeed );
if ( this.moveRight ) this.object.translateX( actualMoveSpeed );
if ( this.moveUp ) this.object.translateY( actualMoveSpeed );
if ( this.moveDown ) this.object.translateY( - actualMoveSpeed );
let actualLookSpeed = delta * this.lookSpeed;
if ( ! this.activeLook ) {
actualLookSpeed = 0;
}
let verticalLookRatio = 1;
if ( this.constrainVertical ) {
verticalLookRatio = Math.PI / ( this.verticalMax - this.verticalMin );
}
lon -= this.pointerX * actualLookSpeed;
if ( this.lookVertical ) lat -= this.pointerY * actualLookSpeed * verticalLookRatio;
lat = Math.max( - 85, Math.min( 85, lat ) );
let phi = MathUtils.degToRad( 90 - lat );
const theta = MathUtils.degToRad( lon );
if ( this.constrainVertical ) {
phi = MathUtils.mapLinear( phi, 0, Math.PI, this.verticalMin, this.verticalMax );
}
const position = this.object.position;
targetPosition.setFromSphericalCoords( 1, phi, theta ).add( position );
this.object.lookAt( targetPosition );
};
}();
this.dispose = function () {
this.domElement.removeEventListener( 'contextmenu', contextmenu );
this.domElement.removeEventListener( 'pointerdown', _onPointerDown );
this.domElement.removeEventListener( 'pointermove', _onPointerMove );
this.domElement.removeEventListener( 'pointerup', _onPointerUp );
window.removeEventListener( 'keydown', _onKeyDown );
window.removeEventListener( 'keyup', _onKeyUp );
};
const _onPointerMove = this.onPointerMove.bind( this );
const _onPointerDown = this.onPointerDown.bind( this );
const _onPointerUp = this.onPointerUp.bind( this );
const _onKeyDown = this.onKeyDown.bind( this );
const _onKeyUp = this.onKeyUp.bind( this );
this.domElement.addEventListener( 'contextmenu', contextmenu );
this.domElement.addEventListener( 'pointerdown', _onPointerDown );
this.domElement.addEventListener( 'pointermove', _onPointerMove );
this.domElement.addEventListener( 'pointerup', _onPointerUp );
window.addEventListener( 'keydown', _onKeyDown );
window.addEventListener( 'keyup', _onKeyUp );
function setOrientation( controls ) {
const quaternion = controls.object.quaternion;
_lookDirection.set( 0, 0, - 1 ).applyQuaternion( quaternion );
_spherical.setFromVector3( _lookDirection );
lat = 90 - MathUtils.radToDeg( _spherical.phi );
lon = MathUtils.radToDeg( _spherical.theta );
}
this.handleResize();
setOrientation( this );
}
}
function contextmenu( event ) {
event.preventDefault();
}
export { FirstPersonControls };

View File

@ -0,0 +1,326 @@
import {
EventDispatcher,
Quaternion,
Vector3
} from 'three';
const _changeEvent = { type: 'change' };
class FlyControls extends EventDispatcher {
constructor( object, domElement ) {
super();
this.object = object;
this.domElement = domElement;
// API
// Set to false to disable this control
this.enabled = true;
this.movementSpeed = 1.0;
this.rollSpeed = 0.005;
this.dragToLook = false;
this.autoForward = false;
// disable default target object behavior
// internals
const scope = this;
const EPS = 0.000001;
const lastQuaternion = new Quaternion();
const lastPosition = new Vector3();
this.tmpQuaternion = new Quaternion();
this.status = 0;
this.moveState = { up: 0, down: 0, left: 0, right: 0, forward: 0, back: 0, pitchUp: 0, pitchDown: 0, yawLeft: 0, yawRight: 0, rollLeft: 0, rollRight: 0 };
this.moveVector = new Vector3( 0, 0, 0 );
this.rotationVector = new Vector3( 0, 0, 0 );
this.keydown = function ( event ) {
if ( event.altKey || this.enabled === false ) {
return;
}
switch ( event.code ) {
case 'ShiftLeft':
case 'ShiftRight': this.movementSpeedMultiplier = .1; break;
case 'KeyW': this.moveState.forward = 1; break;
case 'KeyS': this.moveState.back = 1; break;
case 'KeyA': this.moveState.left = 1; break;
case 'KeyD': this.moveState.right = 1; break;
case 'KeyR': this.moveState.up = 1; break;
case 'KeyF': this.moveState.down = 1; break;
case 'ArrowUp': this.moveState.pitchUp = 1; break;
case 'ArrowDown': this.moveState.pitchDown = 1; break;
case 'ArrowLeft': this.moveState.yawLeft = 1; break;
case 'ArrowRight': this.moveState.yawRight = 1; break;
case 'KeyQ': this.moveState.rollLeft = 1; break;
case 'KeyE': this.moveState.rollRight = 1; break;
}
this.updateMovementVector();
this.updateRotationVector();
};
this.keyup = function ( event ) {
if ( this.enabled === false ) return;
switch ( event.code ) {
case 'ShiftLeft':
case 'ShiftRight': this.movementSpeedMultiplier = 1; break;
case 'KeyW': this.moveState.forward = 0; break;
case 'KeyS': this.moveState.back = 0; break;
case 'KeyA': this.moveState.left = 0; break;
case 'KeyD': this.moveState.right = 0; break;
case 'KeyR': this.moveState.up = 0; break;
case 'KeyF': this.moveState.down = 0; break;
case 'ArrowUp': this.moveState.pitchUp = 0; break;
case 'ArrowDown': this.moveState.pitchDown = 0; break;
case 'ArrowLeft': this.moveState.yawLeft = 0; break;
case 'ArrowRight': this.moveState.yawRight = 0; break;
case 'KeyQ': this.moveState.rollLeft = 0; break;
case 'KeyE': this.moveState.rollRight = 0; break;
}
this.updateMovementVector();
this.updateRotationVector();
};
this.pointerdown = function ( event ) {
if ( this.enabled === false ) return;
if ( this.dragToLook ) {
this.status ++;
} else {
switch ( event.button ) {
case 0: this.moveState.forward = 1; break;
case 2: this.moveState.back = 1; break;
}
this.updateMovementVector();
}
};
this.pointermove = function ( event ) {
if ( this.enabled === false ) return;
if ( ! this.dragToLook || this.status > 0 ) {
const container = this.getContainerDimensions();
const halfWidth = container.size[ 0 ] / 2;
const halfHeight = container.size[ 1 ] / 2;
this.moveState.yawLeft = - ( ( event.pageX - container.offset[ 0 ] ) - halfWidth ) / halfWidth;
this.moveState.pitchDown = ( ( event.pageY - container.offset[ 1 ] ) - halfHeight ) / halfHeight;
this.updateRotationVector();
}
};
this.pointerup = function ( event ) {
if ( this.enabled === false ) return;
if ( this.dragToLook ) {
this.status --;
this.moveState.yawLeft = this.moveState.pitchDown = 0;
} else {
switch ( event.button ) {
case 0: this.moveState.forward = 0; break;
case 2: this.moveState.back = 0; break;
}
this.updateMovementVector();
}
this.updateRotationVector();
};
this.pointercancel = function () {
if ( this.enabled === false ) return;
if ( this.dragToLook ) {
this.status = 0;
this.moveState.yawLeft = this.moveState.pitchDown = 0;
} else {
this.moveState.forward = 0;
this.moveState.back = 0;
this.updateMovementVector();
}
this.updateRotationVector();
};
this.contextMenu = function ( event ) {
if ( this.enabled === false ) return;
event.preventDefault();
};
this.update = function ( delta ) {
if ( this.enabled === false ) return;
const moveMult = delta * scope.movementSpeed;
const rotMult = delta * scope.rollSpeed;
scope.object.translateX( scope.moveVector.x * moveMult );
scope.object.translateY( scope.moveVector.y * moveMult );
scope.object.translateZ( scope.moveVector.z * moveMult );
scope.tmpQuaternion.set( scope.rotationVector.x * rotMult, scope.rotationVector.y * rotMult, scope.rotationVector.z * rotMult, 1 ).normalize();
scope.object.quaternion.multiply( scope.tmpQuaternion );
if (
lastPosition.distanceToSquared( scope.object.position ) > EPS ||
8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS
) {
scope.dispatchEvent( _changeEvent );
lastQuaternion.copy( scope.object.quaternion );
lastPosition.copy( scope.object.position );
}
};
this.updateMovementVector = function () {
const forward = ( this.moveState.forward || ( this.autoForward && ! this.moveState.back ) ) ? 1 : 0;
this.moveVector.x = ( - this.moveState.left + this.moveState.right );
this.moveVector.y = ( - this.moveState.down + this.moveState.up );
this.moveVector.z = ( - forward + this.moveState.back );
//console.log( 'move:', [ this.moveVector.x, this.moveVector.y, this.moveVector.z ] );
};
this.updateRotationVector = function () {
this.rotationVector.x = ( - this.moveState.pitchDown + this.moveState.pitchUp );
this.rotationVector.y = ( - this.moveState.yawRight + this.moveState.yawLeft );
this.rotationVector.z = ( - this.moveState.rollRight + this.moveState.rollLeft );
//console.log( 'rotate:', [ this.rotationVector.x, this.rotationVector.y, this.rotationVector.z ] );
};
this.getContainerDimensions = function () {
if ( this.domElement != document ) {
return {
size: [ this.domElement.offsetWidth, this.domElement.offsetHeight ],
offset: [ this.domElement.offsetLeft, this.domElement.offsetTop ]
};
} else {
return {
size: [ window.innerWidth, window.innerHeight ],
offset: [ 0, 0 ]
};
}
};
this.dispose = function () {
this.domElement.removeEventListener( 'contextmenu', _contextmenu );
this.domElement.removeEventListener( 'pointerdown', _pointerdown );
this.domElement.removeEventListener( 'pointermove', _pointermove );
this.domElement.removeEventListener( 'pointerup', _pointerup );
this.domElement.removeEventListener( 'pointercancel', _pointercancel );
window.removeEventListener( 'keydown', _keydown );
window.removeEventListener( 'keyup', _keyup );
};
const _contextmenu = this.contextMenu.bind( this );
const _pointermove = this.pointermove.bind( this );
const _pointerdown = this.pointerdown.bind( this );
const _pointerup = this.pointerup.bind( this );
const _pointercancel = this.pointercancel.bind( this );
const _keydown = this.keydown.bind( this );
const _keyup = this.keyup.bind( this );
this.domElement.addEventListener( 'contextmenu', _contextmenu );
this.domElement.addEventListener( 'pointerdown', _pointerdown );
this.domElement.addEventListener( 'pointermove', _pointermove );
this.domElement.addEventListener( 'pointerup', _pointerup );
this.domElement.addEventListener( 'pointercancel', _pointercancel );
window.addEventListener( 'keydown', _keydown );
window.addEventListener( 'keyup', _keyup );
this.updateMovementVector();
this.updateRotationVector();
}
}
export { FlyControls };

View File

@ -0,0 +1,28 @@
import { MOUSE, TOUCH } from 'three';
import { OrbitControls } from './OrbitControls.js';
// MapControls performs orbiting, dollying (zooming), and panning.
// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
//
// 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, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.ROTATE };
this.touches = { ONE: TOUCH.PAN, TWO: TOUCH.DOLLY_ROTATE };
}
}
export { MapControls };

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,162 @@
import {
Euler,
EventDispatcher,
Vector3
} from 'three';
const _euler = new Euler( 0, 0, 0, 'YXZ' );
const _vector = new Vector3();
const _changeEvent = { type: 'change' };
const _lockEvent = { type: 'lock' };
const _unlockEvent = { type: 'unlock' };
const _PI_2 = Math.PI / 2;
class PointerLockControls extends EventDispatcher {
constructor( camera, domElement ) {
super();
this.camera = camera;
this.domElement = domElement;
this.isLocked = false;
// Set to constrain the pitch of the camera
// Range is 0 to Math.PI radians
this.minPolarAngle = 0; // radians
this.maxPolarAngle = Math.PI; // radians
this.pointerSpeed = 1.0;
this._onMouseMove = onMouseMove.bind( this );
this._onPointerlockChange = onPointerlockChange.bind( this );
this._onPointerlockError = onPointerlockError.bind( this );
this.connect();
}
connect() {
this.domElement.ownerDocument.addEventListener( 'mousemove', this._onMouseMove );
this.domElement.ownerDocument.addEventListener( 'pointerlockchange', this._onPointerlockChange );
this.domElement.ownerDocument.addEventListener( 'pointerlockerror', this._onPointerlockError );
}
disconnect() {
this.domElement.ownerDocument.removeEventListener( 'mousemove', this._onMouseMove );
this.domElement.ownerDocument.removeEventListener( 'pointerlockchange', this._onPointerlockChange );
this.domElement.ownerDocument.removeEventListener( 'pointerlockerror', this._onPointerlockError );
}
dispose() {
this.disconnect();
}
getObject() { // retaining this method for backward compatibility
return this.camera;
}
getDirection( v ) {
return v.set( 0, 0, - 1 ).applyQuaternion( this.camera.quaternion );
}
moveForward( distance ) {
// move forward parallel to the xz-plane
// assumes camera.up is y-up
const camera = this.camera;
_vector.setFromMatrixColumn( camera.matrix, 0 );
_vector.crossVectors( camera.up, _vector );
camera.position.addScaledVector( _vector, distance );
}
moveRight( distance ) {
const camera = this.camera;
_vector.setFromMatrixColumn( camera.matrix, 0 );
camera.position.addScaledVector( _vector, distance );
}
lock() {
this.domElement.requestPointerLock();
}
unlock() {
this.domElement.ownerDocument.exitPointerLock();
}
}
// event listeners
function onMouseMove( event ) {
if ( this.isLocked === false ) return;
const movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0;
const movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0;
const camera = this.camera;
_euler.setFromQuaternion( camera.quaternion );
_euler.y -= movementX * 0.002 * this.pointerSpeed;
_euler.x -= movementY * 0.002 * this.pointerSpeed;
_euler.x = Math.max( _PI_2 - this.maxPolarAngle, Math.min( _PI_2 - this.minPolarAngle, _euler.x ) );
camera.quaternion.setFromEuler( _euler );
this.dispatchEvent( _changeEvent );
}
function onPointerlockChange() {
if ( this.domElement.ownerDocument.pointerLockElement === this.domElement ) {
this.dispatchEvent( _lockEvent );
this.isLocked = true;
} else {
this.dispatchEvent( _unlockEvent );
this.isLocked = false;
}
}
function onPointerlockError() {
console.error( 'THREE.PointerLockControls: Unable to use Pointer Lock API' );
}
export { PointerLockControls };

View File

@ -0,0 +1,828 @@
import {
EventDispatcher,
MathUtils,
MOUSE,
Quaternion,
Vector2,
Vector3
} from 'three';
const _changeEvent = { type: 'change' };
const _startEvent = { type: 'start' };
const _endEvent = { type: 'end' };
class TrackballControls extends EventDispatcher {
constructor( object, domElement ) {
super();
const scope = this;
const STATE = { NONE: - 1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM_PAN: 4 };
this.object = object;
this.domElement = domElement;
this.domElement.style.touchAction = 'none'; // disable touch scroll
// API
this.enabled = true;
this.screen = { left: 0, top: 0, width: 0, height: 0 };
this.rotateSpeed = 1.0;
this.zoomSpeed = 1.2;
this.panSpeed = 0.3;
this.noRotate = false;
this.noZoom = false;
this.noPan = false;
this.staticMoving = false;
this.dynamicDampingFactor = 0.2;
this.minDistance = 0;
this.maxDistance = Infinity;
this.minZoom = 0;
this.maxZoom = Infinity;
this.keys = [ 'KeyA' /*A*/, 'KeyS' /*S*/, 'KeyD' /*D*/ ];
this.mouseButtons = { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.PAN };
// internals
this.target = new Vector3();
const EPS = 0.000001;
const lastPosition = new Vector3();
let lastZoom = 1;
let _state = STATE.NONE,
_keyState = STATE.NONE,
_touchZoomDistanceStart = 0,
_touchZoomDistanceEnd = 0,
_lastAngle = 0;
const _eye = new Vector3(),
_movePrev = new Vector2(),
_moveCurr = new Vector2(),
_lastAxis = new Vector3(),
_zoomStart = new Vector2(),
_zoomEnd = new Vector2(),
_panStart = new Vector2(),
_panEnd = new Vector2(),
_pointers = [],
_pointerPositions = {};
// for reset
this.target0 = this.target.clone();
this.position0 = this.object.position.clone();
this.up0 = this.object.up.clone();
this.zoom0 = this.object.zoom;
// methods
this.handleResize = function () {
const box = scope.domElement.getBoundingClientRect();
// adjustments come from similar code in the jquery offset() function
const d = scope.domElement.ownerDocument.documentElement;
scope.screen.left = box.left + window.pageXOffset - d.clientLeft;
scope.screen.top = box.top + window.pageYOffset - d.clientTop;
scope.screen.width = box.width;
scope.screen.height = box.height;
};
const getMouseOnScreen = ( function () {
const vector = new Vector2();
return function getMouseOnScreen( pageX, pageY ) {
vector.set(
( pageX - scope.screen.left ) / scope.screen.width,
( pageY - scope.screen.top ) / scope.screen.height
);
return vector;
};
}() );
const getMouseOnCircle = ( function () {
const vector = new Vector2();
return function getMouseOnCircle( pageX, pageY ) {
vector.set(
( ( pageX - scope.screen.width * 0.5 - scope.screen.left ) / ( scope.screen.width * 0.5 ) ),
( ( scope.screen.height + 2 * ( scope.screen.top - pageY ) ) / scope.screen.width ) // screen.width intentional
);
return vector;
};
}() );
this.rotateCamera = ( function () {
const axis = new Vector3(),
quaternion = new Quaternion(),
eyeDirection = new Vector3(),
objectUpDirection = new Vector3(),
objectSidewaysDirection = new Vector3(),
moveDirection = new Vector3();
return function rotateCamera() {
moveDirection.set( _moveCurr.x - _movePrev.x, _moveCurr.y - _movePrev.y, 0 );
let angle = moveDirection.length();
if ( angle ) {
_eye.copy( scope.object.position ).sub( scope.target );
eyeDirection.copy( _eye ).normalize();
objectUpDirection.copy( scope.object.up ).normalize();
objectSidewaysDirection.crossVectors( objectUpDirection, eyeDirection ).normalize();
objectUpDirection.setLength( _moveCurr.y - _movePrev.y );
objectSidewaysDirection.setLength( _moveCurr.x - _movePrev.x );
moveDirection.copy( objectUpDirection.add( objectSidewaysDirection ) );
axis.crossVectors( moveDirection, _eye ).normalize();
angle *= scope.rotateSpeed;
quaternion.setFromAxisAngle( axis, angle );
_eye.applyQuaternion( quaternion );
scope.object.up.applyQuaternion( quaternion );
_lastAxis.copy( axis );
_lastAngle = angle;
} else if ( ! scope.staticMoving && _lastAngle ) {
_lastAngle *= Math.sqrt( 1.0 - scope.dynamicDampingFactor );
_eye.copy( scope.object.position ).sub( scope.target );
quaternion.setFromAxisAngle( _lastAxis, _lastAngle );
_eye.applyQuaternion( quaternion );
scope.object.up.applyQuaternion( quaternion );
}
_movePrev.copy( _moveCurr );
};
}() );
this.zoomCamera = function () {
let factor;
if ( _state === STATE.TOUCH_ZOOM_PAN ) {
factor = _touchZoomDistanceStart / _touchZoomDistanceEnd;
_touchZoomDistanceStart = _touchZoomDistanceEnd;
if ( scope.object.isPerspectiveCamera ) {
_eye.multiplyScalar( factor );
} else if ( scope.object.isOrthographicCamera ) {
scope.object.zoom = MathUtils.clamp( scope.object.zoom / factor, scope.minZoom, scope.maxZoom );
if ( lastZoom !== scope.object.zoom ) {
scope.object.updateProjectionMatrix();
}
} else {
console.warn( 'THREE.TrackballControls: Unsupported camera type' );
}
} else {
factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * scope.zoomSpeed;
if ( factor !== 1.0 && factor > 0.0 ) {
if ( scope.object.isPerspectiveCamera ) {
_eye.multiplyScalar( factor );
} else if ( scope.object.isOrthographicCamera ) {
scope.object.zoom = MathUtils.clamp( scope.object.zoom / factor, scope.minZoom, scope.maxZoom );
if ( lastZoom !== scope.object.zoom ) {
scope.object.updateProjectionMatrix();
}
} else {
console.warn( 'THREE.TrackballControls: Unsupported camera type' );
}
}
if ( scope.staticMoving ) {
_zoomStart.copy( _zoomEnd );
} else {
_zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor;
}
}
};
this.panCamera = ( function () {
const mouseChange = new Vector2(),
objectUp = new Vector3(),
pan = new Vector3();
return function panCamera() {
mouseChange.copy( _panEnd ).sub( _panStart );
if ( mouseChange.lengthSq() ) {
if ( scope.object.isOrthographicCamera ) {
const scale_x = ( scope.object.right - scope.object.left ) / scope.object.zoom / scope.domElement.clientWidth;
const scale_y = ( scope.object.top - scope.object.bottom ) / scope.object.zoom / scope.domElement.clientWidth;
mouseChange.x *= scale_x;
mouseChange.y *= scale_y;
}
mouseChange.multiplyScalar( _eye.length() * scope.panSpeed );
pan.copy( _eye ).cross( scope.object.up ).setLength( mouseChange.x );
pan.add( objectUp.copy( scope.object.up ).setLength( mouseChange.y ) );
scope.object.position.add( pan );
scope.target.add( pan );
if ( scope.staticMoving ) {
_panStart.copy( _panEnd );
} else {
_panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( scope.dynamicDampingFactor ) );
}
}
};
}() );
this.checkDistances = function () {
if ( ! scope.noZoom || ! scope.noPan ) {
if ( _eye.lengthSq() > scope.maxDistance * scope.maxDistance ) {
scope.object.position.addVectors( scope.target, _eye.setLength( scope.maxDistance ) );
_zoomStart.copy( _zoomEnd );
}
if ( _eye.lengthSq() < scope.minDistance * scope.minDistance ) {
scope.object.position.addVectors( scope.target, _eye.setLength( scope.minDistance ) );
_zoomStart.copy( _zoomEnd );
}
}
};
this.update = function () {
_eye.subVectors( scope.object.position, scope.target );
if ( ! scope.noRotate ) {
scope.rotateCamera();
}
if ( ! scope.noZoom ) {
scope.zoomCamera();
}
if ( ! scope.noPan ) {
scope.panCamera();
}
scope.object.position.addVectors( scope.target, _eye );
if ( scope.object.isPerspectiveCamera ) {
scope.checkDistances();
scope.object.lookAt( scope.target );
if ( lastPosition.distanceToSquared( scope.object.position ) > EPS ) {
scope.dispatchEvent( _changeEvent );
lastPosition.copy( scope.object.position );
}
} else if ( scope.object.isOrthographicCamera ) {
scope.object.lookAt( scope.target );
if ( lastPosition.distanceToSquared( scope.object.position ) > EPS || lastZoom !== scope.object.zoom ) {
scope.dispatchEvent( _changeEvent );
lastPosition.copy( scope.object.position );
lastZoom = scope.object.zoom;
}
} else {
console.warn( 'THREE.TrackballControls: Unsupported camera type' );
}
};
this.reset = function () {
_state = STATE.NONE;
_keyState = STATE.NONE;
scope.target.copy( scope.target0 );
scope.object.position.copy( scope.position0 );
scope.object.up.copy( scope.up0 );
scope.object.zoom = scope.zoom0;
scope.object.updateProjectionMatrix();
_eye.subVectors( scope.object.position, scope.target );
scope.object.lookAt( scope.target );
scope.dispatchEvent( _changeEvent );
lastPosition.copy( scope.object.position );
lastZoom = scope.object.zoom;
};
// listeners
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( event );
} else {
onMouseUp();
}
//
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 keydown( event ) {
if ( scope.enabled === false ) return;
window.removeEventListener( 'keydown', keydown );
if ( _keyState !== STATE.NONE ) {
return;
} else if ( event.code === scope.keys[ STATE.ROTATE ] && ! scope.noRotate ) {
_keyState = STATE.ROTATE;
} else if ( event.code === scope.keys[ STATE.ZOOM ] && ! scope.noZoom ) {
_keyState = STATE.ZOOM;
} else if ( event.code === scope.keys[ STATE.PAN ] && ! scope.noPan ) {
_keyState = STATE.PAN;
}
}
function keyup() {
if ( scope.enabled === false ) return;
_keyState = STATE.NONE;
window.addEventListener( 'keydown', keydown );
}
function onMouseDown( event ) {
if ( _state === STATE.NONE ) {
switch ( event.button ) {
case scope.mouseButtons.LEFT:
_state = STATE.ROTATE;
break;
case scope.mouseButtons.MIDDLE:
_state = STATE.ZOOM;
break;
case scope.mouseButtons.RIGHT:
_state = STATE.PAN;
break;
}
}
const state = ( _keyState !== STATE.NONE ) ? _keyState : _state;
if ( state === STATE.ROTATE && ! scope.noRotate ) {
_moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) );
_movePrev.copy( _moveCurr );
} else if ( state === STATE.ZOOM && ! scope.noZoom ) {
_zoomStart.copy( getMouseOnScreen( event.pageX, event.pageY ) );
_zoomEnd.copy( _zoomStart );
} else if ( state === STATE.PAN && ! scope.noPan ) {
_panStart.copy( getMouseOnScreen( event.pageX, event.pageY ) );
_panEnd.copy( _panStart );
}
scope.dispatchEvent( _startEvent );
}
function onMouseMove( event ) {
const state = ( _keyState !== STATE.NONE ) ? _keyState : _state;
if ( state === STATE.ROTATE && ! scope.noRotate ) {
_movePrev.copy( _moveCurr );
_moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) );
} else if ( state === STATE.ZOOM && ! scope.noZoom ) {
_zoomEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) );
} else if ( state === STATE.PAN && ! scope.noPan ) {
_panEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) );
}
}
function onMouseUp() {
_state = STATE.NONE;
scope.dispatchEvent( _endEvent );
}
function onMouseWheel( event ) {
if ( scope.enabled === false ) return;
if ( scope.noZoom === true ) return;
event.preventDefault();
switch ( event.deltaMode ) {
case 2:
// Zoom in pages
_zoomStart.y -= event.deltaY * 0.025;
break;
case 1:
// Zoom in lines
_zoomStart.y -= event.deltaY * 0.01;
break;
default:
// undefined, 0, assume pixels
_zoomStart.y -= event.deltaY * 0.00025;
break;
}
scope.dispatchEvent( _startEvent );
scope.dispatchEvent( _endEvent );
}
function onTouchStart( event ) {
trackPointer( event );
switch ( _pointers.length ) {
case 1:
_state = STATE.TOUCH_ROTATE;
_moveCurr.copy( getMouseOnCircle( _pointers[ 0 ].pageX, _pointers[ 0 ].pageY ) );
_movePrev.copy( _moveCurr );
break;
default: // 2 or more
_state = STATE.TOUCH_ZOOM_PAN;
const dx = _pointers[ 0 ].pageX - _pointers[ 1 ].pageX;
const dy = _pointers[ 0 ].pageY - _pointers[ 1 ].pageY;
_touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy );
const x = ( _pointers[ 0 ].pageX + _pointers[ 1 ].pageX ) / 2;
const y = ( _pointers[ 0 ].pageY + _pointers[ 1 ].pageY ) / 2;
_panStart.copy( getMouseOnScreen( x, y ) );
_panEnd.copy( _panStart );
break;
}
scope.dispatchEvent( _startEvent );
}
function onTouchMove( event ) {
trackPointer( event );
switch ( _pointers.length ) {
case 1:
_movePrev.copy( _moveCurr );
_moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) );
break;
default: // 2 or more
const position = getSecondPointerPosition( event );
const dx = event.pageX - position.x;
const dy = event.pageY - position.y;
_touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy );
const x = ( event.pageX + position.x ) / 2;
const y = ( event.pageY + position.y ) / 2;
_panEnd.copy( getMouseOnScreen( x, y ) );
break;
}
}
function onTouchEnd( event ) {
switch ( _pointers.length ) {
case 0:
_state = STATE.NONE;
break;
case 1:
_state = STATE.TOUCH_ROTATE;
_moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) );
_movePrev.copy( _moveCurr );
break;
case 2:
_state = STATE.TOUCH_ZOOM_PAN;
for ( let i = 0; i < _pointers.length; i ++ ) {
if ( _pointers[ i ].pointerId !== event.pointerId ) {
const position = _pointerPositions[ _pointers[ i ].pointerId ];
_moveCurr.copy( getMouseOnCircle( position.x, position.y ) );
_movePrev.copy( _moveCurr );
break;
}
}
break;
}
scope.dispatchEvent( _endEvent );
}
function contextmenu( 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 ];
}
this.dispose = function () {
scope.domElement.removeEventListener( 'contextmenu', contextmenu );
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 );
window.removeEventListener( 'keydown', keydown );
window.removeEventListener( 'keyup', keyup );
};
this.domElement.addEventListener( 'contextmenu', contextmenu );
this.domElement.addEventListener( 'pointerdown', onPointerDown );
this.domElement.addEventListener( 'pointercancel', onPointerCancel );
this.domElement.addEventListener( 'wheel', onMouseWheel, { passive: false } );
window.addEventListener( 'keydown', keydown );
window.addEventListener( 'keyup', keyup );
this.handleResize();
// force an update at start
this.update();
}
}
export { TrackballControls };

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,384 @@
import {
Vector2,
Vector3,
DirectionalLight,
MathUtils,
ShaderChunk,
Matrix4,
Box3
} from 'three';
import { CSMFrustum } from './CSMFrustum.js';
import { CSMShader } from './CSMShader.js';
const _cameraToLightMatrix = new Matrix4();
const _lightSpaceFrustum = new CSMFrustum();
const _center = new Vector3();
const _bbox = new Box3();
const _uniformArray = [];
const _logArray = [];
const _lightOrientationMatrix = new Matrix4();
const _lightOrientationMatrixInverse = new Matrix4();
const _up = new Vector3( 0, 1, 0 );
export class CSM {
constructor( data ) {
this.camera = data.camera;
this.parent = data.parent;
this.cascades = data.cascades || 3;
this.maxFar = data.maxFar || 100000;
this.mode = data.mode || 'practical';
this.shadowMapSize = data.shadowMapSize || 2048;
this.shadowBias = data.shadowBias || 0.000001;
this.lightDirection = data.lightDirection || new Vector3( 1, - 1, 1 ).normalize();
this.lightIntensity = data.lightIntensity || 3;
this.lightNear = data.lightNear || 1;
this.lightFar = data.lightFar || 2000;
this.lightMargin = data.lightMargin || 200;
this.customSplitsCallback = data.customSplitsCallback;
this.fade = false;
this.mainFrustum = new CSMFrustum();
this.frustums = [];
this.breaks = [];
this.lights = [];
this.shaders = new Map();
this.createLights();
this.updateFrustums();
this.injectInclude();
}
createLights() {
for ( let i = 0; i < this.cascades; i ++ ) {
const light = new DirectionalLight( 0xffffff, this.lightIntensity );
light.castShadow = true;
light.shadow.mapSize.width = this.shadowMapSize;
light.shadow.mapSize.height = this.shadowMapSize;
light.shadow.camera.near = this.lightNear;
light.shadow.camera.far = this.lightFar;
light.shadow.bias = this.shadowBias;
this.parent.add( light );
this.parent.add( light.target );
this.lights.push( light );
}
}
initCascades() {
const camera = this.camera;
camera.updateProjectionMatrix();
this.mainFrustum.setFromProjectionMatrix( camera.projectionMatrix, this.maxFar );
this.mainFrustum.split( this.breaks, this.frustums );
}
updateShadowBounds() {
const frustums = this.frustums;
for ( let i = 0; i < frustums.length; i ++ ) {
const light = this.lights[ i ];
const shadowCam = light.shadow.camera;
const frustum = this.frustums[ i ];
// Get the two points that represent that furthest points on the frustum assuming
// that's either the diagonal across the far plane or the diagonal across the whole
// frustum itself.
const nearVerts = frustum.vertices.near;
const farVerts = frustum.vertices.far;
const point1 = farVerts[ 0 ];
let point2;
if ( point1.distanceTo( farVerts[ 2 ] ) > point1.distanceTo( nearVerts[ 2 ] ) ) {
point2 = farVerts[ 2 ];
} else {
point2 = nearVerts[ 2 ];
}
let squaredBBWidth = point1.distanceTo( point2 );
if ( this.fade ) {
// expand the shadow extents by the fade margin if fade is enabled.
const camera = this.camera;
const far = Math.max( camera.far, this.maxFar );
const linearDepth = frustum.vertices.far[ 0 ].z / ( far - camera.near );
const margin = 0.25 * Math.pow( linearDepth, 2.0 ) * ( far - camera.near );
squaredBBWidth += margin;
}
shadowCam.left = - squaredBBWidth / 2;
shadowCam.right = squaredBBWidth / 2;
shadowCam.top = squaredBBWidth / 2;
shadowCam.bottom = - squaredBBWidth / 2;
shadowCam.updateProjectionMatrix();
}
}
getBreaks() {
const camera = this.camera;
const far = Math.min( camera.far, this.maxFar );
this.breaks.length = 0;
switch ( this.mode ) {
case 'uniform':
uniformSplit( this.cascades, camera.near, far, this.breaks );
break;
case 'logarithmic':
logarithmicSplit( this.cascades, camera.near, far, this.breaks );
break;
case 'practical':
practicalSplit( this.cascades, camera.near, far, 0.5, this.breaks );
break;
case 'custom':
if ( this.customSplitsCallback === undefined ) console.error( 'CSM: Custom split scheme callback not defined.' );
this.customSplitsCallback( this.cascades, camera.near, far, this.breaks );
break;
}
function uniformSplit( amount, near, far, target ) {
for ( let i = 1; i < amount; i ++ ) {
target.push( ( near + ( far - near ) * i / amount ) / far );
}
target.push( 1 );
}
function logarithmicSplit( amount, near, far, target ) {
for ( let i = 1; i < amount; i ++ ) {
target.push( ( near * ( far / near ) ** ( i / amount ) ) / far );
}
target.push( 1 );
}
function practicalSplit( amount, near, far, lambda, target ) {
_uniformArray.length = 0;
_logArray.length = 0;
logarithmicSplit( amount, near, far, _logArray );
uniformSplit( amount, near, far, _uniformArray );
for ( let i = 1; i < amount; i ++ ) {
target.push( MathUtils.lerp( _uniformArray[ i - 1 ], _logArray[ i - 1 ], lambda ) );
}
target.push( 1 );
}
}
update() {
const camera = this.camera;
const frustums = this.frustums;
// for each frustum we need to find its min-max box aligned with the light orientation
// the position in _lightOrientationMatrix does not matter, as we transform there and back
_lightOrientationMatrix.lookAt( new Vector3(), this.lightDirection, _up );
_lightOrientationMatrixInverse.copy( _lightOrientationMatrix ).invert();
for ( let i = 0; i < frustums.length; i ++ ) {
const light = this.lights[ i ];
const shadowCam = light.shadow.camera;
const texelWidth = ( shadowCam.right - shadowCam.left ) / this.shadowMapSize;
const texelHeight = ( shadowCam.top - shadowCam.bottom ) / this.shadowMapSize;
_cameraToLightMatrix.multiplyMatrices( _lightOrientationMatrixInverse, camera.matrixWorld );
frustums[ i ].toSpace( _cameraToLightMatrix, _lightSpaceFrustum );
const nearVerts = _lightSpaceFrustum.vertices.near;
const farVerts = _lightSpaceFrustum.vertices.far;
_bbox.makeEmpty();
for ( let j = 0; j < 4; j ++ ) {
_bbox.expandByPoint( nearVerts[ j ] );
_bbox.expandByPoint( farVerts[ j ] );
}
_bbox.getCenter( _center );
_center.z = _bbox.max.z + this.lightMargin;
_center.x = Math.floor( _center.x / texelWidth ) * texelWidth;
_center.y = Math.floor( _center.y / texelHeight ) * texelHeight;
_center.applyMatrix4( _lightOrientationMatrix );
light.position.copy( _center );
light.target.position.copy( _center );
light.target.position.x += this.lightDirection.x;
light.target.position.y += this.lightDirection.y;
light.target.position.z += this.lightDirection.z;
}
}
injectInclude() {
ShaderChunk.lights_fragment_begin = CSMShader.lights_fragment_begin;
ShaderChunk.lights_pars_begin = CSMShader.lights_pars_begin;
}
setupMaterial( material ) {
material.defines = material.defines || {};
material.defines.USE_CSM = 1;
material.defines.CSM_CASCADES = this.cascades;
if ( this.fade ) {
material.defines.CSM_FADE = '';
}
const breaksVec2 = [];
const scope = this;
const shaders = this.shaders;
material.onBeforeCompile = function ( shader ) {
const far = Math.min( scope.camera.far, scope.maxFar );
scope.getExtendedBreaks( breaksVec2 );
shader.uniforms.CSM_cascades = { value: breaksVec2 };
shader.uniforms.cameraNear = { value: scope.camera.near };
shader.uniforms.shadowFar = { value: far };
shaders.set( material, shader );
};
shaders.set( material, null );
}
updateUniforms() {
const far = Math.min( this.camera.far, this.maxFar );
const shaders = this.shaders;
shaders.forEach( function ( shader, material ) {
if ( shader !== null ) {
const uniforms = shader.uniforms;
this.getExtendedBreaks( uniforms.CSM_cascades.value );
uniforms.cameraNear.value = this.camera.near;
uniforms.shadowFar.value = far;
}
if ( ! this.fade && 'CSM_FADE' in material.defines ) {
delete material.defines.CSM_FADE;
material.needsUpdate = true;
} else if ( this.fade && ! ( 'CSM_FADE' in material.defines ) ) {
material.defines.CSM_FADE = '';
material.needsUpdate = true;
}
}, this );
}
getExtendedBreaks( target ) {
while ( target.length < this.breaks.length ) {
target.push( new Vector2() );
}
target.length = this.breaks.length;
for ( let i = 0; i < this.cascades; i ++ ) {
const amount = this.breaks[ i ];
const prev = this.breaks[ i - 1 ] || 0;
target[ i ].x = prev;
target[ i ].y = amount;
}
}
updateFrustums() {
this.getBreaks();
this.initCascades();
this.updateShadowBounds();
this.updateUniforms();
}
remove() {
for ( let i = 0; i < this.lights.length; i ++ ) {
this.parent.remove( this.lights[ i ].target );
this.parent.remove( this.lights[ i ] );
}
}
dispose() {
const shaders = this.shaders;
shaders.forEach( function ( shader, material ) {
delete material.onBeforeCompile;
delete material.defines.USE_CSM;
delete material.defines.CSM_CASCADES;
delete material.defines.CSM_FADE;
if ( shader !== null ) {
delete shader.uniforms.CSM_cascades;
delete shader.uniforms.cameraNear;
delete shader.uniforms.shadowFar;
}
material.needsUpdate = true;
} );
shaders.clear();
}
}

View File

@ -0,0 +1,152 @@
import { Vector3, Matrix4 } from 'three';
const inverseProjectionMatrix = new Matrix4();
class CSMFrustum {
constructor( data ) {
data = data || {};
this.vertices = {
near: [
new Vector3(),
new Vector3(),
new Vector3(),
new Vector3()
],
far: [
new Vector3(),
new Vector3(),
new Vector3(),
new Vector3()
]
};
if ( data.projectionMatrix !== undefined ) {
this.setFromProjectionMatrix( data.projectionMatrix, data.maxFar || 10000 );
}
}
setFromProjectionMatrix( projectionMatrix, maxFar ) {
const isOrthographic = projectionMatrix.elements[ 2 * 4 + 3 ] === 0;
inverseProjectionMatrix.copy( projectionMatrix ).invert();
// 3 --- 0 vertices.near/far order
// | |
// 2 --- 1
// clip space spans from [-1, 1]
this.vertices.near[ 0 ].set( 1, 1, - 1 );
this.vertices.near[ 1 ].set( 1, - 1, - 1 );
this.vertices.near[ 2 ].set( - 1, - 1, - 1 );
this.vertices.near[ 3 ].set( - 1, 1, - 1 );
this.vertices.near.forEach( function ( v ) {
v.applyMatrix4( inverseProjectionMatrix );
} );
this.vertices.far[ 0 ].set( 1, 1, 1 );
this.vertices.far[ 1 ].set( 1, - 1, 1 );
this.vertices.far[ 2 ].set( - 1, - 1, 1 );
this.vertices.far[ 3 ].set( - 1, 1, 1 );
this.vertices.far.forEach( function ( v ) {
v.applyMatrix4( inverseProjectionMatrix );
const absZ = Math.abs( v.z );
if ( isOrthographic ) {
v.z *= Math.min( maxFar / absZ, 1.0 );
} else {
v.multiplyScalar( Math.min( maxFar / absZ, 1.0 ) );
}
} );
return this.vertices;
}
split( breaks, target ) {
while ( breaks.length > target.length ) {
target.push( new CSMFrustum() );
}
target.length = breaks.length;
for ( let i = 0; i < breaks.length; i ++ ) {
const cascade = target[ i ];
if ( i === 0 ) {
for ( let j = 0; j < 4; j ++ ) {
cascade.vertices.near[ j ].copy( this.vertices.near[ j ] );
}
} else {
for ( let j = 0; j < 4; j ++ ) {
cascade.vertices.near[ j ].lerpVectors( this.vertices.near[ j ], this.vertices.far[ j ], breaks[ i - 1 ] );
}
}
if ( i === breaks.length - 1 ) {
for ( let j = 0; j < 4; j ++ ) {
cascade.vertices.far[ j ].copy( this.vertices.far[ j ] );
}
} else {
for ( let j = 0; j < 4; j ++ ) {
cascade.vertices.far[ j ].lerpVectors( this.vertices.near[ j ], this.vertices.far[ j ], breaks[ i ] );
}
}
}
}
toSpace( cameraMatrix, target ) {
for ( let i = 0; i < 4; i ++ ) {
target.vertices.near[ i ]
.copy( this.vertices.near[ i ] )
.applyMatrix4( cameraMatrix );
target.vertices.far[ i ]
.copy( this.vertices.far[ i ] )
.applyMatrix4( cameraMatrix );
}
}
}
export { CSMFrustum };

View File

@ -0,0 +1,193 @@
import {
Group,
Mesh,
LineSegments,
BufferGeometry,
LineBasicMaterial,
Box3Helper,
Box3,
PlaneGeometry,
MeshBasicMaterial,
BufferAttribute,
DoubleSide
} from 'three';
class CSMHelper extends Group {
constructor( csm ) {
super();
this.csm = csm;
this.displayFrustum = true;
this.displayPlanes = true;
this.displayShadowBounds = true;
const indices = new Uint16Array( [ 0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7 ] );
const positions = new Float32Array( 24 );
const frustumGeometry = new BufferGeometry();
frustumGeometry.setIndex( new BufferAttribute( indices, 1 ) );
frustumGeometry.setAttribute( 'position', new BufferAttribute( positions, 3, false ) );
const frustumLines = new LineSegments( frustumGeometry, new LineBasicMaterial() );
this.add( frustumLines );
this.frustumLines = frustumLines;
this.cascadeLines = [];
this.cascadePlanes = [];
this.shadowLines = [];
}
updateVisibility() {
const displayFrustum = this.displayFrustum;
const displayPlanes = this.displayPlanes;
const displayShadowBounds = this.displayShadowBounds;
const frustumLines = this.frustumLines;
const cascadeLines = this.cascadeLines;
const cascadePlanes = this.cascadePlanes;
const shadowLines = this.shadowLines;
for ( let i = 0, l = cascadeLines.length; i < l; i ++ ) {
const cascadeLine = cascadeLines[ i ];
const cascadePlane = cascadePlanes[ i ];
const shadowLineGroup = shadowLines[ i ];
cascadeLine.visible = displayFrustum;
cascadePlane.visible = displayFrustum && displayPlanes;
shadowLineGroup.visible = displayShadowBounds;
}
frustumLines.visible = displayFrustum;
}
update() {
const csm = this.csm;
const camera = csm.camera;
const cascades = csm.cascades;
const mainFrustum = csm.mainFrustum;
const frustums = csm.frustums;
const lights = csm.lights;
const frustumLines = this.frustumLines;
const frustumLinePositions = frustumLines.geometry.getAttribute( 'position' );
const cascadeLines = this.cascadeLines;
const cascadePlanes = this.cascadePlanes;
const shadowLines = this.shadowLines;
this.position.copy( camera.position );
this.quaternion.copy( camera.quaternion );
this.scale.copy( camera.scale );
this.updateMatrixWorld( true );
while ( cascadeLines.length > cascades ) {
this.remove( cascadeLines.pop() );
this.remove( cascadePlanes.pop() );
this.remove( shadowLines.pop() );
}
while ( cascadeLines.length < cascades ) {
const cascadeLine = new Box3Helper( new Box3(), 0xffffff );
const planeMat = new MeshBasicMaterial( { transparent: true, opacity: 0.1, depthWrite: false, side: DoubleSide } );
const cascadePlane = new Mesh( new PlaneGeometry(), planeMat );
const shadowLineGroup = new Group();
const shadowLine = new Box3Helper( new Box3(), 0xffff00 );
shadowLineGroup.add( shadowLine );
this.add( cascadeLine );
this.add( cascadePlane );
this.add( shadowLineGroup );
cascadeLines.push( cascadeLine );
cascadePlanes.push( cascadePlane );
shadowLines.push( shadowLineGroup );
}
for ( let i = 0; i < cascades; i ++ ) {
const frustum = frustums[ i ];
const light = lights[ i ];
const shadowCam = light.shadow.camera;
const farVerts = frustum.vertices.far;
const cascadeLine = cascadeLines[ i ];
const cascadePlane = cascadePlanes[ i ];
const shadowLineGroup = shadowLines[ i ];
const shadowLine = shadowLineGroup.children[ 0 ];
cascadeLine.box.min.copy( farVerts[ 2 ] );
cascadeLine.box.max.copy( farVerts[ 0 ] );
cascadeLine.box.max.z += 1e-4;
cascadePlane.position.addVectors( farVerts[ 0 ], farVerts[ 2 ] );
cascadePlane.position.multiplyScalar( 0.5 );
cascadePlane.scale.subVectors( farVerts[ 0 ], farVerts[ 2 ] );
cascadePlane.scale.z = 1e-4;
this.remove( shadowLineGroup );
shadowLineGroup.position.copy( shadowCam.position );
shadowLineGroup.quaternion.copy( shadowCam.quaternion );
shadowLineGroup.scale.copy( shadowCam.scale );
shadowLineGroup.updateMatrixWorld( true );
this.attach( shadowLineGroup );
shadowLine.box.min.set( shadowCam.bottom, shadowCam.left, - shadowCam.far );
shadowLine.box.max.set( shadowCam.top, shadowCam.right, - shadowCam.near );
}
const nearVerts = mainFrustum.vertices.near;
const farVerts = mainFrustum.vertices.far;
frustumLinePositions.setXYZ( 0, farVerts[ 0 ].x, farVerts[ 0 ].y, farVerts[ 0 ].z );
frustumLinePositions.setXYZ( 1, farVerts[ 3 ].x, farVerts[ 3 ].y, farVerts[ 3 ].z );
frustumLinePositions.setXYZ( 2, farVerts[ 2 ].x, farVerts[ 2 ].y, farVerts[ 2 ].z );
frustumLinePositions.setXYZ( 3, farVerts[ 1 ].x, farVerts[ 1 ].y, farVerts[ 1 ].z );
frustumLinePositions.setXYZ( 4, nearVerts[ 0 ].x, nearVerts[ 0 ].y, nearVerts[ 0 ].z );
frustumLinePositions.setXYZ( 5, nearVerts[ 3 ].x, nearVerts[ 3 ].y, nearVerts[ 3 ].z );
frustumLinePositions.setXYZ( 6, nearVerts[ 2 ].x, nearVerts[ 2 ].y, nearVerts[ 2 ].z );
frustumLinePositions.setXYZ( 7, nearVerts[ 1 ].x, nearVerts[ 1 ].y, nearVerts[ 1 ].z );
frustumLinePositions.needsUpdate = true;
}
dispose() {
const frustumLines = this.frustumLines;
const cascadeLines = this.cascadeLines;
const cascadePlanes = this.cascadePlanes;
const shadowLines = this.shadowLines;
frustumLines.geometry.dispose();
frustumLines.material.dispose();
const cascades = this.csm.cascades;
for ( let i = 0; i < cascades; i ++ ) {
const cascadeLine = cascadeLines[ i ];
const cascadePlane = cascadePlanes[ i ];
const shadowLineGroup = shadowLines[ i ];
const shadowLine = shadowLineGroup.children[ 0 ];
cascadeLine.dispose(); // Box3Helper
cascadePlane.geometry.dispose();
cascadePlane.material.dispose();
shadowLine.dispose(); // Box3Helper
}
}
}
export { CSMHelper };

View File

@ -0,0 +1,295 @@
import { ShaderChunk } from 'three';
const CSMShader = {
lights_fragment_begin: /* glsl */`
vec3 geometryPosition = - vViewPosition;
vec3 geometryNormal = normal;
vec3 geometryViewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( vViewPosition );
vec3 geometryClearcoatNormal = vec3( 0.0 );
#ifdef USE_CLEARCOAT
geometryClearcoatNormal = clearcoatNormal;
#endif
#ifdef USE_IRIDESCENCE
float dotNVi = saturate( dot( normal, geometryViewDir ) );
if ( material.iridescenceThickness == 0.0 ) {
material.iridescence = 0.0;
} else {
material.iridescence = saturate( material.iridescence );
}
if ( material.iridescence > 0.0 ) {
material.iridescenceFresnel = evalIridescence( 1.0, material.iridescenceIOR, dotNVi, material.iridescenceThickness, material.specularColor );
// Iridescence F0 approximation
material.iridescenceF0 = Schlick_to_F0( material.iridescenceFresnel, 1.0, dotNVi );
}
#endif
IncidentLight directLight;
#if ( NUM_POINT_LIGHTS > 0 ) && defined( RE_Direct )
PointLight pointLight;
#if defined( USE_SHADOWMAP ) && NUM_POINT_LIGHT_SHADOWS > 0
PointLightShadow pointLightShadow;
#endif
#pragma unroll_loop_start
for ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {
pointLight = pointLights[ i ];
getPointLightInfo( pointLight, geometryPosition, directLight );
#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_POINT_LIGHT_SHADOWS )
pointLightShadow = pointLightShadows[ i ];
directLight.color *= ( directLight.visible && receiveShadow ) ? getPointShadow( pointShadowMap[ i ], pointLightShadow.shadowMapSize, pointLightShadow.shadowBias, pointLightShadow.shadowRadius, vPointShadowCoord[ i ], pointLightShadow.shadowCameraNear, pointLightShadow.shadowCameraFar ) : 1.0;
#endif
RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );
}
#pragma unroll_loop_end
#endif
#if ( NUM_SPOT_LIGHTS > 0 ) && defined( RE_Direct )
SpotLight spotLight;
vec4 spotColor;
vec3 spotLightCoord;
bool inSpotLightMap;
#if defined( USE_SHADOWMAP ) && NUM_SPOT_LIGHT_SHADOWS > 0
SpotLightShadow spotLightShadow;
#endif
#pragma unroll_loop_start
for ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {
spotLight = spotLights[ i ];
getSpotLightInfo( spotLight, geometryPosition, directLight );
// spot lights are ordered [shadows with maps, shadows without maps, maps without shadows, none]
#if ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS )
#define SPOT_LIGHT_MAP_INDEX UNROLLED_LOOP_INDEX
#elif ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )
#define SPOT_LIGHT_MAP_INDEX NUM_SPOT_LIGHT_MAPS
#else
#define SPOT_LIGHT_MAP_INDEX ( UNROLLED_LOOP_INDEX - NUM_SPOT_LIGHT_SHADOWS + NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS )
#endif
#if ( SPOT_LIGHT_MAP_INDEX < NUM_SPOT_LIGHT_MAPS )
spotLightCoord = vSpotLightCoord[ i ].xyz / vSpotLightCoord[ i ].w;
inSpotLightMap = all( lessThan( abs( spotLightCoord * 2. - 1. ), vec3( 1.0 ) ) );
spotColor = texture2D( spotLightMap[ SPOT_LIGHT_MAP_INDEX ], spotLightCoord.xy );
directLight.color = inSpotLightMap ? directLight.color * spotColor.rgb : directLight.color;
#endif
#undef SPOT_LIGHT_MAP_INDEX
#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )
spotLightShadow = spotLightShadows[ i ];
directLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( spotShadowMap[ i ], spotLightShadow.shadowMapSize, spotLightShadow.shadowBias, spotLightShadow.shadowRadius, vSpotLightCoord[ i ] ) : 1.0;
#endif
RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );
}
#pragma unroll_loop_end
#endif
#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct ) && defined( USE_CSM ) && defined( CSM_CASCADES )
DirectionalLight directionalLight;
float linearDepth = (vViewPosition.z) / (shadowFar - cameraNear);
#if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0
DirectionalLightShadow directionalLightShadow;
#endif
#if defined( USE_SHADOWMAP ) && defined( CSM_FADE )
vec2 cascade;
float cascadeCenter;
float closestEdge;
float margin;
float csmx;
float csmy;
#pragma unroll_loop_start
for ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {
directionalLight = directionalLights[ i ];
getDirectionalLightInfo( directionalLight, directLight );
#if ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS )
// NOTE: Depth gets larger away from the camera.
// cascade.x is closer, cascade.y is further
cascade = CSM_cascades[ i ];
cascadeCenter = ( cascade.x + cascade.y ) / 2.0;
closestEdge = linearDepth < cascadeCenter ? cascade.x : cascade.y;
margin = 0.25 * pow( closestEdge, 2.0 );
csmx = cascade.x - margin / 2.0;
csmy = cascade.y + margin / 2.0;
if( linearDepth >= csmx && ( linearDepth < csmy || UNROLLED_LOOP_INDEX == CSM_CASCADES - 1 ) ) {
float dist = min( linearDepth - csmx, csmy - linearDepth );
float ratio = clamp( dist / margin, 0.0, 1.0 );
vec3 prevColor = directLight.color;
directionalLightShadow = directionalLightShadows[ i ];
directLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;
bool shouldFadeLastCascade = UNROLLED_LOOP_INDEX == CSM_CASCADES - 1 && linearDepth > cascadeCenter;
directLight.color = mix( prevColor, directLight.color, shouldFadeLastCascade ? ratio : 1.0 );
ReflectedLight prevLight = reflectedLight;
RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );
bool shouldBlend = UNROLLED_LOOP_INDEX != CSM_CASCADES - 1 || UNROLLED_LOOP_INDEX == CSM_CASCADES - 1 && linearDepth < cascadeCenter;
float blendRatio = shouldBlend ? ratio : 1.0;
reflectedLight.directDiffuse = mix( prevLight.directDiffuse, reflectedLight.directDiffuse, blendRatio );
reflectedLight.directSpecular = mix( prevLight.directSpecular, reflectedLight.directSpecular, blendRatio );
reflectedLight.indirectDiffuse = mix( prevLight.indirectDiffuse, reflectedLight.indirectDiffuse, blendRatio );
reflectedLight.indirectSpecular = mix( prevLight.indirectSpecular, reflectedLight.indirectSpecular, blendRatio );
}
#endif
}
#pragma unroll_loop_end
#elif defined (USE_SHADOWMAP)
#pragma unroll_loop_start
for ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {
directionalLight = directionalLights[ i ];
getDirectionalLightInfo( directionalLight, directLight );
#if ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS )
directionalLightShadow = directionalLightShadows[ i ];
if(linearDepth >= CSM_cascades[UNROLLED_LOOP_INDEX].x && linearDepth < CSM_cascades[UNROLLED_LOOP_INDEX].y) directLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;
if(linearDepth >= CSM_cascades[UNROLLED_LOOP_INDEX].x && (linearDepth < CSM_cascades[UNROLLED_LOOP_INDEX].y || UNROLLED_LOOP_INDEX == CSM_CASCADES - 1)) RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );
#endif
}
#pragma unroll_loop_end
#elif ( NUM_DIR_LIGHT_SHADOWS > 0 )
// note: no loop here - all CSM lights are in fact one light only
getDirectionalLightInfo( directionalLights[0], directLight );
RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );
#endif
#if ( NUM_DIR_LIGHTS > NUM_DIR_LIGHT_SHADOWS)
// compute the lights not casting shadows (if any)
#pragma unroll_loop_start
for ( int i = NUM_DIR_LIGHT_SHADOWS; i < NUM_DIR_LIGHTS; i ++ ) {
directionalLight = directionalLights[ i ];
getDirectionalLightInfo( directionalLight, directLight );
RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );
}
#pragma unroll_loop_end
#endif
#endif
#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct ) && !defined( USE_CSM ) && !defined( CSM_CASCADES )
DirectionalLight directionalLight;
#if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0
DirectionalLightShadow directionalLightShadow;
#endif
#pragma unroll_loop_start
for ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {
directionalLight = directionalLights[ i ];
getDirectionalLightInfo( directionalLight, directLight );
#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS )
directionalLightShadow = directionalLightShadows[ i ];
directLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;
#endif
RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );
}
#pragma unroll_loop_end
#endif
#if ( NUM_RECT_AREA_LIGHTS > 0 ) && defined( RE_Direct_RectArea )
RectAreaLight rectAreaLight;
#pragma unroll_loop_start
for ( int i = 0; i < NUM_RECT_AREA_LIGHTS; i ++ ) {
rectAreaLight = rectAreaLights[ i ];
RE_Direct_RectArea( rectAreaLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );
}
#pragma unroll_loop_end
#endif
#if defined( RE_IndirectDiffuse )
vec3 iblIrradiance = vec3( 0.0 );
vec3 irradiance = getAmbientLightIrradiance( ambientLightColor );
#if defined( USE_LIGHT_PROBES )
irradiance += getLightProbeIrradiance( lightProbe, geometryNormal );
#endif
#if ( NUM_HEMI_LIGHTS > 0 )
#pragma unroll_loop_start
for ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {
irradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], geometryNormal );
}
#pragma unroll_loop_end
#endif
#endif
#if defined( RE_IndirectSpecular )
vec3 radiance = vec3( 0.0 );
vec3 clearcoatRadiance = vec3( 0.0 );
#endif
`,
lights_pars_begin: /* glsl */`
#if defined( USE_CSM ) && defined( CSM_CASCADES )
uniform vec2 CSM_cascades[CSM_CASCADES];
uniform float cameraNear;
uniform float shadowFar;
#endif
` + ShaderChunk.lights_pars_begin
};
export { CSMShader };

View File

@ -0,0 +1,422 @@
import {
Curve,
Vector3
} from 'three';
/**
* A bunch of parametric curves
*
* Formulas collected from various sources
* http://mathworld.wolfram.com/HeartCurve.html
* http://en.wikipedia.org/wiki/Viviani%27s_curve
* http://www.mi.sanu.ac.rs/vismath/taylorapril2011/Taylor.pdf
* https://prideout.net/blog/old/blog/index.html@p=44.html
*/
// GrannyKnot
class GrannyKnot extends Curve {
getPoint( t, optionalTarget = new Vector3() ) {
const point = optionalTarget;
t = 2 * Math.PI * t;
const x = - 0.22 * Math.cos( t ) - 1.28 * Math.sin( t ) - 0.44 * Math.cos( 3 * t ) - 0.78 * Math.sin( 3 * t );
const y = - 0.1 * Math.cos( 2 * t ) - 0.27 * Math.sin( 2 * t ) + 0.38 * Math.cos( 4 * t ) + 0.46 * Math.sin( 4 * t );
const z = 0.7 * Math.cos( 3 * t ) - 0.4 * Math.sin( 3 * t );
return point.set( x, y, z ).multiplyScalar( 20 );
}
}
// HeartCurve
class HeartCurve extends Curve {
constructor( scale = 5 ) {
super();
this.scale = scale;
}
getPoint( t, optionalTarget = new Vector3() ) {
const point = optionalTarget;
t *= 2 * Math.PI;
const x = 16 * Math.pow( Math.sin( t ), 3 );
const y = 13 * Math.cos( t ) - 5 * Math.cos( 2 * t ) - 2 * Math.cos( 3 * t ) - Math.cos( 4 * t );
const z = 0;
return point.set( x, y, z ).multiplyScalar( this.scale );
}
}
// Viviani's Curve
class VivianiCurve extends Curve {
constructor( scale = 70 ) {
super();
this.scale = scale;
}
getPoint( t, optionalTarget = new Vector3() ) {
const point = optionalTarget;
t = t * 4 * Math.PI; // normalized to 0..1
const a = this.scale / 2;
const x = a * ( 1 + Math.cos( t ) );
const y = a * Math.sin( t );
const z = 2 * a * Math.sin( t / 2 );
return point.set( x, y, z );
}
}
// KnotCurve
class KnotCurve extends Curve {
getPoint( t, optionalTarget = new Vector3() ) {
const point = optionalTarget;
t *= 2 * Math.PI;
const R = 10;
const s = 50;
const x = s * Math.sin( t );
const y = Math.cos( t ) * ( R + s * Math.cos( t ) );
const z = Math.sin( t ) * ( R + s * Math.cos( t ) );
return point.set( x, y, z );
}
}
// HelixCurve
class HelixCurve extends Curve {
getPoint( t, optionalTarget = new Vector3() ) {
const point = optionalTarget;
const a = 30; // radius
const b = 150; // height
const t2 = 2 * Math.PI * t * b / 30;
const x = Math.cos( t2 ) * a;
const y = Math.sin( t2 ) * a;
const z = b * t;
return point.set( x, y, z );
}
}
// TrefoilKnot
class TrefoilKnot extends Curve {
constructor( scale = 10 ) {
super();
this.scale = scale;
}
getPoint( t, optionalTarget = new Vector3() ) {
const point = optionalTarget;
t *= Math.PI * 2;
const x = ( 2 + Math.cos( 3 * t ) ) * Math.cos( 2 * t );
const y = ( 2 + Math.cos( 3 * t ) ) * Math.sin( 2 * t );
const z = Math.sin( 3 * t );
return point.set( x, y, z ).multiplyScalar( this.scale );
}
}
// TorusKnot
class TorusKnot extends Curve {
constructor( scale = 10 ) {
super();
this.scale = scale;
}
getPoint( t, optionalTarget = new Vector3() ) {
const point = optionalTarget;
const p = 3;
const q = 4;
t *= Math.PI * 2;
const x = ( 2 + Math.cos( q * t ) ) * Math.cos( p * t );
const y = ( 2 + Math.cos( q * t ) ) * Math.sin( p * t );
const z = Math.sin( q * t );
return point.set( x, y, z ).multiplyScalar( this.scale );
}
}
// CinquefoilKnot
class CinquefoilKnot extends Curve {
constructor( scale = 10 ) {
super();
this.scale = scale;
}
getPoint( t, optionalTarget = new Vector3() ) {
const point = optionalTarget;
const p = 2;
const q = 5;
t *= Math.PI * 2;
const x = ( 2 + Math.cos( q * t ) ) * Math.cos( p * t );
const y = ( 2 + Math.cos( q * t ) ) * Math.sin( p * t );
const z = Math.sin( q * t );
return point.set( x, y, z ).multiplyScalar( this.scale );
}
}
// TrefoilPolynomialKnot
class TrefoilPolynomialKnot extends Curve {
constructor( scale = 10 ) {
super();
this.scale = scale;
}
getPoint( t, optionalTarget = new Vector3() ) {
const point = optionalTarget;
t = t * 4 - 2;
const x = Math.pow( t, 3 ) - 3 * t;
const y = Math.pow( t, 4 ) - 4 * t * t;
const z = 1 / 5 * Math.pow( t, 5 ) - 2 * t;
return point.set( x, y, z ).multiplyScalar( this.scale );
}
}
function scaleTo( x, y, t ) {
const r = y - x;
return t * r + x;
}
// FigureEightPolynomialKnot
class FigureEightPolynomialKnot extends Curve {
constructor( scale = 1 ) {
super();
this.scale = scale;
}
getPoint( t, optionalTarget = new Vector3() ) {
const point = optionalTarget;
t = scaleTo( - 4, 4, t );
const x = 2 / 5 * t * ( t * t - 7 ) * ( t * t - 10 );
const y = Math.pow( t, 4 ) - 13 * t * t;
const z = 1 / 10 * t * ( t * t - 4 ) * ( t * t - 9 ) * ( t * t - 12 );
return point.set( x, y, z ).multiplyScalar( this.scale );
}
}
// DecoratedTorusKnot4a
class DecoratedTorusKnot4a extends Curve {
constructor( scale = 40 ) {
super();
this.scale = scale;
}
getPoint( t, optionalTarget = new Vector3() ) {
const point = optionalTarget;
t *= Math.PI * 2;
const x = Math.cos( 2 * t ) * ( 1 + 0.6 * ( Math.cos( 5 * t ) + 0.75 * Math.cos( 10 * t ) ) );
const y = Math.sin( 2 * t ) * ( 1 + 0.6 * ( Math.cos( 5 * t ) + 0.75 * Math.cos( 10 * t ) ) );
const z = 0.35 * Math.sin( 5 * t );
return point.set( x, y, z ).multiplyScalar( this.scale );
}
}
// DecoratedTorusKnot4b
class DecoratedTorusKnot4b extends Curve {
constructor( scale = 40 ) {
super();
this.scale = scale;
}
getPoint( t, optionalTarget = new Vector3() ) {
const point = optionalTarget;
const fi = t * Math.PI * 2;
const x = Math.cos( 2 * fi ) * ( 1 + 0.45 * Math.cos( 3 * fi ) + 0.4 * Math.cos( 9 * fi ) );
const y = Math.sin( 2 * fi ) * ( 1 + 0.45 * Math.cos( 3 * fi ) + 0.4 * Math.cos( 9 * fi ) );
const z = 0.2 * Math.sin( 9 * fi );
return point.set( x, y, z ).multiplyScalar( this.scale );
}
}
// DecoratedTorusKnot5a
class DecoratedTorusKnot5a extends Curve {
constructor( scale = 40 ) {
super();
this.scale = scale;
}
getPoint( t, optionalTarget = new Vector3() ) {
const point = optionalTarget;
const fi = t * Math.PI * 2;
const x = Math.cos( 3 * fi ) * ( 1 + 0.3 * Math.cos( 5 * fi ) + 0.5 * Math.cos( 10 * fi ) );
const y = Math.sin( 3 * fi ) * ( 1 + 0.3 * Math.cos( 5 * fi ) + 0.5 * Math.cos( 10 * fi ) );
const z = 0.2 * Math.sin( 20 * fi );
return point.set( x, y, z ).multiplyScalar( this.scale );
}
}
// DecoratedTorusKnot5c
class DecoratedTorusKnot5c extends Curve {
constructor( scale = 40 ) {
super();
this.scale = scale;
}
getPoint( t, optionalTarget = new Vector3() ) {
const point = optionalTarget;
const fi = t * Math.PI * 2;
const x = Math.cos( 4 * fi ) * ( 1 + 0.5 * ( Math.cos( 5 * fi ) + 0.4 * Math.cos( 20 * fi ) ) );
const y = Math.sin( 4 * fi ) * ( 1 + 0.5 * ( Math.cos( 5 * fi ) + 0.4 * Math.cos( 20 * fi ) ) );
const z = 0.35 * Math.sin( 15 * fi );
return point.set( x, y, z ).multiplyScalar( this.scale );
}
}
export {
GrannyKnot,
HeartCurve,
VivianiCurve,
KnotCurve,
HelixCurve,
TrefoilKnot,
TorusKnot,
CinquefoilKnot,
TrefoilPolynomialKnot,
FigureEightPolynomialKnot,
DecoratedTorusKnot4a,
DecoratedTorusKnot4b,
DecoratedTorusKnot5a,
DecoratedTorusKnot5c
};

View File

@ -0,0 +1,80 @@
import {
Curve,
Vector3,
Vector4
} from 'three';
import * as NURBSUtils from '../curves/NURBSUtils.js';
/**
* NURBS curve object
*
* Derives from Curve, overriding getPoint and getTangent.
*
* Implementation is based on (x, y [, z=0 [, w=1]]) control points with w=weight.
*
**/
class NURBSCurve extends Curve {
constructor(
degree,
knots /* array of reals */,
controlPoints /* array of Vector(2|3|4) */,
startKnot /* index in knots */,
endKnot /* index in knots */
) {
super();
this.degree = degree;
this.knots = knots;
this.controlPoints = [];
// Used by periodic NURBS to remove hidden spans
this.startKnot = startKnot || 0;
this.endKnot = endKnot || ( this.knots.length - 1 );
for ( let i = 0; i < controlPoints.length; ++ i ) {
// ensure Vector4 for control points
const point = controlPoints[ i ];
this.controlPoints[ i ] = new Vector4( point.x, point.y, point.z, point.w );
}
}
getPoint( t, optionalTarget = new Vector3() ) {
const point = optionalTarget;
const u = this.knots[ this.startKnot ] + t * ( this.knots[ this.endKnot ] - this.knots[ this.startKnot ] ); // linear mapping t->u
// following results in (wx, wy, wz, w) homogeneous point
const hpoint = NURBSUtils.calcBSplinePoint( this.degree, this.knots, this.controlPoints, u );
if ( hpoint.w !== 1.0 ) {
// project to 3D space: (wx, wy, wz, w) -> (x, y, z, 1)
hpoint.divideScalar( hpoint.w );
}
return point.set( hpoint.x, hpoint.y, hpoint.z );
}
getTangent( t, optionalTarget = new Vector3() ) {
const tangent = optionalTarget;
const u = this.knots[ 0 ] + t * ( this.knots[ this.knots.length - 1 ] - this.knots[ 0 ] );
const ders = NURBSUtils.calcNURBSDerivatives( this.degree, this.knots, this.controlPoints, u, 1 );
tangent.copy( ders[ 1 ] ).normalize();
return tangent;
}
}
export { NURBSCurve };

View File

@ -0,0 +1,52 @@
import {
Vector4
} from 'three';
import * as NURBSUtils from '../curves/NURBSUtils.js';
/**
* NURBS surface object
*
* Implementation is based on (x, y [, z=0 [, w=1]]) control points with w=weight.
**/
class NURBSSurface {
constructor( degree1, degree2, knots1, knots2 /* arrays of reals */, controlPoints /* array^2 of Vector(2|3|4) */ ) {
this.degree1 = degree1;
this.degree2 = degree2;
this.knots1 = knots1;
this.knots2 = knots2;
this.controlPoints = [];
const len1 = knots1.length - degree1 - 1;
const len2 = knots2.length - degree2 - 1;
// ensure Vector4 for control points
for ( let i = 0; i < len1; ++ i ) {
this.controlPoints[ i ] = [];
for ( let j = 0; j < len2; ++ j ) {
const point = controlPoints[ i ][ j ];
this.controlPoints[ i ][ j ] = new Vector4( point.x, point.y, point.z, point.w );
}
}
}
getPoint( t1, t2, target ) {
const u = this.knots1[ 0 ] + t1 * ( this.knots1[ this.knots1.length - 1 ] - this.knots1[ 0 ] ); // linear mapping t1->u
const v = this.knots2[ 0 ] + t2 * ( this.knots2[ this.knots2.length - 1 ] - this.knots2[ 0 ] ); // linear mapping t2->u
NURBSUtils.calcSurfacePoint( this.degree1, this.degree2, this.knots1, this.knots2, this.controlPoints, u, v, target );
}
}
export { NURBSSurface };

View File

@ -0,0 +1,542 @@
import {
Vector3,
Vector4
} from 'three';
/**
* NURBS utils
*
* See NURBSCurve and NURBSSurface.
**/
/**************************************************************
* NURBS Utils
**************************************************************/
/*
Finds knot vector span.
p : degree
u : parametric value
U : knot vector
returns the span
*/
function findSpan( p, u, U ) {
const n = U.length - p - 1;
if ( u >= U[ n ] ) {
return n - 1;
}
if ( u <= U[ p ] ) {
return p;
}
let low = p;
let high = n;
let mid = Math.floor( ( low + high ) / 2 );
while ( u < U[ mid ] || u >= U[ mid + 1 ] ) {
if ( u < U[ mid ] ) {
high = mid;
} else {
low = mid;
}
mid = Math.floor( ( low + high ) / 2 );
}
return mid;
}
/*
Calculate basis functions. See The NURBS Book, page 70, algorithm A2.2
span : span in which u lies
u : parametric point
p : degree
U : knot vector
returns array[p+1] with basis functions values.
*/
function calcBasisFunctions( span, u, p, U ) {
const N = [];
const left = [];
const right = [];
N[ 0 ] = 1.0;
for ( let j = 1; j <= p; ++ j ) {
left[ j ] = u - U[ span + 1 - j ];
right[ j ] = U[ span + j ] - u;
let saved = 0.0;
for ( let r = 0; r < j; ++ r ) {
const rv = right[ r + 1 ];
const lv = left[ j - r ];
const temp = N[ r ] / ( rv + lv );
N[ r ] = saved + rv * temp;
saved = lv * temp;
}
N[ j ] = saved;
}
return N;
}
/*
Calculate B-Spline curve points. See The NURBS Book, page 82, algorithm A3.1.
p : degree of B-Spline
U : knot vector
P : control points (x, y, z, w)
u : parametric point
returns point for given u
*/
function calcBSplinePoint( p, U, P, u ) {
const span = findSpan( p, u, U );
const N = calcBasisFunctions( span, u, p, U );
const C = new Vector4( 0, 0, 0, 0 );
for ( let j = 0; j <= p; ++ j ) {
const point = P[ span - p + j ];
const Nj = N[ j ];
const wNj = point.w * Nj;
C.x += point.x * wNj;
C.y += point.y * wNj;
C.z += point.z * wNj;
C.w += point.w * Nj;
}
return C;
}
/*
Calculate basis functions derivatives. See The NURBS Book, page 72, algorithm A2.3.
span : span in which u lies
u : parametric point
p : degree
n : number of derivatives to calculate
U : knot vector
returns array[n+1][p+1] with basis functions derivatives
*/
function calcBasisFunctionDerivatives( span, u, p, n, U ) {
const zeroArr = [];
for ( let i = 0; i <= p; ++ i )
zeroArr[ i ] = 0.0;
const ders = [];
for ( let i = 0; i <= n; ++ i )
ders[ i ] = zeroArr.slice( 0 );
const ndu = [];
for ( let i = 0; i <= p; ++ i )
ndu[ i ] = zeroArr.slice( 0 );
ndu[ 0 ][ 0 ] = 1.0;
const left = zeroArr.slice( 0 );
const right = zeroArr.slice( 0 );
for ( let j = 1; j <= p; ++ j ) {
left[ j ] = u - U[ span + 1 - j ];
right[ j ] = U[ span + j ] - u;
let saved = 0.0;
for ( let r = 0; r < j; ++ r ) {
const rv = right[ r + 1 ];
const lv = left[ j - r ];
ndu[ j ][ r ] = rv + lv;
const temp = ndu[ r ][ j - 1 ] / ndu[ j ][ r ];
ndu[ r ][ j ] = saved + rv * temp;
saved = lv * temp;
}
ndu[ j ][ j ] = saved;
}
for ( let j = 0; j <= p; ++ j ) {
ders[ 0 ][ j ] = ndu[ j ][ p ];
}
for ( let r = 0; r <= p; ++ r ) {
let s1 = 0;
let s2 = 1;
const a = [];
for ( let i = 0; i <= p; ++ i ) {
a[ i ] = zeroArr.slice( 0 );
}
a[ 0 ][ 0 ] = 1.0;
for ( let k = 1; k <= n; ++ k ) {
let d = 0.0;
const rk = r - k;
const pk = p - k;
if ( r >= k ) {
a[ s2 ][ 0 ] = a[ s1 ][ 0 ] / ndu[ pk + 1 ][ rk ];
d = a[ s2 ][ 0 ] * ndu[ rk ][ pk ];
}
const j1 = ( rk >= - 1 ) ? 1 : - rk;
const j2 = ( r - 1 <= pk ) ? k - 1 : p - r;
for ( let j = j1; j <= j2; ++ j ) {
a[ s2 ][ j ] = ( a[ s1 ][ j ] - a[ s1 ][ j - 1 ] ) / ndu[ pk + 1 ][ rk + j ];
d += a[ s2 ][ j ] * ndu[ rk + j ][ pk ];
}
if ( r <= pk ) {
a[ s2 ][ k ] = - a[ s1 ][ k - 1 ] / ndu[ pk + 1 ][ r ];
d += a[ s2 ][ k ] * ndu[ r ][ pk ];
}
ders[ k ][ r ] = d;
const j = s1;
s1 = s2;
s2 = j;
}
}
let r = p;
for ( let k = 1; k <= n; ++ k ) {
for ( let j = 0; j <= p; ++ j ) {
ders[ k ][ j ] *= r;
}
r *= p - k;
}
return ders;
}
/*
Calculate derivatives of a B-Spline. See The NURBS Book, page 93, algorithm A3.2.
p : degree
U : knot vector
P : control points
u : Parametric points
nd : number of derivatives
returns array[d+1] with derivatives
*/
function calcBSplineDerivatives( p, U, P, u, nd ) {
const du = nd < p ? nd : p;
const CK = [];
const span = findSpan( p, u, U );
const nders = calcBasisFunctionDerivatives( span, u, p, du, U );
const Pw = [];
for ( let i = 0; i < P.length; ++ i ) {
const point = P[ i ].clone();
const w = point.w;
point.x *= w;
point.y *= w;
point.z *= w;
Pw[ i ] = point;
}
for ( let k = 0; k <= du; ++ k ) {
const point = Pw[ span - p ].clone().multiplyScalar( nders[ k ][ 0 ] );
for ( let j = 1; j <= p; ++ j ) {
point.add( Pw[ span - p + j ].clone().multiplyScalar( nders[ k ][ j ] ) );
}
CK[ k ] = point;
}
for ( let k = du + 1; k <= nd + 1; ++ k ) {
CK[ k ] = new Vector4( 0, 0, 0 );
}
return CK;
}
/*
Calculate "K over I"
returns k!/(i!(k-i)!)
*/
function calcKoverI( k, i ) {
let nom = 1;
for ( let j = 2; j <= k; ++ j ) {
nom *= j;
}
let denom = 1;
for ( let j = 2; j <= i; ++ j ) {
denom *= j;
}
for ( let j = 2; j <= k - i; ++ j ) {
denom *= j;
}
return nom / denom;
}
/*
Calculate derivatives (0-nd) of rational curve. See The NURBS Book, page 127, algorithm A4.2.
Pders : result of function calcBSplineDerivatives
returns array with derivatives for rational curve.
*/
function calcRationalCurveDerivatives( Pders ) {
const nd = Pders.length;
const Aders = [];
const wders = [];
for ( let i = 0; i < nd; ++ i ) {
const point = Pders[ i ];
Aders[ i ] = new Vector3( point.x, point.y, point.z );
wders[ i ] = point.w;
}
const CK = [];
for ( let k = 0; k < nd; ++ k ) {
const v = Aders[ k ].clone();
for ( let i = 1; i <= k; ++ i ) {
v.sub( CK[ k - i ].clone().multiplyScalar( calcKoverI( k, i ) * wders[ i ] ) );
}
CK[ k ] = v.divideScalar( wders[ 0 ] );
}
return CK;
}
/*
Calculate NURBS curve derivatives. See The NURBS Book, page 127, algorithm A4.2.
p : degree
U : knot vector
P : control points in homogeneous space
u : parametric points
nd : number of derivatives
returns array with derivatives.
*/
function calcNURBSDerivatives( p, U, P, u, nd ) {
const Pders = calcBSplineDerivatives( p, U, P, u, nd );
return calcRationalCurveDerivatives( Pders );
}
/*
Calculate rational B-Spline surface point. See The NURBS Book, page 134, algorithm A4.3.
p, q : degrees of B-Spline surface
U, V : knot vectors
P : control points (x, y, z, w)
u, v : parametric values
returns point for given (u, v)
*/
function calcSurfacePoint( p, q, U, V, P, u, v, target ) {
const uspan = findSpan( p, u, U );
const vspan = findSpan( q, v, V );
const Nu = calcBasisFunctions( uspan, u, p, U );
const Nv = calcBasisFunctions( vspan, v, q, V );
const temp = [];
for ( let l = 0; l <= q; ++ l ) {
temp[ l ] = new Vector4( 0, 0, 0, 0 );
for ( let k = 0; k <= p; ++ k ) {
const point = P[ uspan - p + k ][ vspan - q + l ].clone();
const w = point.w;
point.x *= w;
point.y *= w;
point.z *= w;
temp[ l ].add( point.multiplyScalar( Nu[ k ] ) );
}
}
const Sw = new Vector4( 0, 0, 0, 0 );
for ( let l = 0; l <= q; ++ l ) {
Sw.add( temp[ l ].multiplyScalar( Nv[ l ] ) );
}
Sw.divideScalar( Sw.w );
target.set( Sw.x, Sw.y, Sw.z );
}
/*
Calculate rational B-Spline volume point. See The NURBS Book, page 134, algorithm A4.3.
p, q, r : degrees of B-Splinevolume
U, V, W : knot vectors
P : control points (x, y, z, w)
u, v, w : parametric values
returns point for given (u, v, w)
*/
function calcVolumePoint( p, q, r, U, V, W, P, u, v, w, target ) {
const uspan = findSpan( p, u, U );
const vspan = findSpan( q, v, V );
const wspan = findSpan( r, w, W );
const Nu = calcBasisFunctions( uspan, u, p, U );
const Nv = calcBasisFunctions( vspan, v, q, V );
const Nw = calcBasisFunctions( wspan, w, r, W );
const temp = [];
for ( let m = 0; m <= r; ++ m ) {
temp[ m ] = [];
for ( let l = 0; l <= q; ++ l ) {
temp[ m ][ l ] = new Vector4( 0, 0, 0, 0 );
for ( let k = 0; k <= p; ++ k ) {
const point = P[ uspan - p + k ][ vspan - q + l ][ wspan - r + m ].clone();
const w = point.w;
point.x *= w;
point.y *= w;
point.z *= w;
temp[ m ][ l ].add( point.multiplyScalar( Nu[ k ] ) );
}
}
}
const Sw = new Vector4( 0, 0, 0, 0 );
for ( let m = 0; m <= r; ++ m ) {
for ( let l = 0; l <= q; ++ l ) {
Sw.add( temp[ m ][ l ].multiplyScalar( Nw[ m ] ).multiplyScalar( Nv[ l ] ) );
}
}
Sw.divideScalar( Sw.w );
target.set( Sw.x, Sw.y, Sw.z );
}
export {
findSpan,
calcBasisFunctions,
calcBSplinePoint,
calcBasisFunctionDerivatives,
calcBSplineDerivatives,
calcKoverI,
calcRationalCurveDerivatives,
calcNURBSDerivatives,
calcSurfacePoint,
calcVolumePoint,
};

View File

@ -0,0 +1,62 @@
import {
Vector4
} from 'three';
import * as NURBSUtils from '../curves/NURBSUtils.js';
/**
* NURBS volume object
*
* Implementation is based on (x, y, z [, w=1]]) control points with w=weight.
**/
class NURBSVolume {
constructor( degree1, degree2, degree3, knots1, knots2, knots3 /* arrays of reals */, controlPoints /* array^3 of Vector(2|3|4) */ ) {
this.degree1 = degree1;
this.degree2 = degree2;
this.degree3 = degree3;
this.knots1 = knots1;
this.knots2 = knots2;
this.knots3 = knots3;
this.controlPoints = [];
const len1 = knots1.length - degree1 - 1;
const len2 = knots2.length - degree2 - 1;
const len3 = knots3.length - degree3 - 1;
// ensure Vector4 for control points
for ( let i = 0; i < len1; ++ i ) {
this.controlPoints[ i ] = [];
for ( let j = 0; j < len2; ++ j ) {
this.controlPoints[ i ][ j ] = [];
for ( let k = 0; k < len3; ++ k ) {
const point = controlPoints[ i ][ j ][ k ];
this.controlPoints[ i ][ j ][ k ] = new Vector4( point.x, point.y, point.z, point.w );
}
}
}
}
getPoint( t1, t2, t3, target ) {
const u = this.knots1[ 0 ] + t1 * ( this.knots1[ this.knots1.length - 1 ] - this.knots1[ 0 ] ); // linear mapping t1->u
const v = this.knots2[ 0 ] + t2 * ( this.knots2[ this.knots2.length - 1 ] - this.knots2[ 0 ] ); // linear mapping t2->v
const w = this.knots3[ 0 ] + t3 * ( this.knots3[ this.knots3.length - 1 ] - this.knots3[ 0 ] ); // linear mapping t3->w
NURBSUtils.calcVolumePoint( this.degree1, this.degree2, this.degree3, this.knots1, this.knots2, this.knots3, this.controlPoints, u, v, w, target );
}
}
export { NURBSVolume };

View File

@ -0,0 +1,154 @@
import {
LinearFilter,
Matrix3,
Mesh,
NearestFilter,
OrthographicCamera,
PlaneGeometry,
RGBAFormat,
Scene,
ShaderMaterial,
StereoCamera,
WebGLRenderTarget
} from 'three';
class AnaglyphEffect {
constructor( renderer, width = 512, height = 512 ) {
// Dubois matrices from https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.7.6968&rep=rep1&type=pdf#page=4
this.colorMatrixLeft = new Matrix3().fromArray( [
0.456100, - 0.0400822, - 0.0152161,
0.500484, - 0.0378246, - 0.0205971,
0.176381, - 0.0157589, - 0.00546856
] );
this.colorMatrixRight = new Matrix3().fromArray( [
- 0.0434706, 0.378476, - 0.0721527,
- 0.0879388, 0.73364, - 0.112961,
- 0.00155529, - 0.0184503, 1.2264
] );
const _camera = new OrthographicCamera( - 1, 1, 1, - 1, 0, 1 );
const _scene = new Scene();
const _stereo = new StereoCamera();
const _params = { minFilter: LinearFilter, magFilter: NearestFilter, format: RGBAFormat };
const _renderTargetL = new WebGLRenderTarget( width, height, _params );
const _renderTargetR = new WebGLRenderTarget( width, height, _params );
const _material = new ShaderMaterial( {
uniforms: {
'mapLeft': { value: _renderTargetL.texture },
'mapRight': { value: _renderTargetR.texture },
'colorMatrixLeft': { value: this.colorMatrixLeft },
'colorMatrixRight': { value: this.colorMatrixRight }
},
vertexShader: [
'varying vec2 vUv;',
'void main() {',
' vUv = vec2( uv.x, uv.y );',
' gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',
'}'
].join( '\n' ),
fragmentShader: [
'uniform sampler2D mapLeft;',
'uniform sampler2D mapRight;',
'varying vec2 vUv;',
'uniform mat3 colorMatrixLeft;',
'uniform mat3 colorMatrixRight;',
'void main() {',
' vec2 uv = vUv;',
' vec4 colorL = texture2D( mapLeft, uv );',
' vec4 colorR = texture2D( mapRight, uv );',
' vec3 color = clamp(',
' colorMatrixLeft * colorL.rgb +',
' colorMatrixRight * colorR.rgb, 0., 1. );',
' gl_FragColor = vec4(',
' color.r, color.g, color.b,',
' max( colorL.a, colorR.a ) );',
' #include <tonemapping_fragment>',
' #include <colorspace_fragment>',
'}'
].join( '\n' )
} );
const _mesh = new Mesh( new PlaneGeometry( 2, 2 ), _material );
_scene.add( _mesh );
this.setSize = function ( width, height ) {
renderer.setSize( width, height );
const pixelRatio = renderer.getPixelRatio();
_renderTargetL.setSize( width * pixelRatio, height * pixelRatio );
_renderTargetR.setSize( width * pixelRatio, height * pixelRatio );
};
this.render = function ( scene, camera ) {
const currentRenderTarget = renderer.getRenderTarget();
if ( scene.matrixWorldAutoUpdate === true ) scene.updateMatrixWorld();
if ( camera.parent === null && camera.matrixWorldAutoUpdate === true ) camera.updateMatrixWorld();
_stereo.update( camera );
renderer.setRenderTarget( _renderTargetL );
renderer.clear();
renderer.render( scene, _stereo.cameraL );
renderer.setRenderTarget( _renderTargetR );
renderer.clear();
renderer.render( scene, _stereo.cameraR );
renderer.setRenderTarget( null );
renderer.render( _scene, _camera );
renderer.setRenderTarget( currentRenderTarget );
};
this.dispose = function () {
_renderTargetL.dispose();
_renderTargetR.dispose();
_mesh.geometry.dispose();
_mesh.material.dispose();
};
}
}
export { AnaglyphEffect };

View File

@ -0,0 +1,263 @@
/**
* Ascii generation is based on https://github.com/hassadee/jsascii/blob/master/jsascii.js
*
* 16 April 2012 - @blurspline
*/
class AsciiEffect {
constructor( renderer, charSet = ' .:-=+*#%@', options = {} ) {
// ' .,:;=|iI+hHOE#`$';
// darker bolder character set from https://github.com/saw/Canvas-ASCII-Art/
// ' .\'`^",:;Il!i~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$'.split('');
// Some ASCII settings
const fResolution = options[ 'resolution' ] || 0.15; // Higher for more details
const iScale = options[ 'scale' ] || 1;
const bColor = options[ 'color' ] || false; // nice but slows down rendering!
const bAlpha = options[ 'alpha' ] || false; // Transparency
const bBlock = options[ 'block' ] || false; // blocked characters. like good O dos
const bInvert = options[ 'invert' ] || false; // black is white, white is black
const strResolution = options[ 'strResolution' ] || 'low';
let width, height;
const domElement = document.createElement( 'div' );
domElement.style.cursor = 'default';
const oAscii = document.createElement( 'table' );
domElement.appendChild( oAscii );
let iWidth, iHeight;
let oImg;
this.setSize = function ( w, h ) {
width = w;
height = h;
renderer.setSize( w, h );
initAsciiSize();
};
this.render = function ( scene, camera ) {
renderer.render( scene, camera );
asciifyImage( oAscii );
};
this.domElement = domElement;
// Throw in ascii library from https://github.com/hassadee/jsascii/blob/master/jsascii.js (MIT License)
function initAsciiSize() {
iWidth = Math.floor( width * fResolution );
iHeight = Math.floor( height * fResolution );
oCanvas.width = iWidth;
oCanvas.height = iHeight;
// oCanvas.style.display = "none";
// oCanvas.style.width = iWidth;
// oCanvas.style.height = iHeight;
oImg = renderer.domElement;
if ( oImg.style.backgroundColor ) {
oAscii.rows[ 0 ].cells[ 0 ].style.backgroundColor = oImg.style.backgroundColor;
oAscii.rows[ 0 ].cells[ 0 ].style.color = oImg.style.color;
}
oAscii.cellSpacing = 0;
oAscii.cellPadding = 0;
const oStyle = oAscii.style;
oStyle.whiteSpace = 'pre';
oStyle.margin = '0px';
oStyle.padding = '0px';
oStyle.letterSpacing = fLetterSpacing + 'px';
oStyle.fontFamily = strFont;
oStyle.fontSize = fFontSize + 'px';
oStyle.lineHeight = fLineHeight + 'px';
oStyle.textAlign = 'left';
oStyle.textDecoration = 'none';
}
const aDefaultCharList = ( ' .,:;i1tfLCG08@' ).split( '' );
const aDefaultColorCharList = ( ' CGO08@' ).split( '' );
const strFont = 'courier new, monospace';
const oCanvasImg = renderer.domElement;
const oCanvas = document.createElement( 'canvas' );
if ( ! oCanvas.getContext ) {
return;
}
const oCtx = oCanvas.getContext( '2d' );
if ( ! oCtx.getImageData ) {
return;
}
let aCharList = ( bColor ? aDefaultColorCharList : aDefaultCharList );
if ( charSet ) aCharList = charSet;
// Setup dom
const fFontSize = ( 2 / fResolution ) * iScale;
const fLineHeight = ( 2 / fResolution ) * iScale;
// adjust letter-spacing for all combinations of scale and resolution to get it to fit the image width.
let fLetterSpacing = 0;
if ( strResolution == 'low' ) {
switch ( iScale ) {
case 1 : fLetterSpacing = - 1; break;
case 2 :
case 3 : fLetterSpacing = - 2.1; break;
case 4 : fLetterSpacing = - 3.1; break;
case 5 : fLetterSpacing = - 4.15; break;
}
}
if ( strResolution == 'medium' ) {
switch ( iScale ) {
case 1 : fLetterSpacing = 0; break;
case 2 : fLetterSpacing = - 1; break;
case 3 : fLetterSpacing = - 1.04; break;
case 4 :
case 5 : fLetterSpacing = - 2.1; break;
}
}
if ( strResolution == 'high' ) {
switch ( iScale ) {
case 1 :
case 2 : fLetterSpacing = 0; break;
case 3 :
case 4 :
case 5 : fLetterSpacing = - 1; break;
}
}
// can't get a span or div to flow like an img element, but a table works?
// convert img element to ascii
function asciifyImage( oAscii ) {
oCtx.clearRect( 0, 0, iWidth, iHeight );
oCtx.drawImage( oCanvasImg, 0, 0, iWidth, iHeight );
const oImgData = oCtx.getImageData( 0, 0, iWidth, iHeight ).data;
// Coloring loop starts now
let strChars = '';
// console.time('rendering');
for ( let y = 0; y < iHeight; y += 2 ) {
for ( let x = 0; x < iWidth; x ++ ) {
const iOffset = ( y * iWidth + x ) * 4;
const iRed = oImgData[ iOffset ];
const iGreen = oImgData[ iOffset + 1 ];
const iBlue = oImgData[ iOffset + 2 ];
const iAlpha = oImgData[ iOffset + 3 ];
let iCharIdx;
let fBrightness;
fBrightness = ( 0.3 * iRed + 0.59 * iGreen + 0.11 * iBlue ) / 255;
// fBrightness = (0.3*iRed + 0.5*iGreen + 0.3*iBlue) / 255;
if ( iAlpha == 0 ) {
// should calculate alpha instead, but quick hack :)
//fBrightness *= (iAlpha / 255);
fBrightness = 1;
}
iCharIdx = Math.floor( ( 1 - fBrightness ) * ( aCharList.length - 1 ) );
if ( bInvert ) {
iCharIdx = aCharList.length - iCharIdx - 1;
}
// good for debugging
//fBrightness = Math.floor(fBrightness * 10);
//strThisChar = fBrightness;
let strThisChar = aCharList[ iCharIdx ];
if ( strThisChar === undefined || strThisChar == ' ' )
strThisChar = '&nbsp;';
if ( bColor ) {
strChars += '<span style=\''
+ 'color:rgb(' + iRed + ',' + iGreen + ',' + iBlue + ');'
+ ( bBlock ? 'background-color:rgb(' + iRed + ',' + iGreen + ',' + iBlue + ');' : '' )
+ ( bAlpha ? 'opacity:' + ( iAlpha / 255 ) + ';' : '' )
+ '\'>' + strThisChar + '</span>';
} else {
strChars += strThisChar;
}
}
strChars += '<br/>';
}
oAscii.innerHTML = `<tr><td style="display:block;width:${width}px;height:${height}px;overflow:hidden">${strChars}</td></tr>`;
// console.timeEnd('rendering');
// return oAscii;
}
}
}
export { AsciiEffect };

View File

@ -0,0 +1,539 @@
import {
BackSide,
Color,
ShaderMaterial,
UniformsLib,
UniformsUtils
} from 'three';
/**
* Reference: https://en.wikipedia.org/wiki/Cel_shading
*
* API
*
* 1. Traditional
*
* const effect = new OutlineEffect( renderer );
*
* function render() {
*
* effect.render( scene, camera );
*
* }
*
* 2. VR compatible
*
* const effect = new OutlineEffect( renderer );
* let renderingOutline = false;
*
* scene.onAfterRender = function () {
*
* if ( renderingOutline ) return;
*
* renderingOutline = true;
*
* effect.renderOutline( scene, camera );
*
* renderingOutline = false;
*
* };
*
* function render() {
*
* renderer.render( scene, camera );
*
* }
*
* // How to set default outline parameters
* new OutlineEffect( renderer, {
* defaultThickness: 0.01,
* defaultColor: [ 0, 0, 0 ],
* defaultAlpha: 0.8,
* defaultKeepAlive: true // keeps outline material in cache even if material is removed from scene
* } );
*
* // How to set outline parameters for each material
* material.userData.outlineParameters = {
* thickness: 0.01,
* color: [ 0, 0, 0 ],
* alpha: 0.8,
* visible: true,
* keepAlive: true
* };
*/
class OutlineEffect {
constructor( renderer, parameters = {} ) {
this.enabled = true;
const defaultThickness = parameters.defaultThickness !== undefined ? parameters.defaultThickness : 0.003;
const defaultColor = new Color().fromArray( parameters.defaultColor !== undefined ? parameters.defaultColor : [ 0, 0, 0 ] );
const defaultAlpha = parameters.defaultAlpha !== undefined ? parameters.defaultAlpha : 1.0;
const defaultKeepAlive = parameters.defaultKeepAlive !== undefined ? parameters.defaultKeepAlive : false;
// object.material.uuid -> outlineMaterial or
// object.material[ n ].uuid -> outlineMaterial
// save at the outline material creation and release
// if it's unused removeThresholdCount frames
// unless keepAlive is true.
const cache = {};
const removeThresholdCount = 60;
// outlineMaterial.uuid -> object.material or
// outlineMaterial.uuid -> object.material[ n ]
// save before render and release after render.
const originalMaterials = {};
// object.uuid -> originalOnBeforeRender
// save before render and release after render.
const originalOnBeforeRenders = {};
//this.cache = cache; // for debug
const uniformsOutline = {
outlineThickness: { value: defaultThickness },
outlineColor: { value: defaultColor },
outlineAlpha: { value: defaultAlpha }
};
const vertexShader = [
'#include <common>',
'#include <uv_pars_vertex>',
'#include <displacementmap_pars_vertex>',
'#include <fog_pars_vertex>',
'#include <morphtarget_pars_vertex>',
'#include <skinning_pars_vertex>',
'#include <logdepthbuf_pars_vertex>',
'#include <clipping_planes_pars_vertex>',
'uniform float outlineThickness;',
'vec4 calculateOutline( vec4 pos, vec3 normal, vec4 skinned ) {',
' float thickness = outlineThickness;',
' const float ratio = 1.0;', // TODO: support outline thickness ratio for each vertex
' vec4 pos2 = projectionMatrix * modelViewMatrix * vec4( skinned.xyz + normal, 1.0 );',
// NOTE: subtract pos2 from pos because BackSide objectNormal is negative
' vec4 norm = normalize( pos - pos2 );',
' return pos + norm * thickness * pos.w * ratio;',
'}',
'void main() {',
' #include <uv_vertex>',
' #include <beginnormal_vertex>',
' #include <morphnormal_vertex>',
' #include <skinbase_vertex>',
' #include <skinnormal_vertex>',
' #include <begin_vertex>',
' #include <morphtarget_vertex>',
' #include <skinning_vertex>',
' #include <displacementmap_vertex>',
' #include <project_vertex>',
' vec3 outlineNormal = - objectNormal;', // the outline material is always rendered with BackSide
' gl_Position = calculateOutline( gl_Position, outlineNormal, vec4( transformed, 1.0 ) );',
' #include <logdepthbuf_vertex>',
' #include <clipping_planes_vertex>',
' #include <fog_vertex>',
'}',
].join( '\n' );
const fragmentShader = [
'#include <common>',
'#include <fog_pars_fragment>',
'#include <logdepthbuf_pars_fragment>',
'#include <clipping_planes_pars_fragment>',
'uniform vec3 outlineColor;',
'uniform float outlineAlpha;',
'void main() {',
' #include <clipping_planes_fragment>',
' #include <logdepthbuf_fragment>',
' gl_FragColor = vec4( outlineColor, outlineAlpha );',
' #include <tonemapping_fragment>',
' #include <colorspace_fragment>',
' #include <fog_fragment>',
' #include <premultiplied_alpha_fragment>',
'}'
].join( '\n' );
function createMaterial() {
return new ShaderMaterial( {
type: 'OutlineEffect',
uniforms: UniformsUtils.merge( [
UniformsLib[ 'fog' ],
UniformsLib[ 'displacementmap' ],
uniformsOutline
] ),
vertexShader: vertexShader,
fragmentShader: fragmentShader,
side: BackSide
} );
}
function getOutlineMaterialFromCache( originalMaterial ) {
let data = cache[ originalMaterial.uuid ];
if ( data === undefined ) {
data = {
material: createMaterial(),
used: true,
keepAlive: defaultKeepAlive,
count: 0
};
cache[ originalMaterial.uuid ] = data;
}
data.used = true;
return data.material;
}
function getOutlineMaterial( originalMaterial ) {
const outlineMaterial = getOutlineMaterialFromCache( originalMaterial );
originalMaterials[ outlineMaterial.uuid ] = originalMaterial;
updateOutlineMaterial( outlineMaterial, originalMaterial );
return outlineMaterial;
}
function isCompatible( object ) {
const geometry = object.geometry;
const hasNormals = ( geometry !== undefined ) && ( geometry.attributes.normal !== undefined );
return ( object.isMesh === true && object.material !== undefined && hasNormals === true );
}
function setOutlineMaterial( object ) {
if ( isCompatible( object ) === false ) return;
if ( Array.isArray( object.material ) ) {
for ( let i = 0, il = object.material.length; i < il; i ++ ) {
object.material[ i ] = getOutlineMaterial( object.material[ i ] );
}
} else {
object.material = getOutlineMaterial( object.material );
}
originalOnBeforeRenders[ object.uuid ] = object.onBeforeRender;
object.onBeforeRender = onBeforeRender;
}
function restoreOriginalMaterial( object ) {
if ( isCompatible( object ) === false ) return;
if ( Array.isArray( object.material ) ) {
for ( let i = 0, il = object.material.length; i < il; i ++ ) {
object.material[ i ] = originalMaterials[ object.material[ i ].uuid ];
}
} else {
object.material = originalMaterials[ object.material.uuid ];
}
object.onBeforeRender = originalOnBeforeRenders[ object.uuid ];
}
function onBeforeRender( renderer, scene, camera, geometry, material ) {
const originalMaterial = originalMaterials[ material.uuid ];
// just in case
if ( originalMaterial === undefined ) return;
updateUniforms( material, originalMaterial );
}
function updateUniforms( material, originalMaterial ) {
const outlineParameters = originalMaterial.userData.outlineParameters;
material.uniforms.outlineAlpha.value = originalMaterial.opacity;
if ( outlineParameters !== undefined ) {
if ( outlineParameters.thickness !== undefined ) material.uniforms.outlineThickness.value = outlineParameters.thickness;
if ( outlineParameters.color !== undefined ) material.uniforms.outlineColor.value.fromArray( outlineParameters.color );
if ( outlineParameters.alpha !== undefined ) material.uniforms.outlineAlpha.value = outlineParameters.alpha;
}
if ( originalMaterial.displacementMap ) {
material.uniforms.displacementMap.value = originalMaterial.displacementMap;
material.uniforms.displacementScale.value = originalMaterial.displacementScale;
material.uniforms.displacementBias.value = originalMaterial.displacementBias;
}
}
function updateOutlineMaterial( material, originalMaterial ) {
if ( material.name === 'invisible' ) return;
const outlineParameters = originalMaterial.userData.outlineParameters;
material.fog = originalMaterial.fog;
material.toneMapped = originalMaterial.toneMapped;
material.premultipliedAlpha = originalMaterial.premultipliedAlpha;
material.displacementMap = originalMaterial.displacementMap;
if ( outlineParameters !== undefined ) {
if ( originalMaterial.visible === false ) {
material.visible = false;
} else {
material.visible = ( outlineParameters.visible !== undefined ) ? outlineParameters.visible : true;
}
material.transparent = ( outlineParameters.alpha !== undefined && outlineParameters.alpha < 1.0 ) ? true : originalMaterial.transparent;
if ( outlineParameters.keepAlive !== undefined ) cache[ originalMaterial.uuid ].keepAlive = outlineParameters.keepAlive;
} else {
material.transparent = originalMaterial.transparent;
material.visible = originalMaterial.visible;
}
if ( originalMaterial.wireframe === true || originalMaterial.depthTest === false ) material.visible = false;
if ( originalMaterial.clippingPlanes ) {
material.clipping = true;
material.clippingPlanes = originalMaterial.clippingPlanes;
material.clipIntersection = originalMaterial.clipIntersection;
material.clipShadows = originalMaterial.clipShadows;
}
material.version = originalMaterial.version; // update outline material if necessary
}
function cleanupCache() {
let keys;
// clear originialMaterials
keys = Object.keys( originalMaterials );
for ( let i = 0, il = keys.length; i < il; i ++ ) {
originalMaterials[ keys[ i ] ] = undefined;
}
// clear originalOnBeforeRenders
keys = Object.keys( originalOnBeforeRenders );
for ( let i = 0, il = keys.length; i < il; i ++ ) {
originalOnBeforeRenders[ keys[ i ] ] = undefined;
}
// remove unused outlineMaterial from cache
keys = Object.keys( cache );
for ( let i = 0, il = keys.length; i < il; i ++ ) {
const key = keys[ i ];
if ( cache[ key ].used === false ) {
cache[ key ].count ++;
if ( cache[ key ].keepAlive === false && cache[ key ].count > removeThresholdCount ) {
delete cache[ key ];
}
} else {
cache[ key ].used = false;
cache[ key ].count = 0;
}
}
}
this.render = function ( scene, camera ) {
if ( this.enabled === false ) {
renderer.render( scene, camera );
return;
}
const currentAutoClear = renderer.autoClear;
renderer.autoClear = this.autoClear;
renderer.render( scene, camera );
renderer.autoClear = currentAutoClear;
this.renderOutline( scene, camera );
};
this.renderOutline = function ( scene, camera ) {
const currentAutoClear = renderer.autoClear;
const currentSceneAutoUpdate = scene.matrixWorldAutoUpdate;
const currentSceneBackground = scene.background;
const currentShadowMapEnabled = renderer.shadowMap.enabled;
scene.matrixWorldAutoUpdate = false;
scene.background = null;
renderer.autoClear = false;
renderer.shadowMap.enabled = false;
scene.traverse( setOutlineMaterial );
renderer.render( scene, camera );
scene.traverse( restoreOriginalMaterial );
cleanupCache();
scene.matrixWorldAutoUpdate = currentSceneAutoUpdate;
scene.background = currentSceneBackground;
renderer.autoClear = currentAutoClear;
renderer.shadowMap.enabled = currentShadowMapEnabled;
};
/*
* See #9918
*
* The following property copies and wrapper methods enable
* OutlineEffect to be called from other *Effect, like
*
* effect = new StereoEffect( new OutlineEffect( renderer ) );
*
* function render () {
*
* effect.render( scene, camera );
*
* }
*/
this.autoClear = renderer.autoClear;
this.domElement = renderer.domElement;
this.shadowMap = renderer.shadowMap;
this.clear = function ( color, depth, stencil ) {
renderer.clear( color, depth, stencil );
};
this.getPixelRatio = function () {
return renderer.getPixelRatio();
};
this.setPixelRatio = function ( value ) {
renderer.setPixelRatio( value );
};
this.getSize = function ( target ) {
return renderer.getSize( target );
};
this.setSize = function ( width, height, updateStyle ) {
renderer.setSize( width, height, updateStyle );
};
this.setViewport = function ( x, y, width, height ) {
renderer.setViewport( x, y, width, height );
};
this.setScissor = function ( x, y, width, height ) {
renderer.setScissor( x, y, width, height );
};
this.setScissorTest = function ( boolean ) {
renderer.setScissorTest( boolean );
};
this.setRenderTarget = function ( renderTarget ) {
renderer.setRenderTarget( renderTarget );
};
}
}
export { OutlineEffect };

View File

@ -0,0 +1,119 @@
import {
LinearFilter,
Mesh,
NearestFilter,
OrthographicCamera,
PlaneGeometry,
RGBAFormat,
Scene,
ShaderMaterial,
StereoCamera,
WebGLRenderTarget
} from 'three';
class ParallaxBarrierEffect {
constructor( renderer ) {
const _camera = new OrthographicCamera( - 1, 1, 1, - 1, 0, 1 );
const _scene = new Scene();
const _stereo = new StereoCamera();
const _params = { minFilter: LinearFilter, magFilter: NearestFilter, format: RGBAFormat };
const _renderTargetL = new WebGLRenderTarget( 512, 512, _params );
const _renderTargetR = new WebGLRenderTarget( 512, 512, _params );
const _material = new ShaderMaterial( {
uniforms: {
'mapLeft': { value: _renderTargetL.texture },
'mapRight': { value: _renderTargetR.texture }
},
vertexShader: [
'varying vec2 vUv;',
'void main() {',
' vUv = vec2( uv.x, uv.y );',
' gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',
'}'
].join( '\n' ),
fragmentShader: [
'uniform sampler2D mapLeft;',
'uniform sampler2D mapRight;',
'varying vec2 vUv;',
'void main() {',
' vec2 uv = vUv;',
' if ( ( mod( gl_FragCoord.y, 2.0 ) ) > 1.00 ) {',
' gl_FragColor = texture2D( mapLeft, uv );',
' } else {',
' gl_FragColor = texture2D( mapRight, uv );',
' }',
' #include <tonemapping_fragment>',
' #include <colorspace_fragment>',
'}'
].join( '\n' )
} );
const mesh = new Mesh( new PlaneGeometry( 2, 2 ), _material );
_scene.add( mesh );
this.setSize = function ( width, height ) {
renderer.setSize( width, height );
const pixelRatio = renderer.getPixelRatio();
_renderTargetL.setSize( width * pixelRatio, height * pixelRatio );
_renderTargetR.setSize( width * pixelRatio, height * pixelRatio );
};
this.render = function ( scene, camera ) {
if ( scene.matrixWorldAutoUpdate === true ) scene.updateMatrixWorld();
if ( camera.parent === null && camera.matrixWorldAutoUpdate === true ) camera.updateMatrixWorld();
_stereo.update( camera );
renderer.setRenderTarget( _renderTargetL );
renderer.clear();
renderer.render( scene, _stereo.cameraL );
renderer.setRenderTarget( _renderTargetR );
renderer.clear();
renderer.render( scene, _stereo.cameraR );
renderer.setRenderTarget( null );
renderer.render( _scene, _camera );
};
}
}
export { ParallaxBarrierEffect };

View File

@ -0,0 +1,153 @@
import {
PerspectiveCamera,
Quaternion,
Vector3
} from 'three';
/**
* peppers ghost effect based on http://www.instructables.com/id/Reflective-Prism/?ALLSTEPS
*/
class PeppersGhostEffect {
constructor( renderer ) {
const scope = this;
scope.cameraDistance = 15;
scope.reflectFromAbove = false;
// Internals
let _halfWidth, _width, _height;
const _cameraF = new PerspectiveCamera(); //front
const _cameraB = new PerspectiveCamera(); //back
const _cameraL = new PerspectiveCamera(); //left
const _cameraR = new PerspectiveCamera(); //right
const _position = new Vector3();
const _quaternion = new Quaternion();
const _scale = new Vector3();
// Initialization
renderer.autoClear = false;
this.setSize = function ( width, height ) {
_halfWidth = width / 2;
if ( width < height ) {
_width = width / 3;
_height = width / 3;
} else {
_width = height / 3;
_height = height / 3;
}
renderer.setSize( width, height );
};
this.render = function ( scene, camera ) {
if ( scene.matrixWorldAutoUpdate === true ) scene.updateMatrixWorld();
if ( camera.parent === null && camera.matrixWorldAutoUpdate === true ) camera.updateMatrixWorld();
camera.matrixWorld.decompose( _position, _quaternion, _scale );
// front
_cameraF.position.copy( _position );
_cameraF.quaternion.copy( _quaternion );
_cameraF.translateZ( scope.cameraDistance );
_cameraF.lookAt( scene.position );
// back
_cameraB.position.copy( _position );
_cameraB.quaternion.copy( _quaternion );
_cameraB.translateZ( - ( scope.cameraDistance ) );
_cameraB.lookAt( scene.position );
_cameraB.rotation.z += 180 * ( Math.PI / 180 );
// left
_cameraL.position.copy( _position );
_cameraL.quaternion.copy( _quaternion );
_cameraL.translateX( - ( scope.cameraDistance ) );
_cameraL.lookAt( scene.position );
_cameraL.rotation.x += 90 * ( Math.PI / 180 );
// right
_cameraR.position.copy( _position );
_cameraR.quaternion.copy( _quaternion );
_cameraR.translateX( scope.cameraDistance );
_cameraR.lookAt( scene.position );
_cameraR.rotation.x += 90 * ( Math.PI / 180 );
renderer.clear();
renderer.setScissorTest( true );
renderer.setScissor( _halfWidth - ( _width / 2 ), ( _height * 2 ), _width, _height );
renderer.setViewport( _halfWidth - ( _width / 2 ), ( _height * 2 ), _width, _height );
if ( scope.reflectFromAbove ) {
renderer.render( scene, _cameraB );
} else {
renderer.render( scene, _cameraF );
}
renderer.setScissor( _halfWidth - ( _width / 2 ), 0, _width, _height );
renderer.setViewport( _halfWidth - ( _width / 2 ), 0, _width, _height );
if ( scope.reflectFromAbove ) {
renderer.render( scene, _cameraF );
} else {
renderer.render( scene, _cameraB );
}
renderer.setScissor( _halfWidth - ( _width / 2 ) - _width, _height, _width, _height );
renderer.setViewport( _halfWidth - ( _width / 2 ) - _width, _height, _width, _height );
if ( scope.reflectFromAbove ) {
renderer.render( scene, _cameraR );
} else {
renderer.render( scene, _cameraL );
}
renderer.setScissor( _halfWidth + ( _width / 2 ), _height, _width, _height );
renderer.setViewport( _halfWidth + ( _width / 2 ), _height, _width, _height );
if ( scope.reflectFromAbove ) {
renderer.render( scene, _cameraL );
} else {
renderer.render( scene, _cameraR );
}
renderer.setScissorTest( false );
};
}
}
export { PeppersGhostEffect };

View File

@ -0,0 +1,55 @@
import {
StereoCamera,
Vector2
} from 'three';
class StereoEffect {
constructor( renderer ) {
const _stereo = new StereoCamera();
_stereo.aspect = 0.5;
const size = new Vector2();
this.setEyeSeparation = function ( eyeSep ) {
_stereo.eyeSep = eyeSep;
};
this.setSize = function ( width, height ) {
renderer.setSize( width, height );
};
this.render = function ( scene, camera ) {
if ( scene.matrixWorldAutoUpdate === true ) scene.updateMatrixWorld();
if ( camera.parent === null && camera.matrixWorldAutoUpdate === true ) camera.updateMatrixWorld();
_stereo.update( camera );
renderer.getSize( size );
if ( renderer.autoClear ) renderer.clear();
renderer.setScissorTest( true );
renderer.setScissor( 0, 0, size.width / 2, size.height );
renderer.setViewport( 0, 0, size.width / 2, size.height );
renderer.render( scene, _stereo.cameraL );
renderer.setScissor( size.width / 2, 0, size.width / 2, size.height );
renderer.setViewport( size.width / 2, 0, size.width / 2, size.height );
renderer.render( scene, _stereo.cameraR );
renderer.setScissorTest( false );
};
}
}
export { StereoEffect };

View File

@ -0,0 +1,52 @@
import {
BackSide,
BoxGeometry,
Mesh,
MeshLambertMaterial,
MeshStandardMaterial,
PointLight,
Scene,
} from 'three';
class DebugEnvironment extends Scene {
constructor() {
super();
const geometry = new BoxGeometry();
geometry.deleteAttribute( 'uv' );
const roomMaterial = new MeshStandardMaterial( { metalness: 0, side: BackSide } );
const room = new Mesh( geometry, roomMaterial );
room.scale.setScalar( 10 );
this.add( room );
const mainLight = new PointLight( 0xffffff, 50, 0, 2 );
this.add( mainLight );
const material1 = new MeshLambertMaterial( { color: 0xff0000, emissive: 0xffffff, emissiveIntensity: 10 } );
const light1 = new Mesh( geometry, material1 );
light1.position.set( - 5, 2, 0 );
light1.scale.set( 0.1, 1, 1 );
this.add( light1 );
const material2 = new MeshLambertMaterial( { color: 0x00ff00, emissive: 0xffffff, emissiveIntensity: 10 } );
const light2 = new Mesh( geometry, material2 );
light2.position.set( 0, 5, 0 );
light2.scale.set( 1, 0.1, 1 );
this.add( light2 );
const material3 = new MeshLambertMaterial( { color: 0x0000ff, emissive: 0xffffff, emissiveIntensity: 10 } );
const light3 = new Mesh( geometry, material3 );
light3.position.set( 2, 1, 5 );
light3.scale.set( 1.5, 2, 0.1 );
this.add( light3 );
}
}
export { DebugEnvironment };

View File

@ -0,0 +1,148 @@
/**
* https://github.com/google/model-viewer/blob/master/packages/model-viewer/src/three-components/EnvironmentScene.ts
*/
import {
BackSide,
BoxGeometry,
Mesh,
MeshBasicMaterial,
MeshStandardMaterial,
PointLight,
Scene,
} from 'three';
class RoomEnvironment extends Scene {
constructor( renderer = null ) {
super();
const geometry = new BoxGeometry();
geometry.deleteAttribute( 'uv' );
const roomMaterial = new MeshStandardMaterial( { side: BackSide } );
const boxMaterial = new MeshStandardMaterial();
let intensity = 5;
if ( renderer !== null && renderer._useLegacyLights === false ) intensity = 900;
const mainLight = new PointLight( 0xffffff, intensity, 28, 2 );
mainLight.position.set( 0.418, 16.199, 0.300 );
this.add( mainLight );
const room = new Mesh( geometry, roomMaterial );
room.position.set( - 0.757, 13.219, 0.717 );
room.scale.set( 31.713, 28.305, 28.591 );
this.add( room );
const box1 = new Mesh( geometry, boxMaterial );
box1.position.set( - 10.906, 2.009, 1.846 );
box1.rotation.set( 0, - 0.195, 0 );
box1.scale.set( 2.328, 7.905, 4.651 );
this.add( box1 );
const box2 = new Mesh( geometry, boxMaterial );
box2.position.set( - 5.607, - 0.754, - 0.758 );
box2.rotation.set( 0, 0.994, 0 );
box2.scale.set( 1.970, 1.534, 3.955 );
this.add( box2 );
const box3 = new Mesh( geometry, boxMaterial );
box3.position.set( 6.167, 0.857, 7.803 );
box3.rotation.set( 0, 0.561, 0 );
box3.scale.set( 3.927, 6.285, 3.687 );
this.add( box3 );
const box4 = new Mesh( geometry, boxMaterial );
box4.position.set( - 2.017, 0.018, 6.124 );
box4.rotation.set( 0, 0.333, 0 );
box4.scale.set( 2.002, 4.566, 2.064 );
this.add( box4 );
const box5 = new Mesh( geometry, boxMaterial );
box5.position.set( 2.291, - 0.756, - 2.621 );
box5.rotation.set( 0, - 0.286, 0 );
box5.scale.set( 1.546, 1.552, 1.496 );
this.add( box5 );
const box6 = new Mesh( geometry, boxMaterial );
box6.position.set( - 2.193, - 0.369, - 5.547 );
box6.rotation.set( 0, 0.516, 0 );
box6.scale.set( 3.875, 3.487, 2.986 );
this.add( box6 );
// -x right
const light1 = new Mesh( geometry, createAreaLightMaterial( 50 ) );
light1.position.set( - 16.116, 14.37, 8.208 );
light1.scale.set( 0.1, 2.428, 2.739 );
this.add( light1 );
// -x left
const light2 = new Mesh( geometry, createAreaLightMaterial( 50 ) );
light2.position.set( - 16.109, 18.021, - 8.207 );
light2.scale.set( 0.1, 2.425, 2.751 );
this.add( light2 );
// +x
const light3 = new Mesh( geometry, createAreaLightMaterial( 17 ) );
light3.position.set( 14.904, 12.198, - 1.832 );
light3.scale.set( 0.15, 4.265, 6.331 );
this.add( light3 );
// +z
const light4 = new Mesh( geometry, createAreaLightMaterial( 43 ) );
light4.position.set( - 0.462, 8.89, 14.520 );
light4.scale.set( 4.38, 5.441, 0.088 );
this.add( light4 );
// -z
const light5 = new Mesh( geometry, createAreaLightMaterial( 20 ) );
light5.position.set( 3.235, 11.486, - 12.541 );
light5.scale.set( 2.5, 2.0, 0.1 );
this.add( light5 );
// +y
const light6 = new Mesh( geometry, createAreaLightMaterial( 100 ) );
light6.position.set( 0.0, 20.0, 0.0 );
light6.scale.set( 1.0, 0.1, 1.0 );
this.add( light6 );
}
dispose() {
const resources = new Set();
this.traverse( ( object ) => {
if ( object.isMesh ) {
resources.add( object.geometry );
resources.add( object.material );
}
} );
for ( const resource of resources ) {
resource.dispose();
}
}
}
function createAreaLightMaterial( intensity ) {
const material = new MeshBasicMaterial();
material.color.setScalar( intensity );
return material;
}
export { RoomEnvironment };

View File

@ -0,0 +1,267 @@
import { Color } from 'three';
/**
* Export draco compressed files from threejs geometry objects.
*
* Draco files are compressed and usually are smaller than conventional 3D file formats.
*
* The exporter receives a options object containing
* - decodeSpeed, indicates how to tune the encoder regarding decode speed (0 gives better speed but worst quality)
* - encodeSpeed, indicates how to tune the encoder parameters (0 gives better speed but worst quality)
* - encoderMethod
* - quantization, indicates the presision of each type of data stored in the draco file in the order (POSITION, NORMAL, COLOR, TEX_COORD, GENERIC)
* - exportUvs
* - exportNormals
* - exportColor
*/
/* global DracoEncoderModule */
class DRACOExporter {
parse( object, options = {} ) {
options = Object.assign( {
decodeSpeed: 5,
encodeSpeed: 5,
encoderMethod: DRACOExporter.MESH_EDGEBREAKER_ENCODING,
quantization: [ 16, 8, 8, 8, 8 ],
exportUvs: true,
exportNormals: true,
exportColor: false,
}, options );
if ( DracoEncoderModule === undefined ) {
throw new Error( 'THREE.DRACOExporter: required the draco_encoder to work.' );
}
const geometry = object.geometry;
const dracoEncoder = DracoEncoderModule();
const encoder = new dracoEncoder.Encoder();
let builder;
let dracoObject;
if ( object.isMesh === true ) {
builder = new dracoEncoder.MeshBuilder();
dracoObject = new dracoEncoder.Mesh();
const vertices = geometry.getAttribute( 'position' );
builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.POSITION, vertices.count, vertices.itemSize, vertices.array );
const faces = geometry.getIndex();
if ( faces !== null ) {
builder.AddFacesToMesh( dracoObject, faces.count / 3, faces.array );
} else {
const faces = new ( vertices.count > 65535 ? Uint32Array : Uint16Array )( vertices.count );
for ( let i = 0; i < faces.length; i ++ ) {
faces[ i ] = i;
}
builder.AddFacesToMesh( dracoObject, vertices.count, faces );
}
if ( options.exportNormals === true ) {
const normals = geometry.getAttribute( 'normal' );
if ( normals !== undefined ) {
builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.NORMAL, normals.count, normals.itemSize, normals.array );
}
}
if ( options.exportUvs === true ) {
const uvs = geometry.getAttribute( 'uv' );
if ( uvs !== undefined ) {
builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.TEX_COORD, uvs.count, uvs.itemSize, uvs.array );
}
}
if ( options.exportColor === true ) {
const colors = geometry.getAttribute( 'color' );
if ( colors !== undefined ) {
const array = createVertexColorSRGBArray( colors );
builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.COLOR, colors.count, colors.itemSize, array );
}
}
} else if ( object.isPoints === true ) {
builder = new dracoEncoder.PointCloudBuilder();
dracoObject = new dracoEncoder.PointCloud();
const vertices = geometry.getAttribute( 'position' );
builder.AddFloatAttribute( dracoObject, dracoEncoder.POSITION, vertices.count, vertices.itemSize, vertices.array );
if ( options.exportColor === true ) {
const colors = geometry.getAttribute( 'color' );
if ( colors !== undefined ) {
const array = createVertexColorSRGBArray( colors );
builder.AddFloatAttribute( dracoObject, dracoEncoder.COLOR, colors.count, colors.itemSize, array );
}
}
} else {
throw new Error( 'DRACOExporter: Unsupported object type.' );
}
//Compress using draco encoder
const encodedData = new dracoEncoder.DracoInt8Array();
//Sets the desired encoding and decoding speed for the given options from 0 (slowest speed, but the best compression) to 10 (fastest, but the worst compression).
const encodeSpeed = ( options.encodeSpeed !== undefined ) ? options.encodeSpeed : 5;
const decodeSpeed = ( options.decodeSpeed !== undefined ) ? options.decodeSpeed : 5;
encoder.SetSpeedOptions( encodeSpeed, decodeSpeed );
// Sets the desired encoding method for a given geometry.
if ( options.encoderMethod !== undefined ) {
encoder.SetEncodingMethod( options.encoderMethod );
}
// Sets the quantization (number of bits used to represent) compression options for a named attribute.
// The attribute values will be quantized in a box defined by the maximum extent of the attribute values.
if ( options.quantization !== undefined ) {
for ( let i = 0; i < 5; i ++ ) {
if ( options.quantization[ i ] !== undefined ) {
encoder.SetAttributeQuantization( i, options.quantization[ i ] );
}
}
}
let length;
if ( object.isMesh === true ) {
length = encoder.EncodeMeshToDracoBuffer( dracoObject, encodedData );
} else {
length = encoder.EncodePointCloudToDracoBuffer( dracoObject, true, encodedData );
}
dracoEncoder.destroy( dracoObject );
if ( length === 0 ) {
throw new Error( 'THREE.DRACOExporter: Draco encoding failed.' );
}
//Copy encoded data to buffer.
const outputData = new Int8Array( new ArrayBuffer( length ) );
for ( let i = 0; i < length; i ++ ) {
outputData[ i ] = encodedData.GetValue( i );
}
dracoEncoder.destroy( encodedData );
dracoEncoder.destroy( encoder );
dracoEncoder.destroy( builder );
return outputData;
}
}
function createVertexColorSRGBArray( attribute ) {
// While .drc files do not specify colorspace, the only 'official' tooling
// is PLY and OBJ converters, which use sRGB. We'll assume sRGB is expected
// for .drc files, but note that Draco buffers embedded in glTF files will
// be Linear-sRGB instead.
const _color = new Color();
const count = attribute.count;
const itemSize = attribute.itemSize;
const array = new Float32Array( count * itemSize );
for ( let i = 0, il = count; i < il; i ++ ) {
_color.fromBufferAttribute( attribute, i ).convertLinearToSRGB();
array[ i * itemSize ] = _color.r;
array[ i * itemSize + 1 ] = _color.g;
array[ i * itemSize + 2 ] = _color.b;
if ( itemSize === 4 ) {
array[ i * itemSize + 3 ] = attribute.getW( i );
}
}
return array;
}
// Encoder methods
DRACOExporter.MESH_EDGEBREAKER_ENCODING = 1;
DRACOExporter.MESH_SEQUENTIAL_ENCODING = 0;
// Geometry type
DRACOExporter.POINT_CLOUD = 0;
DRACOExporter.TRIANGULAR_MESH = 1;
// Attribute type
DRACOExporter.INVALID = - 1;
DRACOExporter.POSITION = 0;
DRACOExporter.NORMAL = 1;
DRACOExporter.COLOR = 2;
DRACOExporter.TEX_COORD = 3;
DRACOExporter.GENERIC = 4;
export { DRACOExporter };

View File

@ -0,0 +1,579 @@
/**
* @author sciecode / https://github.com/sciecode
*
* EXR format references:
* https://www.openexr.com/documentation/openexrfilelayout.pdf
*/
import {
FloatType,
HalfFloatType,
RGBAFormat,
DataUtils,
} from 'three';
import * as fflate from '../libs/fflate.module.js';
const textEncoder = new TextEncoder();
const NO_COMPRESSION = 0;
const ZIPS_COMPRESSION = 2;
const ZIP_COMPRESSION = 3;
class EXRExporter {
parse( arg1, arg2, arg3 ) {
if ( ! arg1 || ! ( arg1.isWebGLRenderer || arg1.isDataTexture ) ) {
throw Error( 'EXRExporter.parse: Unsupported first parameter, expected instance of WebGLRenderer or DataTexture.' );
} else if ( arg1.isWebGLRenderer ) {
const renderer = arg1, renderTarget = arg2, options = arg3;
supportedRTT( renderTarget );
const info = buildInfoRTT( renderTarget, options ),
dataBuffer = getPixelData( renderer, renderTarget, info ),
rawContentBuffer = reorganizeDataBuffer( dataBuffer, info ),
chunks = compressData( rawContentBuffer, info );
return fillData( chunks, info );
} else if ( arg1.isDataTexture ) {
const texture = arg1, options = arg2;
supportedDT( texture );
const info = buildInfoDT( texture, options ),
dataBuffer = texture.image.data,
rawContentBuffer = reorganizeDataBuffer( dataBuffer, info ),
chunks = compressData( rawContentBuffer, info );
return fillData( chunks, info );
}
}
}
function supportedRTT( renderTarget ) {
if ( ! renderTarget || ! renderTarget.isWebGLRenderTarget ) {
throw Error( 'EXRExporter.parse: Unsupported second parameter, expected instance of WebGLRenderTarget.' );
}
if ( renderTarget.isWebGLCubeRenderTarget || renderTarget.isWebGL3DRenderTarget || renderTarget.isWebGLArrayRenderTarget ) {
throw Error( 'EXRExporter.parse: Unsupported render target type, expected instance of WebGLRenderTarget.' );
}
if ( renderTarget.texture.type !== FloatType && renderTarget.texture.type !== HalfFloatType ) {
throw Error( 'EXRExporter.parse: Unsupported WebGLRenderTarget texture type.' );
}
if ( renderTarget.texture.format !== RGBAFormat ) {
throw Error( 'EXRExporter.parse: Unsupported WebGLRenderTarget texture format, expected RGBAFormat.' );
}
}
function supportedDT( texture ) {
if ( texture.type !== FloatType && texture.type !== HalfFloatType ) {
throw Error( 'EXRExporter.parse: Unsupported DataTexture texture type.' );
}
if ( texture.format !== RGBAFormat ) {
throw Error( 'EXRExporter.parse: Unsupported DataTexture texture format, expected RGBAFormat.' );
}
if ( ! texture.image.data ) {
throw Error( 'EXRExporter.parse: Invalid DataTexture image data.' );
}
if ( texture.type === FloatType && texture.image.data.constructor.name !== 'Float32Array' ) {
throw Error( 'EXRExporter.parse: DataTexture image data doesn\'t match type, expected \'Float32Array\'.' );
}
if ( texture.type === HalfFloatType && texture.image.data.constructor.name !== 'Uint16Array' ) {
throw Error( 'EXRExporter.parse: DataTexture image data doesn\'t match type, expected \'Uint16Array\'.' );
}
}
function buildInfoRTT( renderTarget, options = {} ) {
const compressionSizes = {
0: 1,
2: 1,
3: 16
};
const WIDTH = renderTarget.width,
HEIGHT = renderTarget.height,
TYPE = renderTarget.texture.type,
FORMAT = renderTarget.texture.format,
COMPRESSION = ( options.compression !== undefined ) ? options.compression : ZIP_COMPRESSION,
EXPORTER_TYPE = ( options.type !== undefined ) ? options.type : HalfFloatType,
OUT_TYPE = ( EXPORTER_TYPE === FloatType ) ? 2 : 1,
COMPRESSION_SIZE = compressionSizes[ COMPRESSION ],
NUM_CHANNELS = 4;
return {
width: WIDTH,
height: HEIGHT,
type: TYPE,
format: FORMAT,
compression: COMPRESSION,
blockLines: COMPRESSION_SIZE,
dataType: OUT_TYPE,
dataSize: 2 * OUT_TYPE,
numBlocks: Math.ceil( HEIGHT / COMPRESSION_SIZE ),
numInputChannels: 4,
numOutputChannels: NUM_CHANNELS,
};
}
function buildInfoDT( texture, options = {} ) {
const compressionSizes = {
0: 1,
2: 1,
3: 16
};
const WIDTH = texture.image.width,
HEIGHT = texture.image.height,
TYPE = texture.type,
FORMAT = texture.format,
COMPRESSION = ( options.compression !== undefined ) ? options.compression : ZIP_COMPRESSION,
EXPORTER_TYPE = ( options.type !== undefined ) ? options.type : HalfFloatType,
OUT_TYPE = ( EXPORTER_TYPE === FloatType ) ? 2 : 1,
COMPRESSION_SIZE = compressionSizes[ COMPRESSION ],
NUM_CHANNELS = 4;
return {
width: WIDTH,
height: HEIGHT,
type: TYPE,
format: FORMAT,
compression: COMPRESSION,
blockLines: COMPRESSION_SIZE,
dataType: OUT_TYPE,
dataSize: 2 * OUT_TYPE,
numBlocks: Math.ceil( HEIGHT / COMPRESSION_SIZE ),
numInputChannels: 4,
numOutputChannels: NUM_CHANNELS,
};
}
function getPixelData( renderer, rtt, info ) {
let dataBuffer;
if ( info.type === FloatType ) {
dataBuffer = new Float32Array( info.width * info.height * info.numInputChannels );
} else {
dataBuffer = new Uint16Array( info.width * info.height * info.numInputChannels );
}
renderer.readRenderTargetPixels( rtt, 0, 0, info.width, info.height, dataBuffer );
return dataBuffer;
}
function reorganizeDataBuffer( inBuffer, info ) {
const w = info.width,
h = info.height,
dec = { r: 0, g: 0, b: 0, a: 0 },
offset = { value: 0 },
cOffset = ( info.numOutputChannels == 4 ) ? 1 : 0,
getValue = ( info.type == FloatType ) ? getFloat32 : getFloat16,
setValue = ( info.dataType == 1 ) ? setFloat16 : setFloat32,
outBuffer = new Uint8Array( info.width * info.height * info.numOutputChannels * info.dataSize ),
dv = new DataView( outBuffer.buffer );
for ( let y = 0; y < h; ++ y ) {
for ( let x = 0; x < w; ++ x ) {
const i = y * w * 4 + x * 4;
const r = getValue( inBuffer, i );
const g = getValue( inBuffer, i + 1 );
const b = getValue( inBuffer, i + 2 );
const a = getValue( inBuffer, i + 3 );
const line = ( h - y - 1 ) * w * ( 3 + cOffset ) * info.dataSize;
decodeLinear( dec, r, g, b, a );
offset.value = line + x * info.dataSize;
setValue( dv, dec.a, offset );
offset.value = line + ( cOffset ) * w * info.dataSize + x * info.dataSize;
setValue( dv, dec.b, offset );
offset.value = line + ( 1 + cOffset ) * w * info.dataSize + x * info.dataSize;
setValue( dv, dec.g, offset );
offset.value = line + ( 2 + cOffset ) * w * info.dataSize + x * info.dataSize;
setValue( dv, dec.r, offset );
}
}
return outBuffer;
}
function compressData( inBuffer, info ) {
let compress,
tmpBuffer,
sum = 0;
const chunks = { data: new Array(), totalSize: 0 },
size = info.width * info.numOutputChannels * info.blockLines * info.dataSize;
switch ( info.compression ) {
case 0:
compress = compressNONE;
break;
case 2:
case 3:
compress = compressZIP;
break;
}
if ( info.compression !== 0 ) {
tmpBuffer = new Uint8Array( size );
}
for ( let i = 0; i < info.numBlocks; ++ i ) {
const arr = inBuffer.subarray( size * i, size * ( i + 1 ) );
const block = compress( arr, tmpBuffer );
sum += block.length;
chunks.data.push( { dataChunk: block, size: block.length } );
}
chunks.totalSize = sum;
return chunks;
}
function compressNONE( data ) {
return data;
}
function compressZIP( data, tmpBuffer ) {
//
// Reorder the pixel data.
//
let t1 = 0,
t2 = Math.floor( ( data.length + 1 ) / 2 ),
s = 0;
const stop = data.length - 1;
while ( true ) {
if ( s > stop ) break;
tmpBuffer[ t1 ++ ] = data[ s ++ ];
if ( s > stop ) break;
tmpBuffer[ t2 ++ ] = data[ s ++ ];
}
//
// Predictor.
//
let p = tmpBuffer[ 0 ];
for ( let t = 1; t < tmpBuffer.length; t ++ ) {
const d = tmpBuffer[ t ] - p + ( 128 + 256 );
p = tmpBuffer[ t ];
tmpBuffer[ t ] = d;
}
const deflate = fflate.zlibSync( tmpBuffer );
return deflate;
}
function fillHeader( outBuffer, chunks, info ) {
const offset = { value: 0 };
const dv = new DataView( outBuffer.buffer );
setUint32( dv, 20000630, offset ); // magic
setUint32( dv, 2, offset ); // mask
// = HEADER =
setString( dv, 'compression', offset );
setString( dv, 'compression', offset );
setUint32( dv, 1, offset );
setUint8( dv, info.compression, offset );
setString( dv, 'screenWindowCenter', offset );
setString( dv, 'v2f', offset );
setUint32( dv, 8, offset );
setUint32( dv, 0, offset );
setUint32( dv, 0, offset );
setString( dv, 'screenWindowWidth', offset );
setString( dv, 'float', offset );
setUint32( dv, 4, offset );
setFloat32( dv, 1.0, offset );
setString( dv, 'pixelAspectRatio', offset );
setString( dv, 'float', offset );
setUint32( dv, 4, offset );
setFloat32( dv, 1.0, offset );
setString( dv, 'lineOrder', offset );
setString( dv, 'lineOrder', offset );
setUint32( dv, 1, offset );
setUint8( dv, 0, offset );
setString( dv, 'dataWindow', offset );
setString( dv, 'box2i', offset );
setUint32( dv, 16, offset );
setUint32( dv, 0, offset );
setUint32( dv, 0, offset );
setUint32( dv, info.width - 1, offset );
setUint32( dv, info.height - 1, offset );
setString( dv, 'displayWindow', offset );
setString( dv, 'box2i', offset );
setUint32( dv, 16, offset );
setUint32( dv, 0, offset );
setUint32( dv, 0, offset );
setUint32( dv, info.width - 1, offset );
setUint32( dv, info.height - 1, offset );
setString( dv, 'channels', offset );
setString( dv, 'chlist', offset );
setUint32( dv, info.numOutputChannels * 18 + 1, offset );
setString( dv, 'A', offset );
setUint32( dv, info.dataType, offset );
offset.value += 4;
setUint32( dv, 1, offset );
setUint32( dv, 1, offset );
setString( dv, 'B', offset );
setUint32( dv, info.dataType, offset );
offset.value += 4;
setUint32( dv, 1, offset );
setUint32( dv, 1, offset );
setString( dv, 'G', offset );
setUint32( dv, info.dataType, offset );
offset.value += 4;
setUint32( dv, 1, offset );
setUint32( dv, 1, offset );
setString( dv, 'R', offset );
setUint32( dv, info.dataType, offset );
offset.value += 4;
setUint32( dv, 1, offset );
setUint32( dv, 1, offset );
setUint8( dv, 0, offset );
// null-byte
setUint8( dv, 0, offset );
// = OFFSET TABLE =
let sum = offset.value + info.numBlocks * 8;
for ( let i = 0; i < chunks.data.length; ++ i ) {
setUint64( dv, sum, offset );
sum += chunks.data[ i ].size + 8;
}
}
function fillData( chunks, info ) {
const TableSize = info.numBlocks * 8,
HeaderSize = 259 + ( 18 * info.numOutputChannels ), // 259 + 18 * chlist
offset = { value: HeaderSize + TableSize },
outBuffer = new Uint8Array( HeaderSize + TableSize + chunks.totalSize + info.numBlocks * 8 ),
dv = new DataView( outBuffer.buffer );
fillHeader( outBuffer, chunks, info );
for ( let i = 0; i < chunks.data.length; ++ i ) {
const data = chunks.data[ i ].dataChunk;
const size = chunks.data[ i ].size;
setUint32( dv, i * info.blockLines, offset );
setUint32( dv, size, offset );
outBuffer.set( data, offset.value );
offset.value += size;
}
return outBuffer;
}
function decodeLinear( dec, r, g, b, a ) {
dec.r = r;
dec.g = g;
dec.b = b;
dec.a = a;
}
// function decodeSRGB( dec, r, g, b, a ) {
// dec.r = r > 0.04045 ? Math.pow( r * 0.9478672986 + 0.0521327014, 2.4 ) : r * 0.0773993808;
// dec.g = g > 0.04045 ? Math.pow( g * 0.9478672986 + 0.0521327014, 2.4 ) : g * 0.0773993808;
// dec.b = b > 0.04045 ? Math.pow( b * 0.9478672986 + 0.0521327014, 2.4 ) : b * 0.0773993808;
// dec.a = a;
// }
function setUint8( dv, value, offset ) {
dv.setUint8( offset.value, value );
offset.value += 1;
}
function setUint32( dv, value, offset ) {
dv.setUint32( offset.value, value, true );
offset.value += 4;
}
function setFloat16( dv, value, offset ) {
dv.setUint16( offset.value, DataUtils.toHalfFloat( value ), true );
offset.value += 2;
}
function setFloat32( dv, value, offset ) {
dv.setFloat32( offset.value, value, true );
offset.value += 4;
}
function setUint64( dv, value, offset ) {
dv.setBigUint64( offset.value, BigInt( value ), true );
offset.value += 8;
}
function setString( dv, string, offset ) {
const tmp = textEncoder.encode( string + '\0' );
for ( let i = 0; i < tmp.length; ++ i ) {
setUint8( dv, tmp[ i ], offset );
}
}
function decodeFloat16( binary ) {
const exponent = ( binary & 0x7C00 ) >> 10,
fraction = binary & 0x03FF;
return ( binary >> 15 ? - 1 : 1 ) * (
exponent ?
(
exponent === 0x1F ?
fraction ? NaN : Infinity :
Math.pow( 2, exponent - 15 ) * ( 1 + fraction / 0x400 )
) :
6.103515625e-5 * ( fraction / 0x400 )
);
}
function getFloat16( arr, i ) {
return decodeFloat16( arr[ i ] );
}
function getFloat32( arr, i ) {
return arr[ i ];
}
export { EXRExporter, NO_COMPRESSION, ZIP_COMPRESSION, ZIPS_COMPRESSION };

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More