A beam of light creates the outline of an unfolded Galaxy Z Flip7.
The device folds to show the cover screen. As the device rotates, the overall screen
size increases.
Introducing our thinnest and most advanced Galaxy Z Flip ever, with unparalleled performance that seamlessly fits your lifestyle and your pocket. The expanded Cover Screen is show-stopping from edge to edge, and it's all housed in durable body that's slimmer both folded and unfolded.
Experience
the all-new Flip
Perfectly pocket-sized smartphone
Galaxy Z Flip7
4.1"
Galaxy Z Flip7 is seen folded from the cover screen.
It rotates to show its slim and sleek side profile.
Redesigned, stronger FlexHinge
Hands-free, share-worthy selfies
50MP
The cover screen of Galaxy Z Flip7 is seen expanding
to become wider. Pictures can be taken and viewed from the cover
screen in your hand.
Galaxy AI in the palm of your hand
Now Brief's Weekend brief can be seen on the cover
screen of Galaxy Z Flip7. The user can scroll through the full
Weekend brief on the cover screen to see today's workout record, the
latest weather, photo memories and more.
Powerful battery for long-lasting use,,
Battery capacity
4300mAh
Watch videos up
to
31hrs
"It's truly just a normal phone that folds in half."
9to5Google
07/2025
"Galaxy Z Flip7 is the best camera phone for creators."
Tom's Guide
07/2025
Samsung products are rated #1 in Design
Experience the fun and pocketability of Galaxy Z Flip7 and see why Samsung is rated #1
in Design by the American Customer Satisfaction Index (ACSI®).
Scan the QR code to experience the wonder of Galaxy AI on your phone,
like getting helpful updates from Now Brief and handling multiple tasks with a single ask.Learn more
Personal training, on us
Get 6 months of trainer-led workouts by iFIT on Samsung Health with new qualifying Galaxy Z Flip7.
The redesigned Cover Screen on Galaxy Z Flip7 features our slimmest bezel yet, enabling you to do more from FlexWindow than ever before. The stunning Main Screen now features an undisrupted viewing experience that's a sight to behold.
Upgraded to flip without fear
Armored in aluminum
Strong but lightweight, the frame made of Armor Aluminum protects your Galaxy Z Flip7 and
is IP48-rated for water and dust resistance.,
Enhanced Armor FlexHinge
Meet our innovative Armor FlexHinge that now
closes thinner and is more durable than ever.
A show of strength
A durable display that goes the distance with
Corning® Gorilla®
Glass Victus® 2.
Close up of Galaxy Z Flip7's rear and main screen camera lenses.
Rear Camera
12MPUltra Wide
50MPWide2x Optical Quality Zoom
Main Screen Camera
10MPMain Screen
See camera specs side-by-side
Compare with
Galaxy Z Flip7
50
Flip to your best selfies with a 50MP camera50MP rear camera. Flip to your best
angle.Flip over to a new selfie
experience
Captured#withGalaxy Z Flip7
Captured by Galaxy Z Flip7#withGalaxy
A selfie of a person and their pet is being shot using Galaxy Z Flip7's 50 MP
camera and can be viewed on the cover screen. Details and textures of both the person and their
pet are accurately captured.
Enjoy richer, more detailed selfies that you can
preview and apply custom filters to on FlexWindow.
Finally, a selfie cam worthy of
your closeup.
Finally, a selfie cam in all its
full-screen glory.
Capture the finer
points in every scene with our high-resolution 50MP camera and let our next-gen
ProVisual Engine improve clarity to bring out your natural skin tones. You can even use the
AI learning-based custom filters to add
finishing touches to make your pics uniquely yours.,,The
high-resolution 50MP camera on Galaxy Z Flip7 is boosted by ProVisual Engine to
deliver rich, beautifully detailed shots with accurate skin tones. Plus, instantly apply
custom filters based on AI learning directly from the cover screen to add finishing
touches that will make your pics uniquely yours.,,The
high-resolution 50MP camera with ProVisual engine on Galaxy Z Flip7 delivers rich,
detailed shots with more accurate skin tones–all with the ease of FlexMode. Plus, instantly apply
custom filters based on AI learning directly from the cover screen to add finishing
touches that will make your pics uniquely yours.,,
Max zoom. Max detail
Get super-clear, share-worthy pics from any distance. Powered by our ProVisual
Engine, AI Zoom delivers stunning portraits whether you're taking selfies
up-close, Flip in hand, or going hands-free with FlexCam. Once you've snapped your picture, AI will
analyze and refine the details to produce crisp photos every time —
no need to worry about finding the right distance.
A group selfie is being taken on Galaxy Z Flip7's 50 MP camera and can be
previewed on the cover screen. Even when zoomed in by 1.9 times, AI Zoom ensures the details
remain clear.
Captured#withGalaxy Z
Flip7
Bring details out of the dark
A video is being shot on Galaxy Z Flip7's Wide camera in the style
of a camcorder. Thanks to Nightography, details remain sharp and clear even in low
light.
Captured#withGalaxy Z
Flip7
Capture breathtaking videos even in low light. The
high-resolution sensor works together with our advanced ProVisual Engine to
deliver richer contrast day or night.,
Galaxy Z Flip7 for business
The features you need to work on-the-go, in a smartphone reimagined to fit small pockets and big business moves.
The advanced processor on Galaxy Z Flip7 powers
everything from the battery to the displays, ensuring that it works at the speed you need it to,
whether you're streaming, gaming or editing your latest video.
CPU
9%
faster processing
GPU
23%
smoother graphics
NPU
22%
quicker AI
*Compared to Galaxy Z Flip6
The largest battery on Galaxy Z Flip
A 4300mAh battery and mDNIe
technology have been optimized so this Flip is ready for hours
of uninterrupted entertainment.,,,
See how long the battery on Galaxy Z Flip7 lasts
Compare with
Galaxy Z Flip7
31,,
of video playback time
Watch videos up to
more than
Seamless views, inside and out
Discover our smoothest viewing experience on FlexWindow.
With up to 120 Hz refresh rates and a peak brightness of 2600 nits, scrolling is easy on your
eyes even outdoors. Plus, our improved Vision Booster enhances the color and
contrast so you can see every detail clearly.,
Storage for everything you need
Keep all your treasured moments on hand with 12GB of memory and a choice of two storage options. Choose up to 256GB of storage if you like to take photos and stream videos. Or, go even bigger with up to 512GB of storage, if you're an avid gamer or download larger apps, to avoid running out of storage space.
Galaxy AI in your pocket
Galaxy AI is designed to
push the boundaries of any other smartphone's AI capabilities. Ask for advice in real time,
enhance your photos, receive personalized updates from Now Brief and more. Expand your world
like never before.
Now BriefStay ahead of your day with Now Brief
Now Bar shows Now Brief's Weekend brief alert. Now Bar is seen moving to
the bottom of the cover screen of Galaxy Z Flip7. When the notification is pressed, the
user can scroll through the full Weekend brief on the cover screen to see today's
workout record, the latest weather, photo memories and more.
Receive a personalized summary of your day from Now Brief
right from the cover screen. Tailored insights, such as the latest weather and
Energy Score, are all available at just a glance.
Now barLock into the now
Now Bar, available right from the Cover Screen, gives you direct access to music that's playing, live notifications and more
Various active apps are seen from Now Bar including exercise, music and
Now Brief's Weekend brief alert.
Gemini LiveAsk Gemini about what you see
Speak freely and naturally with Gemini Live to get information in real time. Ask Gemini what symbols mean and get washing instructions for clothes, or get information about a restaurant you saw while walking by. Share your world through the camera to ask for advice or ideas.
Get 6 months of Google AI Pro including 2 TB of storage
Get a 6-month trial on us, with higher access to powerful features in the Gemini app like video generation powered by Veo3, plus 2 TB of secure cloud storage.
Your privacy, secured for the era of AI
On-device protection
Our Personal Data Engine gathers and processes your personal
data safely. Then it encrypts that data and saves it onto your device with Knox
Vault, making it harder for anyone else to gain access.
Tailored controls
Take control over your Galaxy AI. You decide where your data
gets processed, either on-device or in the cloud.
Connected security
Knox Matrix Trust Chain technology provides security across
your Galaxy ecosystem so you can monitor other devices right from your Galaxy
smartphone.
One UI 8.All new for Flip.
Our best, most personalized UI was built from the ground up for both the Main Screen and the Cover Screen. With customizable wallpaper and interactive weather and gallery widgets, your Flip will be uniquely yours.,
Five Galaxy Z Flip7 devices are arranged in various positions with
FlexWindow visible. On each of their cover screens, a portrait with Clock Widget,
Clock Widget that transforms to Mapview Widgets, Brief Widgets, Video Wallpaper and
Cover Home Screen are seen.
Why switch to GalaxyWhy switch to Galaxy from
your iPhone From Android to Galaxy. Get
ready to level up your phone
Imagine where our most powerful
smartphones can take you.Your iPhone can only take
you so far.Your phone can only take
you so far.You won't just
discover a whole new world, but an entire Galaxy that works smoothly with
other Android devices. Make the switch and see how life opens up with Galaxy.
It's
time for new adventures, new opportunities and a new Galaxy that works just
as smoothly with other iOS, Macs and PCs. Life opens up with Galaxy, and we can't wait to
show you how.It's
time for new adventures, new opportunities and a new Galaxy that works just
as smoothly with Android OS and PCs. Life opens up with Galaxy, and we can't wait to show
you how.
Smart SwitchHow easy is switching?Smart Switch
Switching from your current phone is easy.
Thanks to Smart Switch, you can transfer your photos, videos, contacts and apps
effortlessly and safely.,,Thanks to Smart Switch, you can transfer your photos, videos, contacts and apps effortlessly and safely.,,Switching from your
current phone is easy. With Smart Switch, you can transfer your data such as photos,
videos, contacts and apps effortlessly and safely.,,Learn more
Samsung WalletHow’s Samsung’s AI tech?Samsung Wallet
Simplify your life with Samsung Wallet — the
easy way to make payments, access cards, earn rewards and more.Get Galaxy AI tools you won’t find anywhere else, like Photo Assist for editing the perfect pics with no additional apps.Simplify your life with
Samsung Wallet — the easy way to make payments, access cards, earn rewards and
more.Learn moreLearn moreLearn more
RCSWhat about text messaging?RCS
Rich Communication Services (RCS) lets you
send high-quality media, see message receipts and react in real time for enhanced
messaging between Android and iOS.Messaging has never been easier, now that iPhone also runs on RCS. React, see read receipts, and send hi-res content seamlessly.Rich Communication
Services (RCS) lets you send high-quality media, see message receipts and react in real
time for enhanced messaging between Android and iOS.Learn more
Test drive Galaxy AI on your phoneTry GalaxyTry Galaxy
Scan the QR code to experience the wonder of Galaxy AI on your phone, like getting helpful updates from Now Brief and handling multiple tasks with a single ask.Curious about what
Galaxy is like? From Galaxy AI to the latest One UI, try it all out on your
iPhone.Experience a whole
new world when you try out Galaxy on your phone and see what makes it so
special.Learn more
Discover the benefits of Galaxy
Trade in and save on a new device
Trade in your old device and you can get instant
discounts on your new Galaxy Z Flip7.Learn more
Protecting your Galaxy Life
Get powerful protection for your Galaxy device
covering unlimited drops, spills, and mechanical breakdowns.Learn more
Connected living starts with Galaxy
Connect your Galaxy Z Flip7 with Galaxy Watch8, Galaxy Buds3 Pro and other Galaxy
devices for a seamless, smarter living experience.
Compared to previous Galaxy Z Flip models, Galaxy Z Flip7 has been built for improved durability.
It boasts a tough Armor Aluminum frame and Corning® Gorilla® Glass Victus® 2 has been applied to the front and the back of the device for extra protection. It also is rated IP48 for water and dust resistance.
The Armor FlexHinge is a new and improved take on our Dual Rail Hinge that folds the display into a teardrop shape allowing the device to close with virtually no gaps in-between. Its unique design also disperses any external impact to further enhance durability.,
Galaxy Z Flip7 is equipped with a larger 4300 mAh battery. Working together with the
powerful processor, its efficiency has also improved for an all-day battery life with up
to 31 hours of video playback time. Plus, the super-fast charging means shorter breaks
in between sessions.,,,
The 50MP rear camera on Galaxy Z Flip7 allows for taking photos and videos using FlexCam
from the Cover Screen.
When in FlexMode, the Galaxy Z Flip7 stands like a tripod with previews on the Cover
Screen so you can take high-resolution 50MP selfies or shoot videos hands-free. Various
shooting modes are available such as Photo, Portrait, Video and more.
Plus, powered by our ProVisual Engine, the camera system has built-in AI tools that
produce crisp, share-worthy pictures. For example, our AI Zoom can instantly analyze
your image and plug any gaps to ensure no detail is lost. And Auto Zoom will
intelligently show you the best framing before you even take your photo. Now, you can
easily take handheld selfies, or further away ones after setting Flip down in FlexMode
without having to worry about finding the right distance. To ensure your 50MP portraits
are true to life, ProVisual Engine utilizes the processor’s built-in NPU to enhance the
image processing.,,,
Galaxy Z Flip7’s unique form factor allows you to enjoy the benefits of two smartphones
in one. Unfolded, it works just like any other phone but once folded, its full cover
screen usability makes it the most pocketable AI companion.
The 50MP rear camera doubles up as a selfie camera thanks to the previews available from
FlexWindow. Boosted by our ProVisual Engine, your photos and videos will be bright,
clear and sharp. Details are true to life even if you're shooting in the dark with
Nightography, and with AI Zoom, you won’t lose detail no matter how far you zoom
in.
With the edge-to-edge cover screen and AI that’s been optimized for foldables on the new
One UI 8, you can conveniently get things done more easily and swiftly. The slimmed-down
design of the phone, folded or unfolded, makes it comfortable to hold in your
hand.,,,
Yes, you can quickly and easily transfer your iOS data to Galaxy Z Flip7 using Smart
Switch.
Smart Switch is a tool that simplifies transferring data from one device
to another by allowing you to seamlessly move contacts, photos, videos, notes, calendar
events and more. You can connect your new Galaxy Z Flip7 to your old device using a
cable or wirelessly using the Smart Switch app.,,
The Cover Screen on Galaxy Z Flip7 has a reduced bezel, making it the slimmest on Galaxy Z Flip. This edge-to-edge design gives a feel of an infinity cover screen making it even more comfortable to use when folded.
Unfolded, it’s our thinnest Flip yet placing it in the league with any other smartphone. The Main Screen has increased in size to 6.9”, boasting a 21:9 ratio for more versatile use.,,
Galaxy Z Flip7 offers multi-modal AI that adjusts itself to suit your life, your needs
and even your conversation style — a true AI companion.
You can access Gemini Live at the tap of a button. It gives you answers in real time,
even asking questions to prompt a multi-turn conversation, ensuring that you get the
information you need.
There is also a personal assistant, Now Brief — personalized updates that are sent
throughout the day which you can even access via the Now Bar right from the cover
screen. It learns your habits and patterns, offering suggestions and prompts depending
on your schedule and activities. Intelligently aligning with your daily routine, it
informs you of calendar events, displays your Energy Score or offers a recap of your day
as you wind down for the night.,,
Galaxy Z Flip7 is available in three confident colors: Blue Shadow, Jetblack and
Coralred. We also offer a Samsung.com only color, Mint, which is exclusively for
customers who purchase on our site.
Galaxy Z Flip7 has two storage options available; 256 GB or 512 GB. If you take photos,
use various apps and occasionally record videos, then 256 GB is a good balance between
cost and storage.
If you’re a regular gamer, download larger apps or record videos in high definition,
choosing 512 GB is recommended to avoid running out of space and maintain a smooth
performance.
As of July 11, 2025, Galaxy AI's Call Assist (e.g. Live Translate), Note Assist, Browsing Assist, Interpreter, and Transcript Assist currently support 20 languages and Writing Assist supports 39 languages as listed below.
Call Assist (e.g. Live Translate), Note Assist, Browsing Assist, Interpreter and Transcript Assist supported languages: Korean, English, Spanish, French, German, Italian, Portuguese, Chinese, Japanese, Polish, Hindi, Thai, Vietnamese, Arabic, Indonesian, Russian, Turkish, Dutch, Swedish and Romanian.
Galaxy Z Flip7 comes with One UI 8, which has been redesigned and optimized for the Z
Series for easier access to some of our personalized features such as Now Brief and Now
Bar.
There are also brand-new widgets and home widgets are now available via FlexWindow. For
further customizations, there’s animated wallpaper and multi-widget view making it the
most personal UI yet.
When you buy from Samsung.com, you get access to exclusive benefits including Trade-In
and Samsung Care+.
If you purchase your Galaxy Z Flip7 from us, you can receive discounts towards your new
smartphone when you trade in almost any device including smartphones, wearables and
tablets.
Samsung Care+ is coverage for your new device that extends beyond the standard warranty,
offering protection against accidental damage and providing more peace of mind to our
customers.,
In an effort to help reduce digital waste, every Galaxy Z Flip7 comes with only what you
need to get started: the device, a USB-C data cable and an ejection pin for the SIM
tray.
There are a wide range of accessories available to buy for your Galaxy Z Flip7 including
customizable cases such as the Flipsuit Case. With just one case, you can customize your
Flip according to your outfit or mood with interchangeable Flipsuit Cards. Once
installed, the screen background automatically syncs with the card for further
personalization.
The Anti-reflecting Film is easy to install and helps protect the cover screen from
damage to keep the display fully visible.
Based on Samsung internal lab test conditions with pre-release version of given model
connected to earphone via Bluetooth under default settings over LTE. Estimated against battery
capacity and measured current over battery power consumption during video playback (video file
resolution 720p, saved on device). Actual video playback time may vary by network connection,
settings, video file format, screen brightness, battery condition and many other factors.
Actual battery life varies by network environment, features and apps used, frequency of calls
and messages, the number of times charged, and many other factors. Estimated against the average
usage profile compiled by UX Connect Research. Independently assessed by UX Connect Research
between 2025.06.12-2025.06.20 in US with pre-release versions of SM-F766 under default setting
using LTE and 5G Sub6 networks. NOT tested under 5G mmWave Network.
Typical value tested under third-party laboratory condition. Typical value is the estimated
average value considering the deviation in battery capacity among the battery samples tested
under IEC 61960 standard. Rated capacity is 4174 mAh for Galaxy Z Flip7.
Color availability may vary depending on country or carrier.
Armor Aluminum frame does not include volume and side keys, SIM tray or camera lens
barrel.
Corning® Gorilla® Glass Victus® 2 is applied to the front and rear of the device.
Thickness measured from top to bottom of the glass when unfolded.
Galaxy Z Flip3 and Galaxy Z Flip4's thickness measured from top to bottom of the glass at the
thickest point.
Weight may vary by country or region.
Measured diagonally, Galaxy Z Flip7's Cover Screen size is 4.1” in the full rectangular
form; actual viewable area is less due to the rounded corners and camera hole.
Measured diagonally, Galaxy Z Flip3's Cover Screen size is 1.9” in the full rectangle and 1.8”
with accounting for the rounded corners; actual viewable area is less due to the rounded
corners.
Measured diagonally, Galaxy Z Flip4's Cover Screen size is 1.9” in the full rectangle and 1.8”
accounting for the rounded corners; actual viewable area is smaller due to the rounded
corners.
Measured diagonally, Galaxy Z Flip5's Cover Screen size is 3.4” in the full rectangular form;
actual viewable area is approximately 95% of the full rectangular area due to the rounded
corners and lower cutout.
Measured diagonally, Galaxy Z Flip6's Cover Screen size is 3.4” in the full rectangular form;
actual viewable area is approximately 95% of the full rectangular area due to the rounded
corners and lower cutout.
Results may vary depending on light condition and/or shooting conditions including multiple
subjects, being out of focus or moving subjects.
50MP resolution is available on Galaxy Z Flip7's rear wide camera only.
AI Zoom is applied to distances between digital zoom lengths. Accuracy of results is not
guaranteed.
AP performance improvements shown compared to Galaxy Z Flip6. Actual performance will depend
on user environment, conditions and pre-installed software and applications.
Galaxy Z Flip7 has a peak brightness of 2600 nits on both the main screen and cover screen.
The displays are adaptive, adjusting brightness level automatically based on the environment. In
certain illuminance conditions or higher, High Brightness Mode and Vision Booster will be
activated.
Storage options and availability may vary by carrier, country or region. Actual storage
availability may vary depending on pre-installed software.
Now Brief feature requires Samsung Account login. Service availability may vary by country,
language, device model, and apps. Some features may require a network connection. Modes and Routines
need to be enabled to use Personal Data Engine for context-based Routines suggestions. User
needs to consent to access permissions such as photos, videos, audio files, and calendar events.
May not display moments depending upon exposure policy. The description of photos provided by
moments may not align with the user's intent. Events schedule notification is supported with
calendar apps that utilize Android calendar database and available if Samsung Calendar is
installed. Notifications for coupons only available for coupons added to Samsung Wallet with
expiration date. To check the Energy Score, the health data tracked from Samsung Galaxy Watch or
Samsung Galaxy Ring must be synchronized with the Samsung Health App. The result is for your
personal reference only and is not intended for medical purposes.
Availability of functions supported within the apps may vary by country. Some functional
widgets may require a network connection and/or Samsung Account login.
Gemini is a trademark of Google LLC. Results for illustrative purposes. Gemini Live feature
requires internet connection and Google Account login. Service availability may vary by country,
language, device model. Features may differ depending on subscription and results may vary.
Compatible with certain features and certain accounts. Currently, you can use a personal Google
Account that you manage on your own, or a work or school account for which your administrator
has enabled access to Gemini. You must be 13 (or the applicable age in your country) or over to
use Gemini with a personal or school Google Account and 18 or over to use Gemini with a work
account.
Image shortened and simulated. Results for illustrative purposes and may vary.
Check responses for accuracy. Gemini is a trademark of Google LLC. Terms apply. By subscribing,
you agree to terms for Google One, AI Credits and offers. Offer ends July 20, 2026. Only available
for ages 18+. Unless canceled earlier, Google One will charge $19.99/mo after the trial ends. Cancel
anytime. Return of purchased device may result in cancellation of subscription. See full terms at
https://one.google.com/offer/terms-and-conditions/samsung-flipfold7-6month-trial
The Personal Data Engine functions under the condition that the Personal Data Intelligence
menu is on. Analyzed data will be deleted once the Personal Data Intelligence menu is turned
off. Personal Data Engine recognizes select languages and certain accents/dialects, including
but not limited to Arabic, (Simplified) Chinese, Dutch, English, French, German, Hindi,
Indonesian, Italian, Japanese, Korean, Polish, Portuguese, Romanian, Russian, Spanish, Swedish,
Thai, Turkish, and Vietnamese. Personal Data Engine currently analyzes Samsung native
applications.
Some functional widgets may require a network connection and/or Samsung Account login.
Availability of functions supported within the apps may vary by country.
Weather Wallpaper feature requires network connection to receive the weather data. It can
reflect the weather data without network connection once the data is received by the device.
Reflection of real-time weather conditions may be delayed as service relies on local weather
information updates. Certain indoor, nighttime and low-resolution photos may not be compatible.
The accuracy and reliability of the generated output is not guaranteed.
Wired transfers from Android™ devices require the receiving device to have Android™ 5.0 or
later and the sending device to have Android™ 5.0 or later. Transfers can be completed without a
cable through a wireless connection. For wireless connections, the receiving device must have
Android™ 5.0 or later and the sending device must have Android™ 5.0 or later. Open Smart Switch
Mobile in “Settings” on the receiving Galaxy device or download the Smart Switch Mobile app from
the Galaxy Store. Data, content and apps available for transfer may vary by transmission
method.
Wired transfers from iOS require the receiving device to have Android™ 5.0 or later and the
sending device to have iOS 5 or later. Transfers can be completed without a cable through a
wireless connection or iCloud. For wireless connections, the receiving device must have Android™
5.0 or later and the sending device must have iOS 12 or later. iCloud transfers require the
receiving device to have Android™ 5.0 or later and the sending device to have iOS 5 or later.
Open Smart Switch Mobile in “Settings” on the receiving Galaxy device or download the Smart
Switch Mobile app from the Galaxy Store. For wireless transfers, download Smart Switch for iOS
from the App Store on your iPhone or iPad. Data, content and apps available for transfer may
vary by transmission method. Contacts, gallery, videos, calendars and reminders can be
transferred wirelessly from an iOS device.
It is recommended to use a wired connection when transferring diverse sets of data at
once.
Quick Share feature between Galaxy devices available with the following OS: smartphones and
tablets with Android OS version 10.0 (Q) and One UI 2.1 or above, PCs running Windows 10 or
later. Requires Samsung Account and Wi-Fi and Bluetooth connection. Quick Share to iOS and
Android devices available by sending shared link: individual files shared cannot exceed 10GB
(for a total of 10GB per day) and link will expire after two days; requires a Samsung Account
and internet connection.
RCS is the latest in messaging. Availability of RCS varies by region and carrier. For cross-OS communication, all members must have RCS enabled. For Android, requires Google Messages. For iPhone, requires iOS 18 or later. Availability of features may vary by market and device. Encryption only available for Android to Android communications. Image quality may vary. Select ”Original quality” under the Media quality choice (HD+) when an image is attached for higher resolution images. Read receipts and typing indicates only supported in 1:1 chats for cross-OS communication. Reactions only available for text messages; photos and videos are not supported for cross-OS communication.
To be eligible for trade-in, your qualifying device must meet all Trade-In Program eligibility
requirements. For detailed information, please refer to
https://www.samsung.com/us/trade-in/.
Terms and conditions apply. Samsung Care+ coverage, service type and promotion details may
vary by country/region and deductible (service fee) may apply. To be eligible for Samsung Care+
promotion benefit, registration may be required. For detailed Samsung Care+ information, please
visit https://www.samsung.com/us/support/samsung-care-plus.
Galaxy devices each sold separately.
Measured diagonally, Galaxy Z Flip7's Main Screen size is 6.9” in the full rectangle
and 6.8” accounting for the rounded corners; actual viewable area is less due to the rounded
corners and camera hole.
Optical quality zoom is enabled by the Adaptive Pixel sensor.
Only compatible with select Visa, Mastercard, American Express, and Discover cards from
participating banks and qualifying Samsung devices. Check with your bank/issuer to ensure that
your card is compatible; and check the Samsung Wallet FAQ for more information on device
compatibility.
Digital ID only for select corporate, government and educational institution partners. Student
ID only for select education institution partner. Mobile Driver's License only for TSA ID
purposes, only for Arizona and Iowa residents at select Arizona and Iowa airports and TSA
checkpoints; Mobile Driver's License for TSA ID only and does not replace physical license.
Visit the Samsung Wallet FAQ for more information.
Digital keys are available for select SmartThings-compatible smart door locks and automobiles.
Exact feature availability may vary by model and is subject to change.
Movie tickets only available with Atom Tickets currently. Must have the Atom Tickets app and an
Atom Tickets account.
IP48: Resists solid particles >1 mm; Tested for submersion in up to 5 feet of fresh water for up to 30 minutes. Rinse residue / dry after wet.
Source: 2025 ACSI® Survey of customers rating their Cell Phone manufacturer. Samsung was rated #1 for Service Experience for Mobile devices. ACSI and its logo are registered trademarks of the American Customer Satisfaction Index. For more about the ACSI, visit www.theacsi.org
11/3/25-12/31/26 while supplies last, purchase a qualifying product at Samsung.com or the Shop Samsung app and receive a trial of Samsung Health powered by iFit as follows: (a) 30 days with the purchase of: Fold7, Flip7, S25 Ultra, S25+, S25, S25 FE, S25 Edge, A56, A36, A26, or A16, (b) 3 months with the purchase of: Galaxy Buds3 Pro, (c) 6 months with purchase of: Galaxy Watch8, Galaxy Watch8 Classic, Galaxy Watch Ultra. Activation code will be sent via e-mail 14 days after purchase.
\n \n '), e.appendChild(this.els.gestureGuide), this.setGestureGuideFocus() } setGestureGuideFocus() { const t = new il("gesture-guide"); t.on(), this.els.gestureGuide.addEventListener("click", (e => { o(I, !0, 90), this.els.gestureGuide.style.transition = "opacity 0.5s ease", this.els.gestureGuide.style.opacity = 0, t.off(), setTimeout((() => { window.dispatchEvent(new Event("resize")), document.querySelector(".v3d-wrap h1").focus(), this.els.gestureGuide.parentNode.removeChild(this.els.gestureGuide) }), 500) })) } showGestureGuide() { } hideGestureGuide() { } } function sl() { let t = {}; return { get: function (e) { return t[e] }, add: function (e, i) { t[e] = i }, remove: function (e) { delete t[e] }, removeAll: function () { t = {} } } } const rl = "KHR_binary_glTF", al = "KHR_draco_mesh_compression", ol = "KHR_lights_punctual", ll = "KHR_materials_clearcoat", hl = "KHR_materials_ior", cl = "KHR_materials_pbrSpecularGlossiness", dl = "KHR_materials_sheen", ul = "KHR_materials_specular", pl = "KHR_materials_transmission", ml = "KHR_materials_iridescence", fl = "KHR_materials_unlit", gl = "KHR_materials_volume", vl = "KHR_texture_basisu", _l = "KHR_texture_transform", xl = "KHR_mesh_quantization", yl = "KHR_materials_emissive_strength", bl = "EXT_texture_webp", Ml = "EXT_meshopt_compression"; class wl { constructor(t) { this.parser = t, this.name = ol, this.cache = { refs: {}, uses: {} } } _markDefs() { const t = this.parser, e = this.parser.json.nodes || []; for (let i = 0, n = e.length; i = 0) throw new Error("THREE.GLTFLoader: setKTX2Loader must be called before loading KTX2 textures"); return null } return e.loadTextureImage(t, s.source, r) } } class Ol { constructor(t) { this.parser = t, this.name = bl, this.isSupported = null } loadTexture(t) { const e = this.name, i = this.parser, n = i.json, s = n.textures[t]; if (!s.extensions || !s.extensions[e]) return null; const r = s.extensions[e], a = n.images[r.source]; let o = i.textureLoader; if (a.uri) { const t = i.options.manager.getHandler(a.uri); null !== t && (o = t) } return this.detectSupport().then((function (s) { if (s) return i.loadTextureImage(t, r.source, o); if (n.extensionsRequired && n.extensionsRequired.indexOf(e) >= 0) throw new Error("THREE.GLTFLoader: WebP required by asset but unsupported."); return i.loadTexture(t) })) } detectSupport() { return this.isSupported || (this.isSupported = new Promise((function (t) { const e = new Image; e.src = "data:image/webp;base64,UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA", e.onload = e.onerror = function () { t(1 === e.height) } }))), this.isSupported } } class Nl { constructor(t) { this.name = Ml, this.parser = t } loadBufferView(t) { const e = this.parser.json, i = e.bufferViews[t]; if (i.extensions && i.extensions[this.name]) { const t = i.extensions[this.name], n = this.parser.getDependency("buffer", t.buffer), s = this.parser.options.meshoptDecoder; if (!s || !s.supported) { if (e.extensionsRequired && e.extensionsRequired.indexOf(this.name) >= 0) throw new Error("THREE.GLTFLoader: setMeshoptDecoder must be called before loading compressed files"); return null } return Promise.all([n, s.ready]).then((function (e) { const i = t.byteOffset || 0, n = t.byteLength || 0, r = t.count, a = t.byteStride, o = new ArrayBuffer(r * a), l = new Uint8Array(e[0], i, n); return s.decodeGltfBuffer(new Uint8Array(o), r, a, l, t.mode, t.filter), o })) } return null } } const zl = "glTF"; class Fl { constructor(t) { this.name = rl, this.content = null, this.body = null; const e = new DataView(t, 0, 12); if (this.header = { magic: Do.decodeText(new Uint8Array(t.slice(0, 4))), version: e.getUint32(4, !0), length: e.getUint32(8, !0) }, this.header.magic !== zl) throw new Error("THREE.GLTFLoader: Unsupported glTF-Binary header."); if (this.header.version ", e).replace("#include ", i).replace("#include ", n).replace("#include ", s).replace("#include ", r) }, Object.defineProperties(this, { specular: { get: function () { return a.specular.value }, set: function (t) { a.specular.value = t } }, specularMap: { get: function () { return a.specularMap.value }, set: function (t) { a.specularMap.value = t, t ? this.defines.USE_SPECULARMAP = "" : delete this.defines.USE_SPECULARMAP } }, glossiness: { get: function () { return a.glossiness.value }, set: function (t) { a.glossiness.value = t } }, glossinessMap: { get: function () { return a.glossinessMap.value }, set: function (t) { a.glossinessMap.value = t, t ? (this.defines.USE_GLOSSINESSMAP = "", this.defines.USE_UV = "") : (delete this.defines.USE_GLOSSINESSMAP, delete this.defines.USE_UV) } } }), delete this.metalness, delete this.roughness, delete this.metalnessMap, delete this.roughnessMap, this.setValues(t) } copy(t) { return super.copy(t), this.specularMap = t.specularMap, this.specular.copy(t.specular), this.glossinessMap = t.glossinessMap, this.glossiness = t.glossiness, delete this.metalness, delete this.roughness, delete this.metalnessMap, delete this.roughnessMap, this } } class Vl { constructor() { this.name = cl, this.specularGlossinessParams = ["color", "map", "lightMap", "lightMapIntensity", "aoMap", "aoMapIntensity", "emissive", "emissiveIntensity", "emissiveMap", "bumpMap", "bumpScale", "normalMap", "normalMapType", "displacementMap", "displacementScale", "displacementBias", "specularMap", "specular", "glossinessMap", "glossiness", "alphaMap", "envMap", "envMapIntensity"] } getMaterialType() { return Bl } extendParams(t, e, i) { const n = e.extensions[this.name]; t.color = new ae(1, 1, 1), t.opacity = 1; const s = []; if (Array.isArray(n.diffuseFactor)) { const e = n.diffuseFactor; t.color.fromArray(e), t.opacity = e[3] } if (void 0 !== n.diffuseTexture && s.push(i.assignTexture(t, "map", n.diffuseTexture, At)), t.emissive = new ae(0, 0, 0), t.glossiness = void 0 !== n.glossinessFactor ? n.glossinessFactor : 1, t.specular = new ae(1, 1, 1), Array.isArray(n.specularFactor) && t.specular.fromArray(n.specularFactor), void 0 !== n.specularGlossinessTexture) { const e = n.specularGlossinessTexture; s.push(i.assignTexture(t, "glossinessMap", e)), s.push(i.assignTexture(t, "specularMap", e, At)) } return Promise.all(s) } createMaterial(t) { const e = new Bl(t); return e.fog = !0, e.color = t.color, e.map = void 0 === t.map ? null : t.map, e.lightMap = null, e.lightMapIntensity = 1, e.aoMap = void 0 === t.aoMap ? null : t.aoMap, e.aoMapIntensity = 1, e.emissive = t.emissive, e.emissiveIntensity = void 0 === t.emissiveIntensity ? 1 : t.emissiveIntensity, e.emissiveMap = void 0 === t.emissiveMap ? null : t.emissiveMap, e.bumpMap = void 0 === t.bumpMap ? null : t.bumpMap, e.bumpScale = 1, e.normalMap = void 0 === t.normalMap ? null : t.normalMap, e.normalMapType = 0, t.normalScale && (e.normalScale = t.normalScale), e.displacementMap = null, e.displacementScale = 1, e.displacementBias = 0, e.specularMap = void 0 === t.specularMap ? null : t.specularMap, e.specular = t.specular, e.glossinessMap = void 0 === t.glossinessMap ? null : t.glossinessMap, e.glossiness = t.glossiness, e.alphaMap = null, e.envMap = void 0 === t.envMap ? null : t.envMap, e.envMapIntensity = 1, e } } class Gl { constructor() { this.name = xl } } class Hl extends Ya { constructor(t, e, i, n) { super(t, e, i, n) } copySampleValue_(t) { const e = this.resultBuffer, i = this.sampleValues, n = this.valueSize, s = t * n * 3 + n; for (let t = 0; t !== n; t++)e[t] = i[s + t]; return e } interpolate_(t, e, i, n) { const s = this.resultBuffer, r = this.sampleValues, a = this.valueSize, o = 2 * a, l = 3 * a, h = n - e, c = (i - e) / h, d = c * c, u = d * c, p = t * l, m = p - l, f = -2 * u + 3 * d, g = u - d, v = 1 - f, _ = g - d + c; for (let t = 0; t !== a; t++) { const e = r[m + t + a], i = r[m + t + o] * h, n = r[p + t + a], l = r[p + t] * h; s[t] = v * e + _ * i + f * n + g * l } return s } } const jl = new ve; class Wl extends Hl { interpolate_(t, e, i, n) { const s = super.interpolate_(t, e, i, n); return jl.fromArray(s).normalize().toArray(s), s } } const Xl = { 5120: Int8Array, 5121: Uint8Array, 5122: Int16Array, 5123: Uint16Array, 5125: Uint32Array, 5126: Float32Array }, Yl = { 9728: et, 9729: st, 9984: it, 9985: 1007, 9986: nt, 9987: at }, ql = { 33071: Q, 33648: tt, 10497: J }, Zl = { SCALAR: 1, VEC2: 2, VEC3: 3, VEC4: 4, MAT2: 4, MAT3: 9, MAT4: 16 }, $l = { POSITION: "position", NORMAL: "normal", TANGENT: "tangent", TEXCOORD_0: "uv", TEXCOORD_1: "uv2", COLOR_0: "color", WEIGHTS_0: "skinWeight", JOINTS_0: "skinIndex" }, Kl = { scale: "scale", translation: "position", rotation: "quaternion", weights: "morphTargetInfluences" }, Jl = { CUBICSPLINE: void 0, LINEAR: yt, STEP: xt }; function Ql(t, e, i) { for (const n in i.extensions) void 0 === t[n] && (e.userData.gltfExtensions = e.userData.gltfExtensions || {}, e.userData.gltfExtensions[n] = i.extensions[n]) } function th(t, e) { void 0 !== e.extras && "object" == typeof e.extras && Object.assign(t.userData, e.extras) } function eh(t, e) { if (t.updateMorphTargets(), void 0 !== e.weights) for (let i = 0, n = e.weights.length; i -1, s = n ? navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1] : -1; "undefined" == typeof createImageBitmap || i || n && s { const i = this.associations.get(t); null != i && this.associations.set(e, i); for (const [i, n] of t.children.entries()) s(n, e.children[i]) }; return s(i, n), n.name += "_instance_" + t.uses[e]++, n } _invokeOne(t) { const e = Object.values(this.plugins); e.push(this); for (let i = 0; i = 2 && p.setY(e, c[t * r + 1]), r >= 3 && p.setZ(e, c[t * r + 2]), r >= 4 && p.setW(e, c[t * r + 3]), r >= 5) throw new Error("THREE.GLTFLoader: Unsupported itemSize in sparse BufferAttribute.") } } return p })) } loadTexture(t) { const e = this.json, i = this.options, n = e.textures[t].source, s = e.images[n]; let r = this.textureLoader; if (s.uri) { const t = i.manager.getHandler(s.uri); null !== t && (r = t) } return this.loadTextureImage(t, n, r) } loadTextureImage(t, e, i) { const n = this, s = this.json, r = s.textures[t], a = s.images[e], o = (a.uri || a.bufferView) + ":" + r.sampler; if (this.textureCache[o]) return this.textureCache[o]; const l = this.loadImageSource(e, i).then((function (e) { e.flipY = !1, r.name && (e.name = r.name); const i = (s.samplers || {})[r.sampler] || {}; return e.magFilter = Yl[i.magFilter] || st, e.minFilter = Yl[i.minFilter] || at, e.wrapS = ql[i.wrapS] || J, e.wrapT = ql[i.wrapT] || J, n.associations.set(e, { textures: t }), e })).catch((function () { return null })); return this.textureCache[o] = l, l } loadImageSource(t, e) { const i = this.json, n = this.options; if (void 0 !== this.sourceCache[t]) return this.sourceCache[t].then((t => t.clone())); const s = i.images[t], r = self.URL || self.webkitURL; let a = s.uri || "", o = !1; if (void 0 !== s.bufferView) a = this.getDependency("bufferView", s.bufferView).then((function (t) { o = !0; const e = new Blob([t], { type: s.mimeType }); return a = r.createObjectURL(e), a })); else if (void 0 === s.uri) throw new Error("THREE.GLTFLoader: Image " + t + " is missing URI and bufferView"); const l = Promise.resolve(a).then((function (t) { return new Promise((function (i, s) { let r = i; !0 === e.isImageBitmapLoader && (r = function (t) { const e = new ue(t); e.needsUpdate = !0, i(e) }), e.load(Do.resolveURL(t, n.path), r, void 0, s) })) })).then((function (t) { var e; return !0 === o && r.revokeObjectURL(a), t.userData.mimeType = s.mimeType || ((e = s.uri).search(/\.jpe?g($|\?)/i) > 0 || 0 === e.search(/^data\:image\/jpeg/) ? "image/jpeg" : e.search(/\.webp($|\?)/i) > 0 || 0 === e.search(/^data\:image\/webp/) ? "image/webp" : "image/png"), t })).catch((function (t) { throw t })); return this.sourceCache[t] = l, l } assignTexture(t, e, i, n) { const s = this; return this.getDependency("texture", i.index).then((function (r) { if (void 0 !== i.texCoord && 0 != i.texCoord && ("aoMap" !== e || i.texCoord), s.extensions[_l]) { const t = void 0 !== i.extensions ? i.extensions[_l] : void 0; if (t) { const e = s.associations.get(r); r = s.extensions[_l].extendTexture(r, t), s.associations.set(r, e) } } return void 0 !== n && (r.encoding = n), t[e] = r, r })) } assignFinalMaterial(t) { const e = t.geometry; let i = t.material; const n = void 0 === e.attributes.tangent, s = void 0 !== e.attributes.color, r = void 0 === e.attributes.normal; if (t.isPoints) { const t = "PointsMaterial:" + i.uuid; let e = this.cache.get(t); e || (e = new Fa, Ri.prototype.copy.call(e, i), e.color.copy(i.color), e.map = i.map, e.sizeAttenuation = !1, this.cache.add(t, e)), i = e } else if (t.isLine) { const t = "LineBasicMaterial:" + i.uuid; let e = this.cache.get(t); e || (e = new Aa, Ri.prototype.copy.call(e, i), e.color.copy(i.color), this.cache.add(t, e)), i = e } if (n || s || r) { let t = "ClonedMaterial:" + i.uuid + ":"; i.isGLTFSpecularGlossinessMaterial && (t += "specular-glossiness:"), n && (t += "derivative-tangents:"), s && (t += "vertex-colors:"), r && (t += "flat-shading:"); let e = this.cache.get(t); e || (e = i.clone(), s && (e.vertexColors = !0), r && (e.flatShading = !0), n && (e.normalScale && (e.normalScale.y *= -1), e.clearcoatNormalScale && (e.clearcoatNormalScale.y *= -1)), this.cache.add(t, e), this.associations.set(e, this.associations.get(i))), i = e } i.aoMap && void 0 === e.attributes.uv2 && void 0 !== e.attributes.uv && e.setAttribute("uv2", e.attributes.uv), t.material = i } getMaterialType() { return ja } loadMaterial(t) { const e = this, i = this.json, n = this.extensions, s = i.materials[t]; let r; const a = {}, o = s.extensions || {}, l = []; if (o[cl]) { const t = n[cl]; r = t.getMaterialType(), l.push(t.extendParams(a, s, e)) } else if (o[fl]) { const t = n[fl]; r = t.getMaterialType(), l.push(t.extendParams(a, s, e)) } else { const i = s.pbrMetallicRoughness || {}; if (a.color = new ae(1, 1, 1), a.opacity = 1, Array.isArray(i.baseColorFactor)) { const t = i.baseColorFactor; a.color.fromArray(t), a.opacity = t[3] } void 0 !== i.baseColorTexture && l.push(e.assignTexture(a, "map", i.baseColorTexture, At)), a.metalness = void 0 !== i.metallicFactor ? i.metallicFactor : 1, a.roughness = void 0 !== i.roughnessFactor ? i.roughnessFactor : 1, void 0 !== i.metallicRoughnessTexture && (l.push(e.assignTexture(a, "metalnessMap", i.metallicRoughnessTexture)), l.push(e.assignTexture(a, "roughnessMap", i.metallicRoughnessTexture))), r = this._invokeOne((function (e) { return e.getMaterialType && e.getMaterialType(t) })), l.push(Promise.all(this._invokeAll((function (e) { return e.extendMaterialParams && e.extendMaterialParams(t, a) })))) } !0 === s.doubleSided && (a.side = 2); const h = s.alphaMode || "OPAQUE"; if ("BLEND" === h ? (a.transparent = !0, a.depthWrite = !1) : (a.transparent = !1, "MASK" === h && (a.alphaTest = void 0 !== s.alphaCutoff ? s.alphaCutoff : .5)), void 0 !== s.normalTexture && r !== Oi && (l.push(e.assignTexture(a, "normalMap", s.normalTexture)), a.normalScale = new Xt(1, 1), void 0 !== s.normalTexture.scale)) { const t = s.normalTexture.scale; a.normalScale.set(t, t) } return void 0 !== s.occlusionTexture && r !== Oi && (l.push(e.assignTexture(a, "aoMap", s.occlusionTexture)), void 0 !== s.occlusionTexture.strength && (a.aoMapIntensity = s.occlusionTexture.strength)), void 0 !== s.emissiveFactor && r !== Oi && (a.emissive = (new ae).fromArray(s.emissiveFactor)), void 0 !== s.emissiveTexture && r !== Oi && l.push(e.assignTexture(a, "emissiveMap", s.emissiveTexture, At)), Promise.all(l).then((function () { let i; return i = r === Bl ? n[cl].createMaterial(a) : new r(a), s.name && (i.name = s.name), th(i, s), e.associations.set(i, { materials: t }), s.extensions && Ql(n, i, s), i })) } createUniqueName(t) { const e = Bo.sanitizeNodeName(t || ""); let i = e; for (let t = 1; this.nodeNamesUsed[i]; ++t)i = e + "_" + t; return this.nodeNamesUsed[i] = !0, i } loadGeometries(t) { const e = this, i = this.extensions, n = this.primitiveCache; function s(t) { return i[al].decodePrimitive(t, e).then((function (i) { return oh(i, t, e) })) } const r = []; for (let i = 0, a = t.length; i 0 && eh(d, s), d.name = e.createUniqueName(s.name || "mesh_" + t), th(d, s), c.extensions && Ql(n, d, c), e.assignFinalMaterial(d), l.push(d) } for (let i = 0, n = l.length; i 1 ? new sa : 1 === e.length ? e[0] : new yi, a !== e[0]) for (let t = 0, i = e.length; t { const e = new Map; for (const [t, i] of s.associations) (t instanceof Ri || t instanceof ue) && e.set(t, i); return t.traverse((t => { const i = s.associations.get(t); null != i && e.set(t, i) })), e })(r), r })) } } function ah(t, e, i, n) { const s = i.nodes[t]; return n.getDependency("node", t).then((function (t) { if (void 0 === s.skin) return t; let e; return n.getDependency("skin", s.skin).then((function (t) { e = t; const i = []; for (let t = 0, s = e.joints.length; t { const e = t.draco, a = new e.Decoder, o = new e.DecoderBuffer; o.Init(new Int8Array(n), n.byteLength); try { const t = function (t, e, n, s) { const r = s.attributeIDs, a = s.attributeTypes; let o, l; const h = e.GetEncodedGeometryType(n); if (h === t.TRIANGULAR_MESH) o = new t.Mesh, l = e.DecodeBufferToMesh(n, o); else { if (h !== t.POINT_CLOUD) throw new Error("THREE.DRACOLoader: Unexpected geometry type."); o = new t.PointCloud, l = e.DecodeBufferToPointCloud(n, o) } if (!l.ok() || 0 === o.ptr) throw new Error("THREE.DRACOLoader: Decoding failed: " + l.error_msg()); const c = { index: null, attributes: [] }; for (const n in r) { const l = self[a[n]]; let h, d; if (s.useUniqueIDs) d = r[n], h = e.GetAttributeByUniqueId(o, d); else { if (d = e.GetAttributeId(o, t[r[n]]), -1 === d) continue; h = e.GetAttribute(o, d) } c.attributes.push(i(t, e, o, n, l, h)) } return h === t.TRIANGULAR_MESH && (c.index = function (t, e, i) { const n = 3 * i.num_faces(), s = 4 * n, r = t._malloc(s); e.GetTrianglesUInt32Array(i, s, r); const a = new Uint32Array(t.HEAPF32.buffer, r, n).slice(); return t._free(r), { array: a, itemSize: 1 } }(t, e, o)), t.destroy(o), c }(e, a, o, r), n = t.attributes.map((t => t.array.buffer)); t.index && n.push(t.index.array.buffer), self.postMessage({ type: "decode", id: s.id, geometry: t }, n) } catch (t) { self.postMessage({ type: "error", id: s.id, error: t.message }) } finally { e.destroy(o), e.destroy(a) } })) } } } const dh = new lo, uh = new class extends co { constructor(t) { super(t), this.decoderPath = "", this.decoderConfig = {}, this.decoderBinary = null, this.decoderPending = null, this.workerLimit = 4, this.workerPool = [], this.workerNextTaskID = 1, this.workerSourceURL = "", this.defaultAttributeIDs = { position: "POSITION", normal: "NORMAL", color: "COLOR", uv: "TEX_COORD" }, this.defaultAttributeTypes = { position: "Float32Array", normal: "Float32Array", color: "Float32Array", uv: "Float32Array" } } setDecoderPath(t) { return this.decoderPath = t, this } setDecoderConfig(t) { return this.decoderConfig = t, this } setWorkerLimit(t) { return this.workerLimit = t, this } load(t, e, i, n) { const s = new po(this.manager); s.setPath(this.path), s.setResponseType("arraybuffer"), s.setRequestHeader(this.requestHeader), s.setWithCredentials(this.withCredentials), s.load(t, (t => { const i = { attributeIDs: this.defaultAttributeIDs, attributeTypes: this.defaultAttributeTypes, useUniqueIDs: !1 }; this.decodeGeometry(t, i).then(e).catch(n) }), i, n) } decodeDracoFile(t, e, i, n) { const s = { attributeIDs: i || this.defaultAttributeIDs, attributeTypes: n || this.defaultAttributeTypes, useUniqueIDs: !!i }; this.decodeGeometry(t, s).then(e) } decodeGeometry(t, e) { for (const t in e.attributeTypes) { const i = e.attributeTypes[t]; void 0 !== i.BYTES_PER_ELEMENT && (e.attributeTypes[t] = i.name) } const i = JSON.stringify(e); if (hh.has(t)) { const e = hh.get(t); if (e.key === i) return e.promise; if (0 === t.byteLength) throw new Error("THREE.DRACOLoader: Unable to re-decode a buffer with different settings. Buffer has already been transferred.") } let n; const s = this.workerNextTaskID++, r = t.byteLength, a = this._getWorker(s, r).then((i => (n = i, new Promise(((i, r) => { n._callbacks[s] = { resolve: i, reject: r }, n.postMessage({ type: "decode", id: s, taskConfig: e, buffer: t }, [t]) }))))).then((t => this._createGeometry(t.geometry))); return a.catch((() => !0)).then((() => { n && s && this._releaseTask(n, s) })), hh.set(t, { key: i, promise: a }), a } _createGeometry(t) { const e = new qi; t.index && e.setIndex(new Fi(t.index.array, 1)); for (let i = 0; i { i.load(t, e, void 0, n) })) } preload() { return this._initDecoder(), this } _initDecoder() { if (this.decoderPending) return this.decoderPending; const t = "object" != typeof WebAssembly || "js" === this.decoderConfig.type, e = []; return t ? e.push(this._loadLibrary("draco_decoder.js", "text")) : (e.push(this._loadLibrary("draco_wasm_wrapper.js", "text")), e.push(this._loadLibrary("draco_decoder.wasm", "arraybuffer"))), this.decoderPending = Promise.all(e).then((e => { const i = e[0]; t || (this.decoderConfig.wasmBinary = e[1]); const n = ch.toString(), s = ["/* draco decoder */", i, "", "/* worker */", n.substring(n.indexOf("{") + 1, n.lastIndexOf("}"))].join("\n"); this.workerSourceURL = URL.createObjectURL(new Blob([s])) })), this.decoderPending } _getWorker(t, e) { return this._initDecoder().then((() => { if (this.workerPool.length e._taskLoad ? -1 : 1 })); const i = this.workerPool[this.workerPool.length - 1]; return i._taskCosts[t] = e, i._taskLoad += e, i })) } _releaseTask(t, e) { t._taskLoad -= t._taskCosts[e], delete t._callbacks[e], delete t._taskCosts[e] } debug() { } dispose() { for (let t = 0; t =2.0 are supported."))); const l = new rh(o, { path: e || this.resourcePath || "", crossOrigin: this.crossOrigin, requestHeader: this.requestHeader, manager: this.manager, ktx2Loader: this.ktx2Loader, meshoptDecoder: this.meshoptDecoder }); l.fileLoader.setRequestHeader(this.requestHeader); for (let t = 0; t = 0 && a[e] } } l.setExtensions(r), l.setPlugins(a), l.parse(i, n) } parseAsync(t, e) { const i = this; return new Promise((function (n, s) { i.parse(t, e, n, s) })) } }(dh), mh = new class extends fo { constructor(t) { super(t), this.type = ct } parse(t) { const e = function (t, e, i) { e = e || 1024; let n = t.pos, s = -1, r = 0, a = "", o = String.fromCharCode.apply(null, new Uint16Array(t.subarray(n, n + 128))); for (; 0 > (s = o.indexOf("\n")) && r = t.byteLength || !(o = e(t))) return -1; if (!(l = o.match(/^#\?(\S+)/))) return -1; for (a.valid |= 1, a.programtype = l[1], a.string += o + "\n"; o = e(t), !1 !== o;)if (a.string += o + "\n", "#" !== o.charAt(0)) { if ((l = o.match(i)) && (a.gamma = parseFloat(l[1])), (l = o.match(n)) && (a.exposure = parseFloat(l[1])), (l = o.match(s)) && (a.valid |= 2, a.format = l[1]), (l = o.match(r)) && (a.valid |= 4, a.height = parseInt(l[1], 10), a.width = parseInt(l[2], 10)), 2 & a.valid && 4 & a.valid) break } else a.comments += o + "\n"; return 2 & a.valid && 4 & a.valid ? a : -1 }(s); if (-1 !== r) { const t = r.width, e = r.height, a = function (t, e, i) { const n = e; if (n 32767 || 2 !== t[0] || 2 !== t[1] || 128 & t[2]) return new Uint8Array(t); if (n !== (t[2] 0 && a t.byteLength) return -1; if (l[0] = t[a++], l[1] = t[a++], l[2] = t[a++], l[3] = t[a++], 2 != l[0] || 2 != l[1] || (l[2] 128; if (n && (e -= 128), 0 === e || i + e > o) return -1; if (n) { const n = t[a++]; for (let t = 0; t { t.frustumCulled = !1, t.isMesh && (t.castShadow = !0, t.receiceShadow = !1, t.material.map && (t.material.map.colorSpace = Et, t.material.map.format = ut, t.material.map.type = ot, t.material.needsUpdate = !0)), "Display_ActiveArea" == t.name && (this.screen = t, this.screen.type = "default", this.screen.material.color0 = this.screen.material.color.clone(), this.screen.material.envMapIntensity0 = this.screen.material.envMapIntensity, this.screen.material.emissive0 = this.screen.material.emissive, this.screen.material.emissiveIntensity0 = this.screen.material.emissiveIntensity), "Display_ActiveArea_Front" == t.name && (this.screenFront = t, this.screenFront.type = "front", this.screenFront.material.color0 = this.screenFront.material.color.clone(), this.screenFront.material.emissive0 = this.screenFront.material.emissive, this.screenFront.material.envMapIntensity0 = this.screenFront.material.envMapIntensity, this.screenFront.material.emissiveIntensity0 = this.screenFront.material.emissiveIntensity), "Display_ActiveArea_cam" == t.name && t.material.userData.screenToggleStatus && (this.screenCam = t, this.screenCam.type = "cam", this.screenCam.material.color0 = this.screenCam.material.color.clone(), this.screenCam.material.emissive0 = this.screenCam.material.emissive, this.screenCam.material.envMapIntensity0 = this.screenCam.material.envMapIntensity, this.screenCam.material.emissiveIntensity0 = this.screenCam.material.emissiveIntensity) })), this.handleScreen = function (t, e, i = !1) { if (null != t) switch (e) { case 1: t.material.color = t.material.color0.clone(), t.material.emissiveIntensity = t.material.emissiveIntensity0, t.material.envMapIntensity = t.material.envMapIntensity0; break; case 0: t.material.emissiveIntensity = 0, t.material.envMapIntensity = i ? 0 : t.material.envMapIntensity0, (this.modelData.screenOffColor || vh.indexOf(this.modelName) > -1) && t.material.color.set(this.modelData.screenOffColor) } }, this.screenDefaultOn = function (t = !1) { this.modelOption.screen && (this.handleScreen(this.screen, 1), this.screenCam && this.handleScreen(this.screenCam, 1)) }, this.screenDefaultOff = function (t = !1) { this.handleScreen(this.screen, 0, t), this.screenCam && this.handleScreen(this.screenCam, 0) }, this.screenFrontOn = function (t = !1) { this.modelOption.screen && this.handleScreen(this.screenFront, 1) }, this.screenFrontOff = function (t = !1) { this.handleScreen(this.screenFront, 0) }, this.screenOn = function (t = !1) { this.modelOption.screen && (this.screenDefaultOn(), this.screenFrontOn()) }, this.screenOff = function (t = !1) { this.screenDefaultOff(), this.screenFrontOff() }, this.screenUpdate = function () { if ("on" === this.modelOption.screenMode) return this.screenDefaultOn(), void this.screenFrontOn(); if ("off" === this.modelOption.screenMode) return this.screenDefaultOff(), void this.screenFrontOff(); if (!this.modelData.isSample) if (this.mixer) { const t = this.mixer.getTrack(); switch (this.toggleScreenDirection) { case 1: t > .1 ? this.modelOption.screen ? this.screenDefaultOn() : this.screenDefaultOff() : this.screenDefaultOff(!0), t .1 && this.modelOption.screen ? this.screenFrontOn() : this.screenFrontOff(), t 0 ? 1 : -1, h = this; let c; this.isAnimation = !0, this.frame = requestAnimationFrame((function t(n) { null == c && (c = n); const s = a + (n - c) * (o - a) / r; if (-1 == l && s = o || s >= h.duration || s i.toggleActionSetTime(.14, 400, !0)), 350) }, 1700: function () { i.statusToggleInactive(!0), setTimeout((() => i.setPosition({ angleX: 0, angleY: u(-100), zoom: 30 }, 600, void 0, void 0, void 0, !0)), 30) }, 3400: function () { i.setPosition({ angleY: u(-360) }, 1400, void 0, void 0, void 0, !0) }, 5100: function () { } } }, 2: { actions: { 0: function () { i.setPosition({ angleX: u(-40), angleY: u(30), panY: -2 }, 560, void 0, void 0, void 0, !0), setTimeout((() => i.toggleActionSetTime(.14, 400, !0)), 70) }, 1600: function () { i.toggleActionSetTime(0, 300, !0), i.setPosition({ angleX: 0, angleY: u(-132), panY: 0 }, 600, void 0, void 0, void 0, !0) }, 3200: function () { i.setPosition({ angleY: u(-360) }, 650, void 0, void 0, void 0, !0) }, 5e3: function () { } } }, 3: { actions: { 0: function () { i.setPosition({ angleX: u(-40), angleY: u(30), panY: -2 }, 560, void 0, void 0, void 0, !0), setTimeout((() => i.toggleActionSetTime(.15, 0, !0)), 50), setTimeout((() => i.toggleActionSetTime(.21, 130, !0)), 50), setTimeout((() => i.toggleActionSetTime(.38, 320, !0)), 150) }, 1600: function () { i.toggleActionSetTime(0, 300, !0), i.setPosition({ angleX: 0, angleY: u(-132), panY: 0 }, 600, void 0, void 0, void 0, !0) }, 3200: function () { i.setPosition({ angleY: u(-360) }, 650, void 0, void 0, void 0, !0) }, 5e3: function () { } } }, "20251h": { startPose: function () { }, actions: { 0: function () { i.setPosition({ angleY: u(-135) }, 560, void 0, void 0, void 0, !0) }, 600: function () { } } } }, fold: { default: { actions: { 0: function () { i.statusToggleActive(!0) }, 1700: function () { i.setPosition({ angleY: u(138) }, 500, void 0, void 0, void 0, !0) }, 3400: function () { i.statusToggleInactive(!0), i.setPosition({ angleX: 0, angleY: 0, angleZ: 0 }, 500, void 0, void 0, void 0, !0) }, 5100: function () { } } }, 2: { actions: { 0: function () { i.setPosition({ angleX: 0, angleY: u(-245), angleZ: 0, angleLimit: !1 }, 800, "easeInOutSine", void 0, void 0, !0) }, 800: function () { i.setPosition({ angleX: 0, angleY: u(-360), angleZ: 0, angleLimit: !1 }, 568, "easeNone", void 0, void 0, !0), i.toggleActionSetTime(1, 560, !0) }, 1360: function () { i.setPosition({ angleX: 0, angleY: u(-390), angleZ: 0, angleLimit: !1 }, 1120, "easeInSine", void 0, void 0, !0) }, 2480: function () { i.setPosition({ angleX: 0, angleY: u(-720), angleZ: 0, angleLimit: !1 }, 880, "easeOutQuad", void 0, void 0, !0), i.toggleActionSetTime(.65, 240, !0) }, 2720: function () { i.toggleActionSetTime(0, 680, !0) }, 4e3: function () { } } }, 3: { startPose: function () { i.toggleActionSetTime(0, 0, !0), i.screenUpdate() }, actions: { 0: function () { i.setPosition({ angleX: 0, angleY: u(0), angleZ: 0, angleLimit: !1 }, 600, "easeInOutSine", void 0, void 0, !0) }, 700: function () { i.toggleActionSetTime(1, 600, !0) }, 1400: function () { i.setPosition({ angleX: 0, angleY: u(-180), angleZ: 0, angleLimit: !1 }, 800, "easeOut", void 0, void 0, !0) }, 2e3: function () { i.toggleActionSetTime(.7, 400, !0), i.setPosition({ angleX: 0, angleY: u(-200), angleZ: 0, angleLimit: !1 }, 400, "easeInOut", void 0, void 0, !0) }, 2700: function () { } } } }, flip: { default: { actions: { 0: function () { i.setPosition({ angleX: u(-67), angleY: u(4), angleZ: u(54) }, 500, void 0, void 0, void 0, !0), i.toggleActionSetTime(.58, 350, !0) }, 1700: function () { i.setPosition({ angleX: 0, angleY: u(0), angleZ: u(0) }, 500, void 0, void 0, void 0, !0), i.statusToggleActive(!0) }, 3400: function () { i.reset(), i.statusToggleInactive(!0) }, 5100: function () { } } }, 2: { startPose: function () { i.toggleActionSetTime(1, 0, !0), i.screenUpdate() }, actions: { 0: function () { i.cameraControls.rotateLeft(u(-360), 1920, "easeInSine") }, 960: function () { i.setPosition({ angleX: u(-90), poseY: -1.5 }, 960, "easeOutQuint", void 0, void 0, !0), i.toggleActionSetTime(.457, 320, !0) }, 1840: function () { i.cameraControls.rotateLeft(u(-70), 1120, "easeNone") }, 2960: function () { i.cameraControls.rotateLeft(u(-360), 1040, "easeOutQuart") }, 3040: function () { i.setPosition({ angleX: 0, poseY: 0 }, 720, "easeInOutQuad", void 0, void 0, !0), i.toggleActionSetTime(0, 640, !0) }, 4e3: function () { } } }, 3: { startPose: function () { i.toggleActionSetTime(0, 0, !0), i.screenUpdate() }, actions: { 0: function () { i.toggleActionSetTime(1, 800, !0), i.setPosition({ angleX: 0, angleY: u(0), angleZ: 0, angleLimit: !1 }, 800, "easeInOutSine", void 0, void 0, !0) }, 900: function () { i.toggleActionSetTime(0, 800, !0) }, 1800: function () { i.setPosition({ angleX: 0, angleY: u(-180), angleZ: 0, angleLimit: !1 }, 800, "easeInOutSine", void 0, void 0, !0) }, 2700: function () { } } } } }; return n[t] ? n[t][e] ? n[t][e] : n[t].default : void 0 } new yh({ angleX: 0, angleY: 0, angleZ: 0, zoom: E, panX: 0, panY: 0, angleLimit: !1 }); const Mh = "easeOutCubic", wh = !0; let Sh; class Th extends Rt { constructor(t) { super(), this.isRun = !1, this.controls = t, this.controls.cameraControls = this, this.camera = this.controls.object, this.productObject = this.controls.productObject, this.target = new _e(this.controls.productObject.x, 0, this.controls.productObject.z), this.callback = null, this.duration = 700, this.startTime = Date.now(), this.easing = Mh, this.angleLimit = wh, this.startAngle, this.endAngle, this.startSpherical = new jo, this.prevSpherical = new jo, this.endSpherical = new jo, this.endPosition = new _e, this.prevPosition = new _e, this.spherical = new jo, this.sphericalDelta = new jo, this.angle = null, Sh = (new ve).setFromUnitVectors(this.camera.up, new _e(0, 1, 0)) } rotateLeft(t, e, i, n, s, r = !1) { if (null == t) return; s = s || {}; const a = new _e; if (a.copy(this.camera.position).sub(this.target), a.applyQuaternion(Sh), this.startTime = Date.now(), this.angle = -1 * t, this.startSpherical = (new jo).setFromVector3(a), this.endSpherical = (new jo).copy(this.startSpherical), this.endSpherical.theta = this.angle, this.duration = null == e ? 700 : e, this.easing = null == i ? Mh : i, this.angleLimit = null == s.angleLimit ? wh : s.angleLimit, this.spherical.setFromVector3(a), 0 == this.duration) return this.spherical.theta += this.angle, void this.camera.position.setFromSpherical(this.spherical); this.rotateStart() } rotateStart() { this.isRun = !0 } rotateEnd() { this.isRun = !1 } update() { if (!this.isRun) return; let t = (Date.now() - this.startTime) / this.duration; t > 1 && (t = 1); let e = (this.endSpherical.theta - this.startSpherical.theta) * g(this.easing, t), i = this.startSpherical.clone(); i.theta = this.startSpherical.theta + e, this.camera.position.setFromSpherical(i), (i.thera == this.endSpherical.theta || t >= 1) && this.rotateEnd() } reset() { this.rotateLeft(0, 500) } clear() { } } const Ah = "easeOutCubic", Eh = 700, Ph = !0, Ch = !1, Lh = !0; class Dh { constructor(t, e) { this.enabled = !0, this.isZoom = !1, this.izRotation = !1, this.isPan = !1, this.isRun = !1, this.controls = t, this.controls.setPositionControls = this, this.productObject = e || this.controls.productObject, this.camera = this.controls.object, this.target = this.controls.target, this.allowCanceled = !0, this.callback = null, this.duration = Eh, this.startTime = Date.now(), this.easing = Ah, this.dampingFactor = .05, this.calcAngle = Ch, this.angleLimit = Lh, this.startPosition = new yh, this.endPosition = new yh, this.prevRotate = new _e, this.prevPosition = new _e, this.cameraLock = !!this.controls.cameraControls.isRun } setPosition(t, e, i, n, s, r = !1) { if (s = s || {}, this.isRun = !0, this.isZoom = void 0 !== t.zoom, this.callback = n, this.easing = null == i ? Ah : i, this.duration = null == e ? Eh : e, this.allowCanceled = void 0 !== s.allowCanceled ? s.allowCanceled : this.allowCanceled, this.cameraLock = !!this.controls.cameraControls.isRun, this.set(t), 0 == this.duration) return this.productObject.rotation.x = this.endPosition.angleX, this.productObject.rotation.y = this.endPosition.angleY, this.productObject.rotation.z = this.endPosition.angleZ, this.productObject.position.x = this.endPosition.poseX, this.productObject.position.y = this.endPosition.poseY, this.productObject.position.z = this.endPosition.poseZ, this.camera.position.x = this.endPosition.panX, this.camera.position.y = this.endPosition.panY, this.camera.position.z = this.endPosition.zoom, this.target.x = this.endPosition.panX, this.target.y = this.endPosition.panY, void this.positionEnd(); this.positionStart() } set(t) { this.angleLimit = void 0 !== t.angleLimit ? t.angleLimit : Lh, this.startPosition.angleX = this.angleLimit ? p(this.productObject.rotation.x) : this.productObject.rotation.x, this.startPosition.angleY = this.angleLimit ? p(this.productObject.rotation.y) : this.productObject.rotation.y, this.startPosition.angleZ = this.angleLimit ? p(this.productObject.rotation.z) : this.productObject.rotation.z, this.startPosition.poseX = this.productObject.position.x, this.startPosition.poseY = this.productObject.position.y, this.startPosition.poseZ = this.productObject.position.z, this.startPosition.panX = this.camera.position.x || 0, this.startPosition.panY = this.camera.position.y || 0, this.startPosition.zoom = this.camera.position.z || E, this.endPosition.angleX = void 0 !== t.angleX ? t.angleX : this.startPosition.angleX, this.endPosition.angleY = void 0 !== t.angleY ? t.angleY : this.startPosition.angleY, this.endPosition.angleZ = void 0 !== t.angleZ ? t.angleZ : this.startPosition.angleZ, this.endPosition.poseX = void 0 !== t.poseX ? t.poseX : this.startPosition.poseX, this.endPosition.poseY = void 0 !== t.poseY ? t.poseY : this.startPosition.poseY, this.endPosition.poseZ = void 0 !== t.poseZ ? t.poseZ : this.startPosition.poseZ, this.endPosition.panX = void 0 !== t.panX ? t.panX : this.startPosition.panX, this.endPosition.panY = void 0 !== t.panY ? t.panY : this.startPosition.panY, this.endPosition.zoom = void 0 !== t.zoom ? t.zoom : this.startPosition.zoom, this.calcAngle = void 0 !== t.calcAngle ? t.calcAngle : Ch } positionStart() { this.startTime = Date.now(), this.controls.setPositionStart() } positionEnd() { this.isRun = !1, this.callback && this.callback(), this.startPosition = this.endPosition.clone(), this.allowCanceled = Ph, this.calcAngle = Ch, this.angleLimit = Lh, this.callback = null } stop() { this.positionEnd() } update() { if (!this.enabled) return; if (!this.isRun) return; const t = (Date.now() - this.startTime) / this.duration; if (!0 !== this.cameraLock && this.camera.position.z !== this.endPosition.zoom && this.isZoom) { const e = this.startPosition.zoom; let i = this.endPosition.zoom - e, n = i * g(this.easing, t); n = Math.abs(n) > Math.abs(i) || t >= 1 ? i : n, this.camera.position.z = e + n } if (this.productObject.rotation.x !== this.endPosition.angleX || this.productObject.rotation.y !== this.endPosition.angleY || this.productObject.rotation.z !== this.endPosition.angleZ) { let e = this.endPosition.angleX - this.startPosition.angleX, i = this.endPosition.angleY - this.startPosition.angleY, n = this.endPosition.angleZ - this.startPosition.angleZ, s = e * g(this.easing, t), r = i * g(this.easing, t), a = n * g(this.easing, t); s = Math.abs(s) > Math.abs(e) || t >= 1 ? e : s, r = Math.abs(r) > Math.abs(i) || t >= 1 ? i : r, a = Math.abs(a) > Math.abs(n) || t >= 1 ? n : a, this.prevRotate.x = this.startPosition.angleX + s, this.prevRotate.y = this.startPosition.angleY + r, this.prevRotate.z = this.startPosition.angleZ + a, this.angleLimit && (this.prevRotate.x = p(this.prevRotate.x), this.prevRotate.y = p(this.prevRotate.y), this.prevRotate.z = p(this.prevRotate.z)), this.productObject.rotation.x = this.prevRotate.x, this.productObject.rotation.z = this.prevRotate.z, this.productObject.rotation.y = this.calcAngle ? m(this.prevRotate, this.productObject.position.x, this.camera.position.z) : this.prevRotate.y } if ((this.camera.position.x !== this.endPosition.panX || this.camera.position.y !== this.endPosition.panY) && !0 !== this.cameraLock) { let e = this.endPosition.panX - this.startPosition.panX, i = this.endPosition.panY - this.startPosition.panY, n = e * g(this.easing, t), s = i * g(this.easing, t); n = Math.abs(n) > Math.abs(e) || t >= 1 ? e : n, s = Math.abs(s) > Math.abs(i) || t >= 1 ? i : s, this.target.x = this.startPosition.panX + n, this.target.y = this.startPosition.panY + s, this.camera.position.x = this.startPosition.panX + n, this.camera.position.y = this.startPosition.panY + s } if (this.productObject.position.x !== this.endPosition.poseX || this.productObject.position.y !== this.endPosition.poseY || this.productObject.position.z !== this.endPosition.poseZ) { let e = this.endPosition.poseX - this.startPosition.poseX, i = this.endPosition.poseY - this.startPosition.poseY, n = this.endPosition.poseZ - this.startPosition.poseZ, s = e * g(this.easing, t), r = i * g(this.easing, t), a = n * g(this.easing, t); s = Math.abs(s) > Math.abs(e) || t >= 1 ? e : s, r = Math.abs(r) > Math.abs(i) || t >= 1 ? i : r, a = Math.abs(a) > Math.abs(n) || t >= 1 ? n : a, this.prevPosition.x = this.startPosition.poseX + s, this.prevPosition.y = this.startPosition.poseY + r, this.prevPosition.z = this.startPosition.poseZ + a, this.productObject.position.x = this.prevPosition.x, this.productObject.position.y = this.prevPosition.y, this.productObject.position.z = this.prevPosition.z } (this.camera.position.z == this.endPosition.zoom && !0 !== this.cameraLock && this.productObject.rotation.x == this.endPosition.angleX && this.prevRotate.y == this.endPosition.angleY && this.productObject.rotation.z == this.endPosition.angleZ && this.camera.position.x == this.endPosition.panX && this.camera.position.y == this.endPosition.panY && !0 !== this.cameraLock || t > 1) && (this.isRun = !1, this.positionEnd()) } rotateLeft(t) { } rotateRight(t) { } } class Ih { constructor(t, e) { this.enabled = !0, this.isZoom = !1, this.izRotation = !1, this.isPan = !1, this.isRun = !1, this.controls = t, this.controls.setPositionController.list.push(this), this.gltfObject = e, this.camera = this.controls.object, this.target = this.controls.target, this.allowCanceled = !0, this.callback = null, this.duration = Eh, this.startTime = Date.now(), this.easing = Ah, this.dampingFactor = .05, this.calcAngle = Ch, this.angleLimit = Lh, this.startPosition = new yh, this.endPosition = new yh, this.prevRotate = new _e, this.prevPosition = new _e } setPosition(t, e, i, n, s, r = !1) { if (s = s || {}, this.isRun = !0, this.isZoom = void 0 !== t.zoom, this.callback = n, this.easing = null == i ? Ah : i, this.duration = null == e ? Eh : e, this.allowCanceled = void 0 !== s.allowCanceled ? s.allowCanceled : this.allowCanceled, this.set(t), 0 == this.duration) return this.gltfObject.rotation.x = this.endPosition.angleX, this.gltfObject.rotation.y = this.endPosition.angleY, this.gltfObject.rotation.z = this.endPosition.angleZ, this.gltfObject.position.x = this.endPosition.poseX, this.gltfObject.position.y = this.endPosition.poseY, this.gltfObject.position.z = this.endPosition.poseZ, void this.positionEnd(); this.positionStart() } set(t) { this.angleLimit = void 0 !== t.angleLimit ? t.angleLimit : Lh, this.startPosition.angleX = this.angleLimit ? p(this.gltfObject.rotation.x) : this.gltfObject.rotation.x, this.startPosition.angleY = this.angleLimit ? p(this.gltfObject.rotation.y) : this.gltfObject.rotation.y, this.startPosition.angleZ = this.angleLimit ? p(this.gltfObject.rotation.z) : this.gltfObject.rotation.z, this.startPosition.poseX = this.gltfObject.position.x, this.startPosition.poseY = this.gltfObject.position.y, this.startPosition.poseZ = this.gltfObject.position.z, this.endPosition.angleX = void 0 !== t.angleX ? t.angleX : this.startPosition.angleX, this.endPosition.angleY = void 0 !== t.angleY ? t.angleY : this.startPosition.angleY, this.endPosition.angleZ = void 0 !== t.angleZ ? t.angleZ : this.startPosition.angleZ, this.endPosition.poseX = void 0 !== t.poseX ? t.poseX : this.startPosition.poseX, this.endPosition.poseY = void 0 !== t.poseY ? t.poseY : this.startPosition.poseY, this.endPosition.poseZ = void 0 !== t.poseZ ? t.poseZ : this.startPosition.poseZ, this.calcAngle = void 0 !== t.calcAngle ? t.calcAngle : Ch } positionStart() { this.startTime = Date.now(), this.controls.setPositionStart() } positionEnd() { this.isRun = !1, this.callback && this.callback(), this.startPosition = this.endPosition.clone(), this.allowCanceled = Ph, this.calcAngle = Ch, this.angleLimit = Lh, this.callback = null } stop() { this.positionEnd() } update() { if (!this.enabled) return; if (!this.isRun) return; const t = (Date.now() - this.startTime) / this.duration; if (this.gltfObject.rotation.x !== this.endPosition.angleX || this.gltfObject.rotation.y !== this.endPosition.angleY || this.gltfObject.rotation.z !== this.endPosition.angleZ) { let e = this.endPosition.angleX - this.startPosition.angleX, i = this.endPosition.angleY - this.startPosition.angleY, n = this.endPosition.angleZ - this.startPosition.angleZ, s = e * g(this.easing, t), r = i * g(this.easing, t), a = n * g(this.easing, t); s = Math.abs(s) > Math.abs(e) || t >= 1 ? e : s, r = Math.abs(r) > Math.abs(i) || t >= 1 ? i : r, a = Math.abs(a) > Math.abs(n) || t >= 1 ? n : a, this.prevRotate.x = this.startPosition.angleX + s, this.prevRotate.y = this.startPosition.angleY + r, this.prevRotate.z = this.startPosition.angleZ + a, this.angleLimit && (this.prevRotate.x = p(this.prevRotate.x), this.prevRotate.y = p(this.prevRotate.y), this.prevRotate.z = p(this.prevRotate.z)), this.gltfObject.rotation.x = this.prevRotate.x, this.gltfObject.rotation.z = this.prevRotate.z, this.gltfObject.rotation.y = this.calcAngle ? m(this.prevRotate, this.gltfObject.position.x, this.camera.position.z) : this.prevRotate.y } if (this.gltfObject.position.x !== this.endPosition.poseX || this.gltfObject.position.y !== this.endPosition.poseY || this.gltfObject.position.z !== this.endPosition.poseZ) { let e = this.endPosition.poseX - this.startPosition.poseX, i = this.endPosition.poseY - this.startPosition.poseY, n = this.endPosition.poseZ - this.startPosition.poseZ, s = e * g(this.easing, t), r = i * g(this.easing, t), a = n * g(this.easing, t); s = Math.abs(s) > Math.abs(e) || t >= 1 ? e : s, r = Math.abs(r) > Math.abs(i) || t >= 1 ? i : r, a = Math.abs(a) > Math.abs(n) || t >= 1 ? n : a, this.prevPosition.x = this.startPosition.poseX + s, this.prevPosition.y = this.startPosition.poseY + r, this.prevPosition.z = this.startPosition.poseZ + a, this.gltfObject.position.x = this.prevPosition.x, this.gltfObject.position.y = this.prevPosition.y, this.gltfObject.position.z = this.prevPosition.z } (this.gltfObject.rotation.x == this.endPosition.angleX && this.prevRotate.y == this.endPosition.angleY && this.gltfObject.rotation.z == this.endPosition.angleZ || t > 1) && (this.isRun = !1, this.positionEnd()) } } class Rh extends sa { constructor(t, e, { container: i, scene: n, renderer: s, camera: r, viewer: a }) { super(), this.OPTIONS = t, this.productData = e, this.container = i, this.scene = n, this.renderer = s, this.camera = r, this.viewer = a, this.cameraControls = null, this.positionControls = null, this.isInit = !1, this.frustumCulled = !1 } init() { this.isInit || (this.cameraControls = new cameraControls(this.viewer.controls), this.positionControls = new positionControls(this.viewer.controls), this.isInit = !0) } setViewerEncoding() { "sRGB" == this.productData.getModelData(this.children[0].modelName)["output-rendering"] ? (this.renderer.toneMapping = ACESFilmicToneMapping, this.renderer.toneMappingExposure = 1.2, this.renderer.textureEncoding = sRGBEncoding, this.renderer.outputEncoding = sRGBEncoding) : (this.renderer.toneMapping = NoToneMapping, this.renderer.toneMappingExposure = 1, this.renderer.textureEncoding = LinearEncoding, this.renderer.outputEncoding = LinearEncoding) } getTargetObject(t) { return this.children.find((e => e.modelName === t)) } } const Oh = {}, Nh = {}; class zh { constructor(t, e, { container: i, scene: n, renderer: s, camera: r, viewer: a }) { const o = this; this.enabled = !1, this.userControl = !1, this.OPTIONS = t, this.productData = e, this.container = i, this.viewer = a, this.scene = n, this.renderer = s, this.camera = r, this.product = e.product, this.defaultModelName = e.getDefaultModelName(), this.defaultColorName = e.getDefaultColorName(this.defaultModelName), this.positionControls = null, this.cameraControls = null, this.modelData = null, this.colorName = null, this.modelScale = null, this.modelGroup = new Rh(this.OPTIONS, this.productData, { scene: this.scene, renderer: this.renderer, camera: this.camera, container: this.container, uiControls: this.uiControls, viewer: this }), this.modelGroup.rotation.set(this.OPTIONS.y, this.OPTIONS.x, 0), this.isInit = !1, this.isProductChange = !1, this.isMixerRun = !1, this.environmentHdrName = null, this.introPoseReady = !1, this.introPoseEnd = !1, this.modelPathBackup = this.OPTIONS.assetsPath + "models/", this.modelPathMath = { [this.modelPathBackup]: Object.values(A)[0] }, this.toggleScreenDirection = 0, this.initPosition = new yh({ angleX: this.OPTIONS.y || 0, angleY: this.OPTIONS.x || 0, angleZ: 0, zoom: f(this.OPTIONS.zoom) || E, panX: 0, panY: 0, angleLimit: !1 }), this.initPosition0 = this.initPosition.clone(), this.startPosition = this.initPosition.clone(), this.introPoseUsedData = JSON.parse(l(R)) || {}, this.autoRotation = function () { const t = 1700, e = !1; let i = {}; return { duration: t, allowCanceled: e, isRunning: function () { return !!Object.values(i).find((t => t && t.isRun)) }, run: function (n) { if (!o.OPTIONS.getModelOption(n).autoRotation || i[n] && i[n].isRun) return; i[n] && i[n].reserve && clearTimeout(i[n].reserve), i[n] && i[n].animation && cancelAnimationFrame(i[n].animation), i[n] = { isRun: !0, reserve: {}, animation: {} }; const s = this, r = o.modelGroup.getTargetObject(n), a = r.rotation.y; let l; function h(c) { if (null == l && (l = c), !s.isRunning()) return cancelAnimationFrame(i[n].animation), i[n].isRun = !1, s.duration = t, s.allowCanceled = e, void (o.userControl || o.setPositionRelay.isRunning() || o.active()); const d = g("easeOutSine", (c - l) / s.duration); if (r.rotation.y = p(a + 2 * Math.PI * d), d >= 1 || c - l >= s.duration || r.rotation.y >= a + 2 * Math.PI) return cancelAnimationFrame(i[n].animation), i[n].isRun = !1, s.duration = t, s.allowCanceled = e, r.rotation.y = p(a + 2 * Math.PI), void (o.userControl || o.setPositionRelay.isRunning() || o.active()); i[n].animation = requestAnimationFrame(h) } this.allowCanceled || o.inactive(), i[n].reserve = setTimeout((() => { cancelAnimationFrame(i[n].animation), i[n].animation = requestAnimationFrame(h) }), 600) }, cancel: function () { this.allowCanceled && o.userControl && (Object.values(i).forEach((t => { t.reserve.forEach((t => clearTimeout(t))), t.animation.forEach((t => clearTimeout(t))) })), i = {}, this.duration = t, this.allowCanceled = e) } } }(), this.setPositionRelay = function () { let t = {}; return { delay: 0, interval: 0, isRunning: function () { return !!Object.values(t).find((t => t && t.isRun)) }, set: function (e, i) { t[e] = { startPose: null, isRun: !1, actions: [], reserve: [], onRelayEnd: [] }, i && (i.actions && (t[e].actions = Object.entries(i.actions)), i.startPose && (t[e].startPose = i.startPose)) }, startPose: function (e) { if (!t[e]) return; const { startPose: i, actions: n } = t[e]; i && n && n != [] && 0 != n.length && i(e) }, run: function (e, i) { if (!t[e]) return; const { actions: n, onRelayEnd: s } = t[e]; "function" == typeof i && s.push(i), n != [] && 0 != n.length ? (t[e].isRun = !0, t[e].reserve.forEach((t => clearTimeout(t))), t[e].reserve = [], o.inactive(), n.forEach((([i, r], a) => { t[e].reserve[a] = setTimeout((() => { "function" == typeof r && r(), s.length > 0 && a == n.length - 1 && s.forEach((t => t())) }), parseInt(this.delay) + parseInt(i)) }))) : s.length > 0 && s.forEach((t => t())) }, clear: function () { this.delay = 0, this.interval = 0, Object.values(t).forEach((t => t.reserve.forEach((t => clearTimeout(t))))), t = {} }, cancel: function (e) { t[e] && (t[e].reserve.forEach((t => clearTimeout(t))), t[e].isRun = !1) } } }(), this.init() } init() { this.scene.add(this.modelGroup) } setModelData(t) { this.modelData = this.productData.getModelData(t), this.modelData.color = c(this.modelData.color, this.OPTIONS.colorchip, this.colorName), this.modelName = t, this.colorName = this.productData.getDefaultColorName(t), this.modelScale = this.modelData.scale || 100, this.OPTIONS.zoom && (this.initPosition.zoom = f(this.OPTIONS.zoom)), this.OPTIONS.x && (this.initPosition.angleY = this.OPTIONS.x), this.OPTIONS.y && (this.initPosition.angleX = this.OPTIONS.y), this.startPosition = this.OPTIONS.startPosition || this.initPosition.clone() } setViewerEncoding(t) { const e = t || this.modelName; "sRGB" == this.productData.getModelData(e)["output-rendering"] ? (this.renderer.toneMapping = 4, this.renderer.toneMappingExposure = 1.2, this.renderer.textureEncoding = At, this.renderer.outputEncoding = At) : (this.renderer.toneMapping = 0, this.renderer.toneMappingExposure = 1, this.renderer.textureEncoding = Tt, this.renderer.outputEncoding = Tt) } checkIsSample(t) { const e = "v3d-modelviewer--sample", i = "v3d-msg-smaple"; let n = this.container.querySelector(`.${i}`); t && !this.container.classList.contains(e) ? (this.container.classList.add(e), n ? setHiddenAttribute(n, !1) : (n = document.createElement("div"), n.classList.add(i), n.innerText = "Placeholder Model Sample Text", this.gltfObject.add(new CSS2DObject(n)))) : !t && this.container.classList.contains(e) && (this.container.classList.remove(e), setHiddenAttribute(n, !0)) } getFilePath(t, e) { const i = location.hostname; let n = t; for (let t in e) e[t].find((t => i.indexOf(t) > -1)) && (n = t); return n } async loadViewerModels() { this.viewer.stopRender(), this.removeModels(); const t = this.OPTIONS.modelOptionList, e = t.find((t => t.default)) || t[0]; this.defaultModelName = e.modelName, this.defaultColorName = e.color || this.productData.getDefaultColorName(this.defaultModelName), (await this.loadAllHDR(this.defaultModelName)).forEach((t => { "scene" === t.hdrType ? this.setHdrCache(t, t.hdrName, !0) : "model" === t.hdrType && this.setHdrCache(t, t.hdrName) })), this.isInit || (this.cameraControls = new Th(this.viewer.controls), this.positionControls = new Dh(this.viewer.controls), this.setLoadModelStatusBeforeInit()); for (const e of t) await this.loadByModelOption(e); this.update(), this.isInit || (this.isInit = !0) } async loadByModelOption(t) { const e = t.modelName, i = t.color || this.productData.getDefaultColorName(e), n = this.productData.getModelData(e); t.introPose && this.setPositionRelay.set(e, bh.call(this, n.statustoggle, t.introPose)), this.setModelData(e); const s = await this.loadModel(e, i); this.setViewerEncoding(); const r = await this.postProcessModel(s, i); this.addModel(r), this.changeColorActive(e) } async loadModel(t, e, i) { e = e || this.colorName; const n = this.productData.getColorData(t, e), s = this.modelData.isSample ? this.modelPathBackup : this.getFilePath(T, this.modelPathMath), r = this.OPTIONS.jp && n.glbJp || n.glb, a = i || s + (this.modelData.isSample ? L : this.modelName) + "/" + r, o = this.OPTIONS.getModelOption(t); if (Oh[a]) return this.isProductChange = !1, Oh[a]; this.isProductChange = !0; try { const t = await gh(a); return this.loadGltfObject(t, o, a) } catch (e) { const i = this.modelPathBackup + (this.modelData.isSample ? L : t) + "/" + r; try { const t = await gh(i); return this.loadGltfObject(t, o, i) } catch (t) { } } } loadGltfObject(t, e, i) { const n = new _h(e, this.modelData, t), s = e.modelName; return n.modelName = s, n.scale.set(this.modelScale, this.modelScale, this.modelScale), n.position.set(e.position.x, e.position.y, e.position.z), n.rotation.set(e.rotation.x, e.rotation.y, e.rotation.z), n.initPosition = new yh({ angleX: e.rotation.x, angleY: e.rotation.y, angleZ: e.rotation.z, zoom: E, poseX: e.position.x, poseY: e.position.y, poseZ: e.position.z, angleLimit: !1 }), n.initPosition0 = n.initPosition.clone(), n.positionController = new Ih(this.viewer.controls, n), void 0 !== t.animations && t.animations.map((t => { "status_toggle" === t.name && (n.mixer = new xh(t, n), n.mixer.clipAction(t).play(), n.mixer.type = this.productData.getModelData(s).statustoggle) })), n.statusToggle ? (n.statusToggle = !0, n.mixer && n.mixer.setTrack(1)) : (n.statusToggle = !1, n.mixer && n.mixer.setTrack(0)), Oh[i] = n, n } addModel(t) { this.removeModel(), t.viewerModel = this, this.modelGroup.add(t) } removeModels() { this.modelGroup.children = this.modelGroup.children.filter((t => "gltfObject" !== t.name)) } async loadHDR(t, e) { e = e || this.colorName; const i = this.productData.getColorData(t, e), n = this.getFilePath(T, this.modelPathMath), s = i.envtype; if (Nh[s]) return; const r = `${n}hdr/${this.productData.hdrPath[s]}`, a = `${this.modelPathBackup}hdr/${this.productData.hdrPath[s]}`; try { await gh(r).then((t => this.setHdrCache(t, s, !0))) } catch (t) { await gh(a).then((t => this.setHdrCache(t, s, !0))) } } async loadAllHDR(t) { const e = this.productData.getColorData(t, this.defaultColorName), i = this.getFilePath(T, this.modelPathMath), n = e.envtype, s = `${i}hdr/${this.productData.hdrPath[n]}`, r = `${this.modelPathBackup}hdr/${this.productData.hdrPath[n]}`, a = new Promise((async t => { let e; try { e = await gh(s) } catch (t) { e = await gh(r) } e.hdrType = "scene", e.hdrName = n, t(e) })), o = this.productData.getModelData(t), l = new Set, h = []; return o.color.forEach((t => { for (const e of t.envCustom) l.has(e.type) || l.add(e.type) })), l.forEach((t => { const e = new Promise((async e => { const i = this.productData.hdrPath[t], n = `${this.getFilePath(T, this.modelPathMath)}hdr/${i}`, s = `${this.modelPathBackup}hdr/${i}`; let r; try { r = await gh(n) } catch (t) { r = await gh(s) } r.hdrType = "model", r.hdrName = t, e(r) })); h.push(e) })), Promise.all([a].concat(h)) } async setCustomEnv(t, e) { e = e || this.colorName; const i = t.modelName, n = this.productData.getColorData(i, e).envCustom; if (null != n) { for (const e of n) { const i = this.productData.hdrPath[e.type]; if (null == i) continue; let n = {}; if (t.traverse((t => { t.isMesh && e.target.indexOf(t.material.name) > -1 && null == n[t.material.name] && (n[t.material.name] = t.material) })), Nh[e.type]) { for (let t in n) this.setEnvMapByCache(n[t], e.type); continue } const s = `${this.getFilePath(T, this.modelPathMath)}hdr/${i}`, r = `${this.modelPathBackup}hdr/${i}`; try { await gh(s).then((t => { for (let i in n) this.setHdrCache(t, e.type), this.setEnvMapByCache(n[i], e.type) })) } catch (t) { await gh(r).then((t => { for (let i in n) this.setHdrCache(t, e.type), this.setEnvMapByCache(n[i], e.type) })) } } return t } } async setCustomMaterial(t, e) { const i = t.modelName, n = this.productData.getColorData(i, e).custumMaterial; return n ? (await n.forEach((e => { let n = {}; t.traverse((t => { if (t.isMesh && e.target.indexOf(t.material.name) > -1 && null == n[t.material.name]) for (let n in e.options) if ("color" == n || "emissive" == n) { const i = new ae(e.options[n]); t.material[n] = i, "color" == n && (t.material.color0 = i) } else if ("renderOrder" == n) t[n] = e.options[n]; else if ("emissiveMap" != n && "alphaMap" != n && "map" != n && "metalnessMap" != n || null === e.options[n]) t.material[n] = e.options[n]; else { const s = Array.isArray(e.options[n]) ? e.options[n][0] : e.options[n], r = Array.isArray(e.options[n]) ? e.options[n][1] : null; gh(`${this.OPTIONS.assetsPath}models/${i}/${s}`).then((e => { if (e.src0 = s, e.options0 = r, e.flipY = t.material[n] && t.material[n].flipY, e.encoding = At, "alphaMap" == n && (t.material.transparent = !0), r) for (let t in r) "repeat" == t && r[t] && (e.wrapS = J, e.wrapT = J); t.material.needsUpdate = !0, t.material[n] = e })) } })) })), t) : t } setHdrCache(t, e, i = !1) { const n = new Qn(this.renderer); n.compileEquirectangularShader(), i && (n.far = 50, this.environmentHdrName = e); const s = n.fromEquirectangular(t).texture; s.hdrType = t.hdrType || "", s.hdrName = t.hdrName || "", Nh[e] = s, t.dispose(), n.dispose() } setEnvMapByCache(t, e) { t.envMap = Nh[e] } async changeColor(t, e) { if (!e) return; if (!this.productData.getColorData(t, e)) return; const i = { modelName: t, color: e }; this.modelGroup.getTargetObject(t) ? this.OPTIONS.setModelOption(t, i) : this.OPTIONS.setModelOptionList([i]), this.setModelData(t); const n = await this.loadModel(t, e); this.modelGroup.children[0].modelName === t && (n.statusToggle = this.modelGroup.children[0].statusToggle); const s = await this.postProcessModel(n, e); this.removeModels(), this.addModel(s) } async postProcessModel(t, e) { const i = t.modelName; return t.setScreenSync(this.modelData.statustoggle), this.scene.environment = Nh[this.environmentHdrName], this.colorName = e, !this.isProductChange && this.isInit || (this.changeProductActive(i), this.checkIsSample(this.modelData.isSample)), await this.setCustomEnv(t, e), await this.setCustomMaterial(t, e), t.screenUpdate(), t } changeColorActive(t) { const e = this.OPTIONS.getModelOption(t); this.modelGroup.getTargetObject(t).screenUpdate(), null === e.introPose || e.introPoseOnce && this.checkIntroPoseUsed(t) || this.setPositionRelay.startPose(t), e.introPose ? this.actionIntroPose(t) : (e.autoRotation && this.autoRotation.run(t), this.setLoadModelAfterActive()) } changeProductActive(t) { this.modelGroup.children.forEach((t => { const e = this.productData.getModelData(t.modelName); void 0 !== t.mixer && void 0 !== e.statustoggle && null !== t.mixer && (t.statusToggle ? (t.statusToggle = !0, t.mixer.setTrack(1)) : (t.statusToggle = !1, t.mixer.setTrack(0))) })) } actionIntroPose(t) { const e = this, i = this.modelGroup.getTargetObject(t), n = this.OPTIONS.getModelOption(t), s = bh.call(this, this.modelData.statustoggle, n.introPose); !s || !s.actions || n.introPoseOnce && this.checkIntroPoseUsed(t) ? (n.autoRotation && this.autoRotation.run(t), this.setLoadModelAfterActive()) : (this.inactive(), n.introPoseOnce || (this.setPositionRelay.enabled = !0), this.setPositionRelay.delay = n.introPoseHold ? 5e3 : 1e3, n.introPoseHold && !1 !== n.introPoseHoldLimit && (this.setPositionRelay.delay = 1e3 * n.introPoseHoldLimit), this.introPoseReady = !0, setTimeout((() => { const e = new CustomEvent("introPoseReady", { detail: { modelName: t } }); window.dispatchEvent(e) }), 100), this.setPositionRelay.run(t, (function () { n.introPoseOnce && !e.checkIntroPoseUsed(t) && e.setIntroPoseUsed(t), e.setPositionRelay.cancel(t); const s = e.getModelPosition(t); if (s && !i.initPosition.equals(s)) { function a(t) { const n = i.initPosition.clone(); n.angleLimit = !0, i.positionController.setPosition(n, 500, void 0, void 0, void 0, !0), e.cameraControls.reset(), e.container.removeEventListener("pointerdown", a), e.container.removeEventListener("touchstart", a), e.container.removeEventListener("wheel", a) } e.container.addEventListener("pointerdown", a), e.container.addEventListener("touchstart", a, { passive: !0 }), e.container.addEventListener("wheel", a, { passive: !0 }) } e.setLoadModelAfterActive(), e.introPoseEnd = !0; const r = new CustomEvent("introPoseEnd", { detail: { modelName: t } }); window.dispatchEvent(r) }))) } checkIntroPoseUsed(t) { return !!this.introPoseUsedData[t] } setIntroPoseUsed(t) { this.introPoseUsedData[t] = !0, o(R, JSON.stringify(this.introPoseUsedData)) } setLoadModelAfterActive() { !this.autoRotation.allowCanceled && this.autoRotation.isRunning() || this.setPositionRelay.isRunning() || this.active() } removeModel(t) { const e = this.modelGroup.getTargetObject(t); this.modelGroup.remove(e) } inactive(t = !0) { this.userControl = !1, this.viewer.controls.userControl = !1, t && this.autoRotation.cancel(), this.setPositionRelay.isRunning() && this.autoRotation.allowCanceled && !this.autoRotation.isRunning() && this.modelGroup.children.forEach((t => { this.statusToggleInactive(t.modelName, !0), this.setPositionRelay.clear(t.modelName) })) } active() { this.viewer.controls && (this.userControl = !0, this.viewer.controls.userControl = !0) } disable() { this.enabled = !1 } enable() { this.enabled = !0 } getModelPosition(t) { const e = this.modelGroup.getTargetObject(t); if (e) return new yh({ angleX: e.rotation.x, angleY: e.rotation.y, angleZ: e.rotation.z, poseX: e.position.x, poseY: e.position.y, poseZ: e.position.z, zoom: this.camera.position.z }) } setLoadModelStatusBeforeInit() { this.enable(), this.initPosition0 = this.initPosition.clone(), this.initPosition0.angleLimit = !1, this.setInitPosition() } setCanvasAltText(t) { this.renderer.domElement.setAttribute("aria-hidden", !0); const e = document.createElement("span"); e.className = "sr-only"; let i = t || ""; e.innerText = i; const n = this.renderer.domElement.parentElement.querySelector("span.sr-only"); n ? n.innerText != i && (n.innerText = i) : this.renderer.domElement.insertAdjacentElement("afterend", e) } statusToggleActive(t, e = !1) { if (!this.enabled && !e) return; if (!this.userControl && !e) return; const i = this.modelGroup.getTargetObject(t), n = this.productData.getModelData(t); i.statusToggle = !0, n.statustoggle && i && i.mixer && i.mixer.open(this.onMixerStart.bind(this), this.onMixerChange.bind(this), this.onMixerEnd.bind(this)) } statusToggleInactive(t, e = !1) { if (!this.enabled && !e) return; if (!this.userControl && !e) return; const i = this.modelGroup.getTargetObject(t), n = this.productData.getModelData(t); i.statusToggle = !1, n.statustoggle && i && i.mixer && i.mixer.close(this.onMixerStart.bind(this), this.onMixerChange.bind(this), this.onMixerEnd.bind(this)) } setInitPosition() { this.positionControls.setPosition(this.initPosition, 0) } setPosition(t, e, i, n, s, r = !1) { (this.enabled || r) && (this.userControl || r) && (this.modelGroup.children.forEach((t => { t.positionController && t.positionController.stop() })), this.autoRotation.cancel(), this.positionControls.setPosition(t, e, i, n, s, r)) } toggleActionSetTime(t, e = 1, i = 0, n = !1) { const s = this.modelGroup.getTargetObject(t); if (!t || !s) return; const r = s.mixer; null != r && (e = Math.min(Math.max(e, 0), 1), r.setTrack(e, i, void 0, this.onMixerChange.bind(this), void 0)) } screenUpdate() { this.modelGroup.children.forEach((t => { t.screenUpdate() })) } reset(t, e = !1) { this.enabled && (this.userControl || e) && (this.autoRotation.cancel(), this.initPosition0.angleLimit = !0, this.setPosition(this.initPosition0, void 0, void 0, t, void 0, !0), this.setPositionRelay.isRunning() && this.modelGroup.children.forEach((t => { this.statusToggleInactive(t.modelName, !0), this.setPositionRelay.cancel(t.modelNmae) })), this.cameraControls.isRun && this.cameraControls.reset()) } onPointerDown(t) { 1 == this.autoRotation.allowCanceled && this.autoRotation.cancel() } onPointerMove(t) { } onDoubleClick(t) { this.modelGroup.children.forEach((t => { !0 === t.statusToggle ? this.statusToggleInactive(t.modelName) : this.statusToggleActive(t.modelName) })) } onMixerStart() { this.isMixerRun = !0; const t = new CustomEvent("mixerStart"); window.dispatchEvent(t) } onMixerChange() { this.screenUpdate() } onMixerEnd() { this.isMixerRun = !1 } update() { this.positionControls && this.positionControls.update(), this.camera.updateProjectionMatrix(), this.viewer.render() } } class Fh { constructor(t, e) { this.backupPath = void 0 !== t.backupPath ? t.backupPath : "./assets/", this.assetsPath = void 0 !== t.assetsPath ? t.assetsPath : this.backupPath, this.bgcolor = void 0 !== t.bgcolor ? t.bgcolor : "F7F7F7", this.language = void 0 !== t.language ? t.language : null, this.jp = void 0 !== t.jp && t.jp, this.jpDomain = !!S.find((t => location.hostname.indexOf(t) > -1)), this.mode = void 0 !== t.mode ? t.mode : null, this.ar = void 0 === t.ar || t.ar, this.device = void 0 !== t.device ? t.device : null, this.devMode = void 0 !== t.devMode ? t.devMode : null, this.devModeOS = void 0 !== t.devModeOS ? t.devModeOS : null, this.exclusiveMode = void 0 !== t.exclusiveMode && t.exclusiveMode, this.useZoom = void 0 === t.useZoom || t.useZoom, this.usePan = void 0 === t.usePan || t.usePan, this.x = void 0 !== t.x ? u(t.x) : x, this.y = void 0 !== t.y ? u(t.y) : y, this.minAngleX = void 0 !== t.minAngleX ? u(t.minAngleX) : u(-180), this.minAngleY = void 0 !== t.minAngleY ? u(t.minAngleY) : u(-180), this.maxAngleX = void 0 !== t.maxAngleX ? u(t.maxAngleX) : u(180), this.maxAngleY = void 0 !== t.maxAngleY ? u(t.maxAngleY) : u(180), this.zoom = void 0 !== t.zoom ? t.zoom : 100, this.modelOptionList = [], this.setModelOptionList(e) } getModelOption(t) { return this.modelOptionList.find((e => e.modelName === t)) } setModelOption(t, e) { const i = this.modelOptionList.find((e => e.modelName === t)); for (const t in e) i[t] = e[t] } setModelOptionList(t) { this.modelOptionList = t.map((t => ({ modelName: void 0 !== t.modelName ? t.modelName : null, color: void 0 !== t.color ? t.color : null, autoRotation: void 0 !== t.autoRotation && t.autoRotation, screen: void 0 !== t.screen ? t.screen : 1, screenMode: void 0 !== t.screenMode ? t.screenMode : "normal", statusToggle: void 0 !== t.statusToggle ? t.statusToggle : 0, rotation: t.rotation ? { x: void 0 !== t.rotation.x ? t.rotation.x : x, y: void 0 !== t.rotation.y ? t.rotation.y : y, z: void 0 !== t.rotation.z ? t.rotation.z : b } : M, position: t.position ? { x: void 0 !== t.position.x ? t.position.x : x, y: void 0 !== t.position.y ? t.position.y : y, z: void 0 !== t.position.z ? t.position.z : b } : M, initPosition: t.initPosition ? new yh(t.initPosition) : null, introPose: void 0 !== t.introPose ? t.introPose : null, introPoseOnce: void 0 !== t.introPoseOnce && t.introPoseOnce, introPoseHold: void 0 !== t.introPoseHold && t.introPoseHold, introPoseHoldLimit: void 0 !== t.introPoseHoldLimit ? t.introPoseHoldLimit : 5 }))) } } const kh = []; class Uh { constructor(t, e) { this.OPTIONS = t, this.localData = e, this.color = null, this.defaultModelName = null, this.modelList = null, this.product = null, this.hdrPath = null, this.compare = null, this.compareAll = null } async loadViewerData() { const t = await this.loadKey(); await this.loadProductData(t) } async loadKey() { const t = this; let e = [`${this.OPTIONS.assetsPath}product/key.json?${(new Date).getTime()}`]; return await Promise.all(e.map((t => _(t)))).then((e => (this.hdrPath = e[0].hdr, this.compare = this.localData && this.localData.compare ? this.localData.compare : null, this.compareAll = e[0].compare, this.defaultModelName = this.OPTIONS.modelOptionList[0].modelName ? function (e) { if (t.compareAll.indexOf(e) > -1) return e; let i = e; return t.compareAll.includes(i) || (i.includes("-5g") ? i = i.replace("-5g", "") : i += "-5g"), t.compareAll.includes(i) ? i : e }(this.OPTIONS.modelOptionList[0].modelName) : "galaxy-sample", this.modelList = this.localData && this.localData.models ? this.localData.models : this.compareAll, this.product = {}, this.modelList && -1 == this.modelList.indexOf(this.defaultModelName) && this.defaultModelName && this.isSampleDataMatch(this.defaultModelName) && this.modelList.unshift(this.defaultModelName), e))) } async loadProductData() { let t = [this.defaultModelName]; this.modelList && (t = t.concat(this.modelList)), t = [...new Set(t)]; const e = this.compareAll.filter((e => !t.includes(e))); for (let t = 0; t _(t)))).then((t => { if (Object.assign(this.product, ...t), this.localData) for (let t in this.product) this.localData[t] && this.localData[t].color && (this.product[t].color = c(this.product[t].color, this.localData[t].color, this.localData[t].defaultColor)); return t })) } getModelsURL() { let t = this.OPTIONS.modelOptionList.map((t => t.modelName)), e = []; this.modelList && (t = t.concat(this.modelList)), t = [...new Set(t)]; for (let i = 0; i 21 && t.includes("ultra") && (i.statustoggle = "s-pen"); break; case "galaxy-note": i.statustoggle = "s-pen"; break; case "galaxy-z-fold": i.statustoggle = "fold"; break; case "galaxy-z-flip": i.statustoggle = "flip" }let n = this.localData[t].color; return i.color = n.map(((t, e) => ({ key: t, text: t, default: 0 == e, compression: !0, glb: i.statustoggle ? D[i.statustoggle] : D.bar, envtype: "envSRGBPhantom" }))), i } getModelData(t) { return this.product[t] } getDefaultModelName() { return this.defaultModelName } getDefaultColorName(t) { return this.getModelData(t).color.find((t => t.default)).key } getColorData(t, e) { return this.getModelData(t).color.find((t => t.key == e)) } getExclusiveColorNameList(t) { return this.getModelData(t).color.filter((t => t.exclusive)).map((t => t.key)) } } const Bh = new qe, Vh = new Cn, Gh = Math.cos(70 * Wt.DEG2RAD), Hh = new _e, jh = 2 * Math.PI, Wh = -1, Xh = 1e-6; class Yh { constructor(t, e, i, n = null) { this.OPTIONS = t, this.object = e, this.domElement = n, this.productObject = i, this.state = Wh, this.enabled = !0, this.userControl = !1, this.isPositioning = !1, this.target = new _e, this.cursor = new _e, this.minDistance = 100 * E / P, this.maxDistance = 100 * E / C, this.minZoom = 0, this.maxZoom = 1 / 0, this.minTargetRadius = 0, this.maxTargetRadius = 1 / 0, this.minPolarAngle = 0, this.maxPolarAngle = Math.PI, this.minAzimuthAngle = -1 / 0, this.maxAzimuthAngle = 1 / 0, this.enableDamping = !0, this.dampingFactor = .05, this.enableZoom = !0, this.zoomSpeed = 1, this.enableRotate = !0, this.rotateSpeed = .4, this.enablePan = !0, this.panSpeed = 1, this.screenSpacePanning = !0, this.keyPanSpeed = 7, this.zoomToCursor = !1, this.autoRotate = !1, this.autoRotateSpeed = 2, this.keys = { LEFT: "ArrowLeft", UP: "ArrowUp", RIGHT: "ArrowRight", BOTTOM: "ArrowDown" }, this.mouseButtons = { LEFT: 0, MIDDLE: 1, RIGHT: 2 }, this.touches = { ONE: 0, TWO: 2 }, this.target0 = this.target.clone(), this.position0 = this.object.position.clone(), this.zoom0 = this.object.zoom, this.productOriginAngle = new _e, this._domElementKeyEvents = null, this._lastPosition = new _e, this._lastRotation = new _e, this._lastQuaternion = new ve, this._lastTargetPosition = new _e, this._quat = (new ve).setFromUnitVectors(e.up, new _e(0, 1, 0)), this._quatInverse = this._quat.clone().invert(), this._spherical = new jo, this._sphericalDelta = new jo, this._scale = 1, this._panOffset = new _e, this._rotateStart = new Xt, this._rotateEnd = new Xt, this._rotateDelta = new Xt, this._rotatePrev = new Xt(this.productObject.rotation.x, this.productObject.rotation.y), this._panStart = new Xt, this._panEnd = new Xt, this._panDelta = new Xt, this._dollyStart = new Xt, this._dollyEnd = new Xt, this._dollyDelta = new Xt, this._positionStart = new Xt, this._positionEnd = new Xt, this._positionDelta = new Xt, this._zoomStart = new Xt, this._zoomEnd = new Xt, this._zoomDelta = .1, this._setPositionSpeed = 1, this._dollyDirection = new _e, this._mouse = new Xt, this._performCursorZoom = !1, this._pointers = [], this._pointerPositions = {}, this._controlActive = !1, this._onPointerMove = Zh.bind(this), this._onPointerDown = qh.bind(this), this._onPointerUp = $h.bind(this), this._onContextMenu = rc.bind(this), this._onKeyDown = ec.bind(this), this._onTouchStart = ic.bind(this), this._onTouchMove = nc.bind(this), this._onTouchEnd = sc.bind(this), this._onMouseDown = Kh.bind(this), this._onMouseMove = Jh.bind(this), this._onMouseUp = Qh.bind(this), this._onMouseWheel = tc.bind(this), this._interceptControlDown = ac.bind(this), this._interceptControlUp = oc.bind(this), this.setPositionControls = null, this.setPositionController = { list: [], isRun: !1, update: function () { this.list.forEach((t => { t.update() })), this.list.find((t => t.isRun)) ? this.isRun = !0 : this.isRun = !1 } }, this.cameraControls = null, null !== this.domElement && this.connect(), this.update() } setPositionStop() { this._rotateDelta.set(0, 0), this.setPositionSync() } setPositionSync() { this._rotateEnd.set(this.productObject.rotation.x, this.productObject.rotation.y), this._rotatePrev.set(this.productObject.rotation.x, this.productObject.rotation.y), this._positionEnd.set(this.productObject.position.x, this.productObject.position.y), this._rotateStart.copy(this._rotateEnd), this._positionStart.copy(this._positionEnd) } connect() { this.domElement.addEventListener("contextmenu", this._onContextMenu), this.domElement.addEventListener("pointerdown", this._onPointerDown), this.domElement.addEventListener("wheel", this._onMouseWheel, { passive: !0 }), this.domElement.addEventListener("touchstart", this._onTouchStart), this.domElement.getRootNode().addEventListener("keydown", this._interceptControlDown, { passive: !0, capture: !0 }) } disconnect() { this.domElement.removeEventListener("contextmenu", this._onContextMenu), this.domElement.removeEventListener("pointerdown", this._onPointerDown), this.domElement.removeEventListener("wheel", this._onMouseWheel), this.domElement.removeEventListener("touchstart", this._onTouchStart), this.domElement.removeEventListener("touchend", this._onTouchEnd), this.domElement.removeEventListener("touchmove", this._onTouchMove), this.domElement.removeEventListener("pointermove", this._onPointerMove), this.domElement.removeEventListener("pointerup", this._onPointerUp), this.domElement.removeEventListener("pointerleave", this._onPointerUp), this.stopListenToKeyEvents(), this.domElement.getRootNode().removeEventListener("keydown", this._interceptControlDown, { capture: !0 }) } dispose() { this.disconnect() } getPolarAngle() { return this._spherical.phi } getAzimuthalAngle() { return this._spherical.theta } getDistance() { return this.object.position.distanceTo(this.target) } listenToKeyEvents(t) { t.addEventListener("keydown", this._onKeyDown), this._domElementKeyEvents = t } stopListenToKeyEvents() { null !== this._domElementKeyEvents && (this._domElementKeyEvents.removeEventListener("keydown", this._onKeyDown), this._domElementKeyEvents = null) } saveState() { this.target0.copy(this.target), this.position0.copy(this.object.position), this.zoom0 = this.object.zoom } reset() { this.target.copy(this.target0), this.object.position.copy(this.position0), this.object.zoom = this.zoom0, this.object.updateProjectionMatrix(), this.update(), this.state = Wh } setPosition(t, e = 1, i) { const n = null == (t = t || {}).angleX ? this.productObject.rotation.x : t.angleX, s = null == t.angleY ? this.productObject.rotation.y : t.angleY, r = null == t.zoom ? this.object.position.z : t.zoom, a = null == t.panX ? this.object.position.x : t.panX, o = null == t.panY ? this.object.position.y : t.panY; if (0 == e) return this._rotateUp(n - this.productObject.rotation.x), this._rotateLeft(s - this.productObject.rotation.y), this.object.position.z = r, this.object.position.x = a, this.object.position.y = o, this.target.x = a, void (this.target.y = o); state = 8, this._setPositionSpeed = e, zoomStart = this.object.position.z, zoomEnd = Math.max(Math.min(r, this.maxDistance), this.minDistance), zoomDelta = .3 * this.dampingFactor * this._setPositionSpeed, this._rotateStart.set(this.productObject.rotation.x, this.productObject.rotation.y), this._rotateEnd.set(n, s), this._rotateDelta.set(this.dampingFactor * this.rotateSpeed * this._setPositionSpeed, this.dampingFactor * this.rotateSpeed * this._setPositionSpeed), this._positionStart.set(this.object.position.x, this.object.position.y), this._positionEnd.set(a, o), this._positionDelta.set(this.dampingFactor, this.dampingFactor) } setPositionStart() { this.state = 8 } update(t = null) { const e = this.object.position; Hh.copy(e).sub(this.target), Hh.applyQuaternion(this._quat), this._spherical.setFromVector3(Hh), this.autoRotate && this.state === Wh && this._rotateLeft(this._getAutoRotationAngle(t)), this.enableDamping && 8 !== this.state && 0 !== this.state ? (this._spherical.theta += this._sphericalDelta.theta * this.dampingFactor, this._spherical.phi += this._sphericalDelta.phi * this.dampingFactor, this._rotateDelta.length() > .01 && (this._rotateLeft(this._rotateDelta.x * this.dampingFactor * .08), this._rotateUp(this._rotateDelta.y * this.dampingFactor * .08))) : 8 !== this.state && (this._spherical.theta += this._sphericalDelta.theta, this._spherical.phi += this._sphericalDelta.phi); let i = this.minAzimuthAngle, n = this.maxAzimuthAngle; isFinite(i) && isFinite(n) && (i Math.PI && (i -= jh), n Math.PI && (n -= jh), this._spherical.theta = i (i + n) / 2 ? Math.max(i, this._spherical.theta) : Math.min(n, this._spherical.theta)), this._spherical.phi = Math.max(this.minPolarAngle, Math.min(this.maxPolarAngle, this._spherical.phi)), this._spherical.makeSafe(), !0 === this.enableDamping && 8 !== this.state ? this.target.addScaledVector(this._panOffset, this.dampingFactor) : 8 !== this.state && this.target.add(this._panOffset), this.target.sub(this.cursor), this.target.clampLength(this.minTargetRadius, this.maxTargetRadius), this.target.add(this.cursor); let s = !1; if (this.zoomToCursor && this._performCursorZoom || this.object.isOrthographicCamera) this._spherical.radius = this._clampDistance(this._spherical.radius); else { const t = this._spherical.radius; this._spherical.radius = this._clampDistance(this._spherical.radius * this._scale), s = t != this._spherical.radius } if (Hh.setFromSpherical(this._spherical), Hh.applyQuaternion(this._quatInverse), e.copy(this.target).add(Hh), this.cameraControls && this.cameraControls.isRun && this.cameraControls.update(), this.object.lookAt(this.target), !0 === this.enableDamping && 8 !== this.state ? (this._sphericalDelta.theta *= 1 - this.dampingFactor, this._sphericalDelta.phi *= 1 - this.dampingFactor, this._rotateDelta.x *= 1 - this.dampingFactor, this._rotateDelta.y *= 1 - this.dampingFactor, this._panOffset.multiplyScalar(1 - this.dampingFactor)) : 8 !== this.state && (this._sphericalDelta.set(0, 0, 0), this._rotateDelta.set(0, 0), this._panOffset.set(0, 0, 0)), 8 == this.state && (this.setPositionControls.update(), this.setPositionController.update(), this.setPositionControls.isRun || this.setPositionController.isRun || (this.state = Wh, this.isPositioning = !1, this._sphericalDelta.set(0, 0, 0), this._panOffset.set(0, 0, 0), this._rotateDelta.set(0, 0), this._zoomStart = this._zoomEnd, this.setPositionSync())), this.zoomToCursor && this._performCursorZoom) { let t = null; if (this.object.isPerspectiveCamera) { const e = Hh.length(); t = this._clampDistance(e * this._scale); const i = e - t; this.object.position.addScaledVector(this._dollyDirection, i), this.object.updateMatrixWorld(), s = !!i } else if (this.object.isOrthographicCamera) { const e = new _e(this._mouse.x, this._mouse.y, 0); e.unproject(this.object); const i = this.object.zoom; this.object.zoom = Math.max(this.minZoom, Math.min(this.maxZoom, this.object.zoom / this._scale)), this.object.updateProjectionMatrix(), s = i !== this.object.zoom; const n = new _e(this._mouse.x, this._mouse.y, 0); n.unproject(this.object), this.object.position.sub(n).add(e), this.object.updateMatrixWorld(), t = Hh.length() } else this.zoomToCursor = !1; null !== t && (this.screenSpacePanning ? this.target.set(0, 0, -1).transformDirection(this.object.matrix).multiplyScalar(t).add(this.object.position) : (Bh.origin.copy(this.object.position), Bh.direction.set(0, 0, -1).transformDirection(this.object.matrix), Math.abs(this.object.up.dot(Bh.direction)) Xh || this._lastRotation.distanceToSquared(this.object.rotation) > Xh || 8 * (1 - this._lastQuaternion.dot(this.object.quaternion)) > Xh || this._lastTargetPosition.distanceToSquared(this.target) > Xh) && (8 !== this.state && 1 == this.isPositioning && (this.isPositioning = !1), this._lastPosition.copy(this.object.position), this._lastRotation.copy(this.object.rotation), this._lastQuaternion.copy(this.object.quaternion), this._lastTargetPosition.copy(this.target), s = !1, !0) } _getAutoRotationAngle(t) { return null !== t ? jh / 60 * this.autoRotateSpeed * t : jh / 60 / 60 * this.autoRotateSpeed } _getZoomScale() { return Math.pow(.95, this.zoomSpeed) } _rotateLeft(t) { this._rotatePrev.y = p(this._rotatePrev.y + t), this._rotatePrev.y > 0 ? this._rotatePrev.y = Math.min(this.OPTIONS.maxAngleX, this._rotatePrev.y) : this._rotatePrev.y 0 ? this.productObject.rotation.x = Math.min(this.OPTIONS.maxAngleY, this.productObject.rotation.x) : this.productObject.rotation.x 0 ? this._dollyOut(this._getZoomScale()) : this._dollyDelta.y 0 && this._dollyOut(this._getZoomScale()), this.update() } _handleKeyDown(t) { let e = !1; switch (t.code) { case this.keys.UP: t.ctrlKey || t.metaKey || t.shiftKey ? this._rotateUp(jh * this.rotateSpeed / this.domElement.clientHeight) : this._pan(0, this.keyPanSpeed), e = !0; break; case this.keys.BOTTOM: t.ctrlKey || t.metaKey || t.shiftKey ? this._rotateUp(-jh * this.rotateSpeed / this.domElement.clientHeight) : this._pan(0, -this.keyPanSpeed), e = !0; break; case this.keys.LEFT: t.ctrlKey || t.metaKey || t.shiftKey ? this._rotateLeft(jh * this.rotateSpeed / this.domElement.clientHeight) : this._pan(this.keyPanSpeed, 0), e = !0; break; case this.keys.RIGHT: t.ctrlKey || t.metaKey || t.shiftKey ? this._rotateLeft(-jh * this.rotateSpeed / this.domElement.clientHeight) : this._pan(-this.keyPanSpeed, 0), e = !0 }e && (t.preventDefault(), this.update()) } _handleTouchStartRotate(t) { if (1 == t.touches.length) this._rotateStart.set(t.touches[0].pageX, t.touches[0].pageY); else { const e = .5 * (t.touches[0].pageX + t.touches[1].pageX), i = .5 * (t.touches[0].pageY + t.touches[1].pageY); this._rotateStart.set(e, i) } } _handleTouchStartPan(t) { if (1 == t.touches.length) this._panStart.set(t.touches[0].pageX, t.touches[0].pageY); else { const e = .5 * (t.touches[0].pageX + t.touches[1].pageX), i = .5 * (t.touches[0].pageY + t.touches[1].pageY); this._panStart.set(e, i) } } _handleTouchStartDolly(t) { const e = t.touches[0].pageX - t.touches[1].pageX, i = t.touches[0].pageY - t.touches[1].pageY, n = Math.sqrt(e * e + i * i); this._dollyStart.set(0, n) } _handleTouchStartDollyPan(t) { this.enableZoom && this._handleTouchStartDolly(t), this.enablePan && this._handleTouchStartPan(t) } _handleTouchStartDollyRotate(t) { this.enableZoom && this._handleTouchStartDolly(t), this.enableRotate && this._handleTouchStartRotate(t) } _handleTouchMoveRotate(t) { if (1 == t.touches.length) this._rotateEnd.set(t.touches[0].pageX, t.touches[0].pageY); else { const e = .5 * (t.touches[0].pageX + t.touches[1].pageX), i = .5 * (t.touches[0].pageY + t.touches[1].pageY); this._rotateEnd.set(e, i) } this._rotateDelta.subVectors(this._rotateEnd, this._rotateStart).multiplyScalar(this.rotateSpeed); const e = this.domElement; this._rotateLeft(jh * this._rotateDelta.x / e.clientHeight), this._rotateUp(jh * this._rotateDelta.y / e.clientHeight), this._rotateStart.copy(this._rotateEnd) } _handleTouchMovePan(t) { if (1 == t.touches.length) this._panEnd.set(t.touches[0].pageX, t.touches[0].pageY); else { const e = .5 * (t.touches[0].pageX + t.touches[1].pageX), i = .5 * (t.touches[0].pageY + t.touches[1].pageY); this._panEnd.set(e, i) } this._panDelta.subVectors(this._panEnd, this._panStart).multiplyScalar(this.panSpeed), this._pan(this._panDelta.x, this._panDelta.y), this._panStart.copy(this._panEnd) } _handleTouchMoveDolly(t) { const e = t.touches[0].pageX - t.touches[1].pageX, i = t.touches[0].pageY - t.touches[1].pageY, n = Math.sqrt(e * e + i * i); this._dollyEnd.set(0, n), this._dollyDelta.set(0, Math.pow(this._dollyEnd.y / this._dollyStart.y, this.zoomSpeed)), this._dollyOut(this._dollyDelta.y), this._dollyStart.copy(this._dollyEnd) } _handleTouchMoveDollyPan(t) { this.enableZoom && this._handleTouchMoveDolly(t), this.enablePan && this._handleTouchMovePan(t) } _handleTouchMoveDollyRotate(t) { this.enableZoom && this._handleTouchMoveDolly(t), this.enableRotate && this._handleTouchMoveRotate(t) } _addPointer(t) { this._pointers.push(t.pointerId) } _removePointer(t) { delete this._pointerPositions[t.pointerId]; for (let e = 0; e 50 && 0 == t.button && this.onDoubleClick(t), this.lastPointerUpTime = e } onDoubleClick(t) { this.viewer.onDoubleClick(t) } onHashChange(t) { } onReciveMessage(t) { } }
/**
* lil-gui
* https://lil-gui.georgealways.com
* @version 0.16.0
* @author George Michael Brower
* @license MIT
**/
class hc { constructor(t, e, i, n, s = "div") { this.parent = t, this.object = e, this.property = i, this._disabled = !1, this.initialValue = this.getValue(), this.domElement = document.createElement("div"), this.domElement.classList.add("controller"), this.domElement.classList.add(n), this.$name = document.createElement("div"), this.$name.classList.add("name"), hc.nextNameID = hc.nextNameID || 0, this.$name.id = "lil-gui-name-" + ++hc.nextNameID, this.$widget = document.createElement(s), this.$widget.classList.add("widget"), this.$disable = this.$widget, this.domElement.appendChild(this.$name), this.domElement.appendChild(this.$widget), this.parent.children.push(this), this.parent.controllers.push(this), this.parent.$children.appendChild(this.domElement), this._listenCallback = this._listenCallback.bind(this), this.name(i) } name(t) { return this._name = t, this.$name.innerHTML = t, this } onChange(t) { return this._onChange = t, this } _callOnChange() { this.parent._callOnChange(this), void 0 !== this._onChange && this._onChange.call(this, this.getValue()), this._changed = !0 } onFinishChange(t) { return this._onFinishChange = t, this } _callOnFinishChange() { this._changed && (this.parent._callOnFinishChange(this), void 0 !== this._onFinishChange && this._onFinishChange.call(this, this.getValue())), this._changed = !1 } reset() { return this.setValue(this.initialValue), this._callOnFinishChange(), this } enable(t = !0) { return this.disable(!t) } disable(t = !0) { return t === this._disabled || (this._disabled = t, this.domElement.classList.toggle("disabled", t), this.$disable.toggleAttribute("disabled", t)), this } options(t) { const e = this.parent.add(this.object, this.property, t); return e.name(this._name), this.destroy(), e } min(t) { return this } max(t) { return this } step(t) { return this } listen(t = !0) { return this._listening = t, void 0 !== this._listenCallbackID && (cancelAnimationFrame(this._listenCallbackID), this._listenCallbackID = void 0), this._listening && this._listenCallback(), this } _listenCallback() { this._listenCallbackID = requestAnimationFrame(this._listenCallback), this.updateDisplay() } getValue() { return this.object[this.property] } setValue(t) { return this.object[this.property] = t, this._callOnChange(), this.updateDisplay(), this } updateDisplay() { return this } load(t) { return this.setValue(t), this._callOnFinishChange(), this } save() { return this.getValue() } destroy() { this.parent.children.splice(this.parent.children.indexOf(this), 1), this.parent.controllers.splice(this.parent.controllers.indexOf(this), 1), this.parent.$children.removeChild(this.domElement) } } class cc extends hc { constructor(t, e, i) { super(t, e, i, "boolean", "label"), this.$input = document.createElement("input"), this.$input.setAttribute("type", "checkbox"), this.$input.setAttribute("aria-labelledby", this.$name.id), this.$widget.appendChild(this.$input), this.$input.addEventListener("change", (() => { this.setValue(this.$input.checked), this._callOnFinishChange() })), this.$disable = this.$input, this.updateDisplay() } updateDisplay() { return this.$input.checked = this.getValue(), this } } function dc(t) { let e, i; return (e = t.match(/(#|0x)?([a-f0-9]{6})/i)) ? i = e[2] : (e = t.match(/rgb\(\s*(\d*)\s*,\s*(\d*)\s*,\s*(\d*)\s*\)/)) ? i = parseInt(e[1]).toString(16).padStart(2, 0) + parseInt(e[2]).toString(16).padStart(2, 0) + parseInt(e[3]).toString(16).padStart(2, 0) : (e = t.match(/^#?([a-f0-9])([a-f0-9])([a-f0-9])$/i)) && (i = e[1] + e[1] + e[2] + e[2] + e[3] + e[3]), !!i && "#" + i } const uc = { isPrimitive: !0, match: t => "string" == typeof t, fromHexString: dc, toHexString: dc }, pc = { isPrimitive: !0, match: t => "number" == typeof t, fromHexString: t => parseInt(t.substring(1), 16), toHexString: t => "#" + t.toString(16).padStart(6, 0) }, mc = { isPrimitive: !1, match: Array.isArray, fromHexString(t, e, i = 1) { const n = pc.fromHexString(t); e[0] = (n >> 16 & 255) / 255 * i, e[1] = (n >> 8 & 255) / 255 * i, e[2] = (255 & n) / 255 * i }, toHexString: ([t, e, i], n = 1) => pc.toHexString(t * (n = 255 / n) Object(t) === t, fromHexString(t, e, i = 1) { const n = pc.fromHexString(t); e.r = (n >> 16 & 255) / 255 * i, e.g = (n >> 8 & 255) / 255 * i, e.b = (255 & n) / 255 * i }, toHexString: ({ r: t, g: e, b: i }, n = 1) => pc.toHexString(t * (n = 255 / n) t.match(s)))), this._rgbScale = n, this._initialValueHexString = this.save(), this._textFocused = !1, this.$input.addEventListener("input", (() => { this._setValueFromHexString(this.$input.value) })), this.$input.addEventListener("blur", (() => { this._callOnFinishChange() })), this.$text.addEventListener("input", (() => { const t = dc(this.$text.value); t && this._setValueFromHexString(t) })), this.$text.addEventListener("focus", (() => { this._textFocused = !0, this.$text.select() })), this.$text.addEventListener("blur", (() => { this._textFocused = !1, this.updateDisplay(), this._callOnFinishChange() })), this.$disable = this.$text, this.updateDisplay() } reset() { return this._setValueFromHexString(this._initialValueHexString), this } _setValueFromHexString(t) { if (this._format.isPrimitive) { const e = this._format.fromHexString(t); this.setValue(e) } else this._format.fromHexString(t, this.getValue(), this._rgbScale), this._callOnChange(), this.updateDisplay() } save() { return this._format.toHexString(this.getValue(), this._rgbScale) } load(t) { return this._setValueFromHexString(t), this._callOnFinishChange(), this } updateDisplay() { return this.$input.value = this._format.toHexString(this.getValue(), this._rgbScale), this._textFocused || (this.$text.value = this.$input.value.substring(1)), this.$display.style.backgroundColor = this.$input.value, this } } class _c extends hc { constructor(t, e, i) { super(t, e, i, "function"), this.$button = document.createElement("button"), this.$button.appendChild(this.$name), this.$widget.appendChild(this.$button), this.$button.addEventListener("click", (t => { t.preventDefault(), this.getValue().call(this.object) })), this.$button.addEventListener("touchstart", (() => { })), this.$disable = this.$button } } class xc extends hc { constructor(t, e, i, n, s, r) { super(t, e, i, "number"), this._initInput(), this.min(n), this.max(s); const a = void 0 !== r; this.step(a ? r : this._getImplicitStep(), a), this.updateDisplay() } min(t) { return this._min = t, this._onUpdateMinMax(), this } max(t) { return this._max = t, this._onUpdateMinMax(), this } step(t, e = !0) { return this._step = t, this._stepExplicit = e, this } updateDisplay() { const t = this.getValue(); if (this._hasSlider) { let e = (t - this._min) / (this._max - this._min); e = Math.max(0, Math.min(e, 1)), this.$fill.style.width = 100 * e + "%" } return this._inputFocused || (this.$input.value = t), this } _initInput() { this.$input = document.createElement("input"), this.$input.setAttribute("type", "number"), this.$input.setAttribute("step", "any"), this.$input.setAttribute("aria-labelledby", this.$name.id), this.$widget.appendChild(this.$input), this.$disable = this.$input; const t = t => { const e = parseFloat(this.$input.value); isNaN(e) || (this._snapClampSetValue(e + t), this.$input.value = this.getValue()) }; let e, i, n, s, r, a = !1; const o = t => { if (a) { const n = t.clientX - e, s = t.clientY - i; Math.abs(s) > 5 ? (t.preventDefault(), this.$input.blur(), a = !1, this._setDraggingStyle(!0, "vertical")) : Math.abs(n) > 5 && l() } if (!a) { const e = t.clientY - n; r -= e * this._step * this._arrowKeyMultiplier(t), s + r > this._max ? r = this._max - s : s + r { this._setDraggingStyle(!1, "vertical"), this._callOnFinishChange(), window.removeEventListener("mousemove", o), window.removeEventListener("mouseup", l) }; this.$input.addEventListener("input", (() => { const t = parseFloat(this.$input.value); isNaN(t) || this.setValue(this._clamp(t)) })), this.$input.addEventListener("keydown", (e => { "Enter" === e.code && this.$input.blur(), "ArrowUp" === e.code && (e.preventDefault(), t(this._step * this._arrowKeyMultiplier(e))), "ArrowDown" === e.code && (e.preventDefault(), t(this._step * this._arrowKeyMultiplier(e) * -1)) })), this.$input.addEventListener("wheel", (e => { this._inputFocused && (e.preventDefault(), t(this._step * this._normalizeMouseWheel(e))) })), this.$input.addEventListener("mousedown", (t => { e = t.clientX, i = n = t.clientY, a = !0, s = this.getValue(), r = 0, window.addEventListener("mousemove", o), window.addEventListener("mouseup", l) })), this.$input.addEventListener("focus", (() => { this._inputFocused = !0 })), this.$input.addEventListener("blur", (() => { this._inputFocused = !1, this.updateDisplay(), this._callOnFinishChange() })) } _initSlider() { this._hasSlider = !0, this.$slider = document.createElement("div"), this.$slider.classList.add("slider"), this.$fill = document.createElement("div"), this.$fill.classList.add("fill"), this.$slider.appendChild(this.$fill), this.$widget.insertBefore(this.$slider, this.$input), this.domElement.classList.add("hasSlider"); const t = t => { const e = this.$slider.getBoundingClientRect(); let i = (n = t, s = e.left, r = e.right, a = this._min, (n - s) / (r - s) * (this._max - a) + a); var n, s, r, a; this._snapClampSetValue(i) }, e = e => { t(e.clientX) }, i = () => { this._callOnFinishChange(), this._setDraggingStyle(!1), window.removeEventListener("mousemove", e), window.removeEventListener("mouseup", i) }; let n, s, r = !1; const a = e => { e.preventDefault(), this._setDraggingStyle(!0), t(e.touches[0].clientX), r = !1 }, o = e => { if (r) { const t = e.touches[0].clientX - n, i = e.touches[0].clientY - s; Math.abs(t) > Math.abs(i) ? a(e) : (window.removeEventListener("touchmove", o), window.removeEventListener("touchend", l)) } else e.preventDefault(), t(e.touches[0].clientX) }, l = () => { this._callOnFinishChange(), this._setDraggingStyle(!1), window.removeEventListener("touchmove", o), window.removeEventListener("touchend", l) }, h = this._callOnFinishChange.bind(this); let c; this.$slider.addEventListener("mousedown", (n => { this._setDraggingStyle(!0), t(n.clientX), window.addEventListener("mousemove", e), window.addEventListener("mouseup", i) })), this.$slider.addEventListener("touchstart", (t => { t.touches.length > 1 || (this._hasScrollBar ? (n = t.touches[0].clientX, s = t.touches[0].clientY, r = !0) : a(t), window.addEventListener("touchmove", o), window.addEventListener("touchend", l)) })), this.$slider.addEventListener("wheel", (t => { if (Math.abs(t.deltaX) this._max && (t = this._max), t } _snapClampSetValue(t) { this.setValue(this._clamp(this._snap(t))) } get _hasScrollBar() { const t = this.parent.root.$children; return t.scrollHeight > t.clientHeight } get _hasMin() { return void 0 !== this._min } get _hasMax() { return void 0 !== this._max } } class yc extends hc { constructor(t, e, i, n) { super(t, e, i, "option"), this.$select = document.createElement("select"), this.$select.setAttribute("aria-labelledby", this.$name.id), this.$display = document.createElement("div"), this.$display.classList.add("display"), this._values = Array.isArray(n) ? n : Object.values(n), this._names = Array.isArray(n) ? n : Object.keys(n), this._names.forEach((t => { const e = document.createElement("option"); e.innerHTML = t, this.$select.appendChild(e) })), this.$select.addEventListener("change", (() => { this.setValue(this._values[this.$select.selectedIndex]), this._callOnFinishChange() })), this.$select.addEventListener("focus", (() => { this.$display.classList.add("focus") })), this.$select.addEventListener("blur", (() => { this.$display.classList.remove("focus") })), this.$widget.appendChild(this.$select), this.$widget.appendChild(this.$display), this.$disable = this.$select, this.updateDisplay() } updateDisplay() { const t = this.getValue(), e = this._values.indexOf(t); return this.$select.selectedIndex = e, this.$display.innerHTML = -1 === e ? t : this._names[e], this } } class bc extends hc { constructor(t, e, i) { super(t, e, i, "string"), this.$input = document.createElement("input"), this.$input.setAttribute("type", "text"), this.$input.setAttribute("aria-labelledby", this.$name.id), this.$input.addEventListener("input", (() => { this.setValue(this.$input.value) })), this.$input.addEventListener("keydown", (t => { "Enter" === t.code && this.$input.blur() })), this.$input.addEventListener("blur", (() => { this._callOnFinishChange() })), this.$widget.appendChild(this.$input), this.$disable = this.$input, this.updateDisplay() } updateDisplay() { return this.$input.value = this.getValue(), this } } let Mc, wc, Sc, Tc = !1; class Ac { constructor({ parent: t, autoPlace: e = void 0 === t, container: i, width: n, title: s = "Controls", injectStyles: r = !0, touchStyles: a = !0 } = {}) { if (this.parent = t, this.root = t ? t.root : this, this.children = [], this.controllers = [], this.folders = [], this._closed = !1, this._hidden = !1, this.domElement = document.createElement("div"), this.domElement.classList.add("lil-gui"), this.$title = document.createElement("div"), this.$title.classList.add("title"), this.$title.setAttribute("role", "button"), this.$title.setAttribute("aria-expanded", !0), this.$title.setAttribute("tabindex", 0), this.$title.addEventListener("click", (() => this.openAnimated(this._closed))), this.$title.addEventListener("keydown", (t => { "Enter" !== t.code && "Space" !== t.code || (t.preventDefault(), this.$title.click()) })), this.$title.addEventListener("touchstart", (() => { })), this.$children = document.createElement("div"), this.$children.classList.add("children"), this.domElement.appendChild(this.$title), this.domElement.appendChild(this.$children), this.title(s), a && this.domElement.classList.add("allow-touch-styles"), this.parent) return this.parent.children.push(this), this.parent.folders.push(this), void this.parent.$children.appendChild(this.domElement); this.domElement.classList.add("root"), !Tc && r && (function () { const t = document.createElement("style"); t.innerHTML = '.lil-gui{--background-color:#1f1f1f;--text-color:#ebebeb;--title-background-color:#111;--title-text-color:#ebebeb;--widget-color:#424242;--hover-color:#4f4f4f;--focus-color:#595959;--number-color:#2cc9ff;--string-color:#a2db3c;--font-size:11px;--input-font-size:11px;--font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Arial,sans-serif;--font-family-mono:Menlo,Monaco,Consolas,"Droid Sans Mono",monospace;--padding:4px;--spacing:4px;--widget-height:20px;--name-width:45%;--slider-knob-width:2px;--slider-input-width:27%;--color-input-width:27%;--slider-input-min-width:45px;--color-input-min-width:45px;--folder-indent:7px;--widget-padding:0 0 0 3px;--widget-border-radius:2px;--checkbox-size:calc(var(--widget-height)*0.75);--scrollbar-width:5px;background-color:var(--background-color);color:var(--text-color);font-family:var(--font-family);font-size:var(--font-size);font-style:normal;font-weight:400;line-height:1;text-align:left;touch-action:manipulation;user-select:none;-webkit-user-select:none}.lil-gui,.lil-gui *{box-sizing:border-box;margin:0;padding:0}.lil-gui.root{display:flex;flex-direction:column;width:var(--width,245px)}.lil-gui.root>.title{background:var(--title-background-color);color:var(--title-text-color)}.lil-gui.root>.children{overflow-x:hidden;overflow-y:auto}.lil-gui.root>.children::-webkit-scrollbar{background:var(--background-color);height:var(--scrollbar-width);width:var(--scrollbar-width)}.lil-gui.root>.children::-webkit-scrollbar-thumb{background:var(--focus-color);border-radius:var(--scrollbar-width)}.lil-gui.force-touch-styles{--widget-height:28px;--padding:6px;--spacing:6px;--font-size:13px;--input-font-size:16px;--folder-indent:10px;--scrollbar-width:7px;--slider-input-min-width:50px;--color-input-min-width:65px}.lil-gui.autoPlace{max-height:100%;position:fixed;right:15px;top:0;z-index:1001}.lil-gui .controller{align-items:center;display:flex;margin:var(--spacing) 0;padding:0 var(--padding)}.lil-gui .controller.disabled{opacity:.5}.lil-gui .controller.disabled,.lil-gui .controller.disabled *{pointer-events:none!important}.lil-gui .controller>.name{flex-shrink:0;line-height:var(--widget-height);min-width:var(--name-width);padding-right:var(--spacing);white-space:pre}.lil-gui .controller .widget{align-items:center;display:flex;min-height:var(--widget-height);position:relative;width:100%}.lil-gui .controller.string input{color:var(--string-color)}.lil-gui .controller.boolean .widget{cursor:pointer}.lil-gui .controller.color .display{border-radius:var(--widget-border-radius);height:var(--widget-height);position:relative;width:100%}.lil-gui .controller.color input[type=color]{cursor:pointer;height:100%;opacity:0;width:100%}.lil-gui .controller.color input[type=text]{flex-shrink:0;font-family:var(--font-family-mono);margin-left:var(--spacing);min-width:var(--color-input-min-width);width:var(--color-input-width)}.lil-gui .controller.option select{max-width:100%;opacity:0;position:absolute;width:100%}.lil-gui .controller.option .display{background:var(--widget-color);border-radius:var(--widget-border-radius);height:var(--widget-height);line-height:var(--widget-height);max-width:100%;overflow:hidden;padding-left:.55em;padding-right:1.75em;pointer-events:none;position:relative;word-break:break-all}.lil-gui .controller.option .display.active{background:var(--focus-color)}.lil-gui .controller.option .display:after{bottom:0;content:"↕";font-family:lil-gui;padding-right:.375em;position:absolute;right:0;top:0}.lil-gui .controller.option .widget,.lil-gui .controller.option select{cursor:pointer}.lil-gui .controller.number input{color:var(--number-color)}.lil-gui .controller.number.hasSlider input{flex-shrink:0;margin-left:var(--spacing);min-width:var(--slider-input-min-width);width:var(--slider-input-width)}.lil-gui .controller.number .slider{background-color:var(--widget-color);border-radius:var(--widget-border-radius);cursor:ew-resize;height:var(--widget-height);overflow:hidden;padding-right:var(--slider-knob-width);touch-action:pan-y;width:100%}.lil-gui .controller.number .slider.active{background-color:var(--focus-color)}.lil-gui .controller.number .slider.active .fill{opacity:.95}.lil-gui .controller.number .fill{border-right:var(--slider-knob-width) solid var(--number-color);box-sizing:content-box;height:100%}.lil-gui-dragging .lil-gui{--hover-color:var(--widget-color)}.lil-gui-dragging *{cursor:ew-resize!important}.lil-gui-dragging.lil-gui-vertical *{cursor:ns-resize!important}.lil-gui .title{--title-height:calc(var(--widget-height) + var(--spacing)*1.25);-webkit-tap-highlight-color:transparent;text-decoration-skip:objects;cursor:pointer;font-weight:600;height:var(--title-height);line-height:calc(var(--title-height) - 4px);outline:none;padding:0 var(--padding)}.lil-gui .title:before{content:"▾";display:inline-block;font-family:lil-gui;padding-right:2px}.lil-gui .title:active{background:var(--title-background-color);opacity:.75}.lil-gui.root>.title:focus{text-decoration:none!important}.lil-gui.closed>.title:before{content:"▸"}.lil-gui.closed>.children{opacity:0;transform:translateY(-7px)}.lil-gui.closed:not(.transition)>.children{display:none}.lil-gui.transition>.children{overflow:hidden;pointer-events:none;transition-duration:.3s;transition-property:height,opacity,transform;transition-timing-function:cubic-bezier(.2,.6,.35,1)}.lil-gui .children:empty:before{content:"Empty";display:block;font-style:italic;height:var(--widget-height);line-height:var(--widget-height);margin:var(--spacing) 0;opacity:.5;padding:0 var(--padding)}.lil-gui.root>.children>.lil-gui>.title{border-width:0;border-bottom:1px solid var(--widget-color);border-left:0 solid var(--widget-color);border-right:0 solid var(--widget-color);border-top:1px solid var(--widget-color);transition:border-color .3s}.lil-gui.root>.children>.lil-gui.closed>.title{border-bottom-color:transparent}.lil-gui+.controller{border-top:1px solid var(--widget-color);margin-top:0;padding-top:var(--spacing)}.lil-gui .lil-gui .lil-gui>.title{border:none}.lil-gui .lil-gui .lil-gui>.children{border:none;border-left:2px solid var(--widget-color);margin-left:var(--folder-indent)}.lil-gui .lil-gui .controller{border:none}.lil-gui input{-webkit-tap-highlight-color:transparent;background:var(--widget-color);border:0;border-radius:var(--widget-border-radius);color:var(--text-color);font-family:var(--font-family);font-size:var(--input-font-size);height:var(--widget-height);outline:none;width:100%}.lil-gui input:disabled{opacity:1}.lil-gui input[type=number],.lil-gui input[type=text]{padding:var(--widget-padding)}.lil-gui input[type=number]:focus,.lil-gui input[type=text]:focus{background:var(--focus-color)}.lil-gui input::-webkit-inner-spin-button,.lil-gui input::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.lil-gui input[type=number]{-moz-appearance:textfield}.lil-gui input[type=checkbox]{appearance:none;-webkit-appearance:none;border-radius:var(--widget-border-radius);cursor:pointer;height:var(--checkbox-size);text-align:center;width:var(--checkbox-size)}.lil-gui input[type=checkbox]:checked:before{content:"✓";font-family:lil-gui;font-size:var(--checkbox-size);line-height:var(--checkbox-size)}.lil-gui button{-webkit-tap-highlight-color:transparent;background:var(--widget-color);border:1px solid var(--widget-color);border-radius:var(--widget-border-radius);color:var(--text-color);cursor:pointer;font-family:var(--font-family);font-size:var(--font-size);height:var(--widget-height);line-height:calc(var(--widget-height) - 4px);outline:none;text-align:center;text-transform:none;width:100%}.lil-gui button:active{background:var(--focus-color)}@font-face{font-family:lil-gui;src:url("data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAAUsAAsAAAAACJwAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAAH4AAADAImwmYE9TLzIAAAGIAAAAPwAAAGBKqH5SY21hcAAAAcgAAAD0AAACrukyyJBnbHlmAAACvAAAAF8AAACEIZpWH2hlYWQAAAMcAAAAJwAAADZfcj2zaGhlYQAAA0QAAAAYAAAAJAC5AHhobXR4AAADXAAAABAAAABMAZAAAGxvY2EAAANsAAAAFAAAACgCEgIybWF4cAAAA4AAAAAeAAAAIAEfABJuYW1lAAADoAAAASIAAAIK9SUU/XBvc3QAAATEAAAAZgAAAJCTcMc2eJxVjbEOgjAURU+hFRBK1dGRL+ALnAiToyMLEzFpnPz/eAshwSa97517c/MwwJmeB9kwPl+0cf5+uGPZXsqPu4nvZabcSZldZ6kfyWnomFY/eScKqZNWupKJO6kXN3K9uCVoL7iInPr1X5baXs3tjuMqCtzEuagm/AAlzQgPAAB4nGNgYRBlnMDAysDAYM/gBiT5oLQBAwuDJAMDEwMrMwNWEJDmmsJwgCFeXZghBcjlZMgFCzOiKOIFAB71Bb8AeJy1kjFuwkAQRZ+DwRAwBtNQRUGKQ8OdKCAWUhAgKLhIuAsVSpWz5Bbkj3dEgYiUIszqWdpZe+Z7/wB1oCYmIoboiwiLT2WjKl/jscrHfGg/pKdMkyklC5Zs2LEfHYpjcRoPzme9MWWmk3dWbK9ObkWkikOetJ554fWyoEsmdSlt+uR0pCJR34b6t/TVg1SY3sYvdf8vuiKrpyaDXDISiegp17p7579Gp3p++y7HPAiY9pmTibljrr85qSidtlg4+l25GLCaS8e6rRxNBmsnERunKbaOObRz7N72ju5vdAjYpBXHgJylOAVsMseDAPEP8LYoUHicY2BiAAEfhiAGJgZWBgZ7RnFRdnVJELCQlBSRlATJMoLV2DK4glSYs6ubq5vbKrJLSbGrgEmovDuDJVhe3VzcXFwNLCOILB/C4IuQ1xTn5FPilBTj5FPmBAB4WwoqAHicY2BkYGAA4sk1sR/j+W2+MnAzpDBgAyEMQUCSg4EJxAEAwUgFHgB4nGNgZGBgSGFggJMhDIwMqEAYAByHATJ4nGNgAIIUNEwmAABl3AGReJxjYAACIQYlBiMGJ3wQAEcQBEV4nGNgZGBgEGZgY2BiAAEQyQWEDAz/wXwGAAsPATIAAHicXdBNSsNAHAXwl35iA0UQXYnMShfS9GPZA7T7LgIu03SSpkwzYTIt1BN4Ak/gKTyAeCxfw39jZkjymzcvAwmAW/wgwHUEGDb36+jQQ3GXGot79L24jxCP4gHzF/EIr4jEIe7wxhOC3g2TMYy4Q7+Lu/SHuEd/ivt4wJd4wPxbPEKMX3GI5+DJFGaSn4qNzk8mcbKSR6xdXdhSzaOZJGtdapd4vVPbi6rP+cL7TGXOHtXKll4bY1Xl7EGnPtp7Xy2n00zyKLVHfkHBa4IcJ2oD3cgggWvt/V/FbDrUlEUJhTn/0azVWbNTNr0Ens8de1tceK9xZmfB1CPjOmPH4kitmvOubcNpmVTN3oFJyjzCvnmrwhJTzqzVj9jiSX911FjeAAB4nG3HMRKCMBBA0f0giiKi4DU8k0V2GWbIZDOh4PoWWvq6J5V8If9NVNQcaDhyouXMhY4rPTcG7jwYmXhKq8Wz+p762aNaeYXom2n3m2dLTVgsrCgFJ7OTmIkYbwIbC6vIB7WmFfAAAA==") format("woff")}@media (pointer:coarse){.lil-gui.allow-touch-styles{--widget-height:28px;--padding:6px;--spacing:6px;--font-size:13px;--input-font-size:16px;--folder-indent:10px;--scrollbar-width:7px;--slider-input-min-width:50px;--color-input-min-width:65px}}@media (hover:hover){.lil-gui .controller.color .display:hover:before{border:1px solid #fff9;border-radius:var(--widget-border-radius);bottom:0;content:" ";display:block;left:0;position:absolute;right:0;top:0}.lil-gui .controller.option .display.focus{background:var(--focus-color)}.lil-gui .controller.option .widget:hover .display{background:var(--hover-color)}.lil-gui .controller.number .slider:hover{background-color:var(--hover-color)}body:not(.lil-gui-dragging) .lil-gui .title:hover{background:var(--title-background-color);opacity:.85}.lil-gui .title:focus{text-decoration:underline var(--focus-color)}.lil-gui input:hover{background:var(--hover-color)}.lil-gui input:active{background:var(--focus-color)}.lil-gui input[type=checkbox]:focus{box-shadow:inset 0 0 0 1px var(--focus-color)}.lil-gui button:hover{background:var(--hover-color);border-color:var(--hover-color)}.lil-gui button:focus{border-color:var(--focus-color)}}'; const e = document.querySelector("head link[rel=stylesheet], head style"); e ? document.head.insertBefore(t, e) : document.head.appendChild(t) }(), Tc = !0), i ? i.appendChild(this.domElement) : e && (this.domElement.classList.add("autoPlace"), document.body.appendChild(this.domElement)), n && this.domElement.style.setProperty("--width", n + "px"), this.domElement.addEventListener("keydown", (t => t.stopPropagation())), this.domElement.addEventListener("keyup", (t => t.stopPropagation())) } add(t, e, i, n, s) { if (Object(i) === i) return new yc(this, t, e, i); switch (typeof t[e]) { case "number": return new xc(this, t, e, i, n, s); case "boolean": return new cc(this, t, e); case "string": return new bc(this, t, e); case "function": return new _c(this, t, e) } } addColor(t, e, i = 1) { return new vc(this, t, e, i) } addFolder(t) { return new Ac({ parent: this, title: t }) } load(t, e = !0) { return t.controllers && this.controllers.forEach((e => { e instanceof _c || e._name in t.controllers && e.load(t.controllers[e._name]) })), e && t.folders && this.folders.forEach((e => { e._title in t.folders && e.load(t.folders[e._title]) })), this } save(t = !0) { const e = { controllers: {}, folders: {} }; return this.controllers.forEach((t => { if (!(t instanceof _c)) { if (t._name in e.controllers) throw new Error(`Cannot save GUI with duplicate property "${t._name}"`); e.controllers[t._name] = t.save() } })), t && this.folders.forEach((t => { if (t._title in e.folders) throw new Error(`Cannot save GUI with duplicate folder "${t._title}"`); e.folders[t._title] = t.save() })), e } open(t = !0) { return this._closed = !t, this.$title.setAttribute("aria-expanded", !this._closed), this.domElement.classList.toggle("closed", this._closed), this } close() { return this.open(!1) } show(t = !0) { return this._hidden = !t, this.domElement.style.display = this._hidden ? "none" : "", this } hide() { return this.show(!1) } openAnimated(t = !0) { return this._closed = !t, this.$title.setAttribute("aria-expanded", !this._closed), requestAnimationFrame((() => { const e = this.$children.clientHeight; this.$children.style.height = e + "px", this.domElement.classList.add("transition"); const i = t => { t.target === this.$children && (this.$children.style.height = "", this.domElement.classList.remove("transition"), this.$children.removeEventListener("transitionend", i)) }; this.$children.addEventListener("transitionend", i); const n = t ? this.$children.scrollHeight : 0; this.domElement.classList.toggle("closed", !t), requestAnimationFrame((() => { this.$children.style.height = n + "px" })) })), this } title(t) { return this._title = t, this.$title.innerHTML = t, this } reset(t = !0) { return (t ? this.controllersRecursive() : this.controllers).forEach((t => t.reset())), this } onChange(t) { return this._onChange = t, this } _callOnChange(t) { this.parent && this.parent._callOnChange(t), void 0 !== this._onChange && this._onChange.call(this, { object: t.object, property: t.property, value: t.getValue(), controller: t }) } onFinishChange(t) { return this._onFinishChange = t, this } _callOnFinishChange(t) { this.parent && this.parent._callOnFinishChange(t), void 0 !== this._onFinishChange && this._onFinishChange.call(this, { object: t.object, property: t.property, value: t.getValue(), controller: t }) } destroy() { this.parent && (this.parent.children.splice(this.parent.children.indexOf(this), 1), this.parent.folders.splice(this.parent.folders.indexOf(this), 1)), this.domElement.parentElement && this.domElement.parentElement.removeChild(this.domElement), Array.from(this.children).forEach((t => t.destroy())) } controllersRecursive() { let t = Array.from(this.controllers); return this.folders.forEach((e => { t = t.concat(e.controllersRecursive()) })), t } foldersRecursive() { let t = Array.from(this.folders); return this.folders.forEach((e => { t = t.concat(e.foldersRecursive()) })), t } } function Ec(t) { switch (wc = t, Sc = t.viewerModel, w) { case "dev": Pc(), function () { Mc || (Mc = new Ac); const t = Mc.addFolder("Helper"); t.add({ "Axes Helper": !1 }, "Axes Helper").onChange((t => { t ? wc.viewerModel.modelGroup.add(new Xo(10)) : wc.viewerModel.modelGroup.remove(wc.viewerModel.modelGroup.children.find((t => "AxesHelper" == t.type))) })), t.add({ "Grid Helper": !1 }, "Grid Helper").onChange((t => { t ? wc.viewerModel.modelGroup.add(new Wo(10)) : wc.viewerModel.modelGroup.remove(wc.viewerModel.modelGroup.children.find((t => "GridHelper" == t.type))) })) }(), Lc(), ((t, e) => { const i = v("div.v3d-dev-text", document.body); v(`div${e}`, i).innerHTML = t })(navigator.userAgent, "#agent"); break; case "design": Ic(t), function () { Mc || (Mc = new Ac), wc.controls.minDistance = 0, wc.controls.maxDistance = 1 / 0; const t = { cameraSetting: Mc.addFolder("Camera Setting"), positionSetting: Mc.addFolder("Set Position") }; t.cameraSetting.add({ "camera fov": wc.camera.fov }, "camera fov", 0, 90).step(1).onChange((t => { wc.camera.fov = t, wc.camera.updateProjectionMatrix() })), t.cameraSetting.add({ "camera aspect": wc.camera.aspect }, "camera aspect", 0, 4).step(.1).onChange((t => { wc.camera.aspect = t, wc.camera.updateProjectionMatrix() })), t.cameraSetting.add({ "camera far": wc.camera.far }, "camera far", 0, 4e3).step(100).onChange((t => { wc.camera.far = t, wc.camera.updateProjectionMatrix() })), t.cameraSetting.add({ "camera filmGauge": wc.camera.filmGauge }, "camera filmGauge", 1, 1e3).step(1).onChange((t => { wc.camera.filmGauge = t, wc.camera.updateProjectionMatrix() })), t.cameraSetting.add({ "camera filmOffset": wc.camera.filmOffset }, "camera filmOffset", 1, 1e3).min(-1e3).step(.1).onChange((t => { wc.camera.filmOffset = t, wc.camera.updateProjectionMatrix() })); const e = { angleX: 0, angleY: 0, angleZ: 0, panX: 0, panY: 0, zoom: wc.camera.position.z, actionSetTime: 0 }; function i() { wc.viewerModel.setPosition({ angleX: e.angleX * (Math.PI / 180), angleY: e.angleY * (Math.PI / 180), angleZ: e.angleZ * (Math.PI / 180), panX: e.panX, panY: e.panY, zoom: e.zoom }, 0) } t.positionSetting.add(e, "angleX", -360, 360, 1).onChange(i), t.positionSetting.add(e, "angleY", -360, 360, 1).onChange(i), t.positionSetting.add(e, "angleZ", -360, 360, 1).onChange(i), t.positionSetting.add(e, "panX", -10, 10, .1).onChange(i), t.positionSetting.add(e, "panY", -10, 10, .1).onChange(i), t.positionSetting.add(e, "zoom", 10, 80).onChange(i).name("camera distance (zoom)"), t.positionSetting.add(e, "actionSetTime", 0, 100, 1).onChange((t => Cc(t / 100))), t.positionSetting.add({ Reset: function () { e.angleX = 0, e.angleY = 0, e.angleZ = 0, e.panX = 0, e.panY = 0, e.zoom = E, e.actionSetTime = 0, t.positionSetting.controllers.forEach((t => t.updateDisplay())), Cc(0), i() } }, "Reset"), t.positionSetting.add({ Screenshot: () => V3D.screenShot() }, "Screenshot") }(); break; case "mmd": wc.OPTIONS.autoRotation = !1, Pc(), Ic(t), Lc(), Dc(); break; case "mmd-material": wc.OPTIONS.autoRotation = !1, Ic(t), Dc(), function () { Mc || (Mc = new Ac); const t = V3D.devTools.originModel.materials; let e = wc.viewerModel.gltfObject, i = {}, n = {}; e.traverse((t => { t.isMesh && !i[t.material.name] && (i[t.material.name] = t.material) })); const s = Object.keys(i).sort(), r = Mc.addFolder("Material Mode"); function a(t) { if (n[t]) return; const e = i[t]; e.name, n[t] = r.addFolder(e.name), n[t].addColor(e, "color").onChange((e => o(t, "color", e))), n[t].addColor(e, "emissive").onChange((e => o(t, "emissive", e))), n[t].add(e, "emissiveIntensity", 0, 2, .01).step(.01).onChange((e => o(t, "emissiveIntensity", e))), n[t].add(e, "metalness", 0, 1, .01).step(.01).onChange((e => o(t, "metalness", e))), n[t].add(e, "roughness", 0, 1, .01).step(.01).onChange((e => o(t, "roughness", e))), n[t].add(e, "opacity", 0, 1, .01).step(.01).onChange((e => o(t, "opacity", e))), n[t].add(e, "depthWrite").onChange((e => o(t, "depthWrite", e))), n[t].add(e, "toneMapped").onChange((e => o(t, "toneMapped", e))), n[t].add(e, "transparent").onChange((e => o(t, "transparent", e))), n[t].add(e, "visible").onChange((e => o(t, "visible", e))), n[t].add({ X: () => function (t) { n[t].destroy(), delete n[t] }(t) }, "X") } function o(t, e, i) { const n = Sc.modelData.color.find((t => t.key === Sc.colorName)).custumMaterial, s = n.find((e => 1 === e.target.length && e.target[0] === t)); if (i = i && i.isColor ? l(i) : i, s) s.options[e] = i; else { const s = { target: [t], options: {} }; s.options[e] = i, n.push(s) } } function l(t) { return `rgb(${parseInt(255 * t.r)}, ${parseInt(255 * t.g)}, ${parseInt(255 * t.b)})` } r.add(i, "material list", s).onChange(a), r.add({ "get json": () => function () { const e = {}, i = new Set, n = new Map; e[wc.modelName] = JSON.parse(JSON.stringify(wc.modelData)), e[wc.modelName].color.forEach((t => { delete t.left, t.custumMaterial.forEach((t => { t.target.forEach((e => { if (s.includes(e)) { i.add(e); const s = n.get(e) || new Set; Object.keys(t.options).forEach((t => s.add(t))), n.set(e, s) } })) })) })), n.forEach(((i, n, s) => { e[wc.modelName].color.forEach((e => { const s = e.custumMaterial, r = s.find((t => t.target.length > 1 && t.target.includes(n))); let a = s.find((t => 1 === t.target.length && t.target[0] === n)); a || (a = { target: [n], options: {} }, s.push(a)), i.forEach((e => { const i = t[n][e]; a.options.hasOwnProperty(e) || (a.options[e] = r ? r.options[e] : i && i.isColor ? l(i) : i) })) })) })); const r = document.createElement("a"), a = new Blob([JSON.stringify(e, null, 2)], { type: "application/json" }), o = `${wc.modelName}_${(new Date).toISOString().slice(0, 16).split(/[-:TO]/g).join("_")}`; r.href = URL.createObjectURL(a), r.download = o, r.click() }() }, "get json"), window.addEventListener("changeColorAfter", (t => { !function () { i = {}, wc.productGroup.model.traverse((t => { t.isMesh && !i[t.material.name] && (i[t.material.name] = t.material) })); for (let t in n) n[t].destroy(), delete n[t], a(t) }() })) }() } } function Pc() { const t = wc.container, e = t.style.opacity; t.addEventListener("dragover", (e => { e.preventDefault(), t.style.opacity = .6 })), t.addEventListener("dragleave", (i => { t.style.opacity = e })), t.addEventListener("drop", (i => { i.preventDefault(), t.style.opacity = e; const n = i.dataTransfer.files[0], s = URL.createObjectURL(n); switch (null !== n.name.match(/(glb)|(hdr)$/g) ? n.name.match(/(glb)|(hdr)$/g)[0] : null) { case "glb": targetViewer.loadModelObject(void 0, s); break; case "hdr": (new RGBELoader).load(s, (t => { const e = new Qn(targetViewer.renderer); e.compileEquirectangularShader(), e.far = 50, targetViewer.scene.environment = e.fromEquirectangular(t).texture })) } })) } function Cc(t) { wc.viewerModel.modelGroup.children[0].mixer.setTrack(t) } function Lc() { Mc || (Mc = new Ac), Mc.add({ Screenshot: () => V3D.screenShot() }, "Screenshot") } function Dc() { Mc || (Mc = new Ac), Mc.add({ toneMapping: "ACESFilmic(무선ver)" }, "toneMapping", { None: "None", Linear: "Linear", Reinhard: "Reinhard", Cineon: "Cineon", ACESFilmic: "ACESFilmic", "ACESFilmic(무선ver)": "ACESFilmic(무선ver)", Custom: "Custom" }).onChange((function (t) { switch (t) { case "None": wc.renderer.toneMapping = 0, wc.renderer.toneMappingExposure = 1; break; case "Linear": wc.renderer.toneMapping = 1, wc.renderer.toneMappingExposure = 1; break; case "Reinhard": wc.renderer.toneMapping = 2, wc.renderer.toneMappingExposure = 1; break; case "Cineon": wc.renderer.toneMapping = 3, wc.renderer.toneMappingExposure = 1; break; case "ACESFilmic": wc.renderer.toneMapping = 4, wc.renderer.toneMappingExposure = 1; break; case "ACESFilmic(무선ver)": wc.renderer.toneMapping = 4, wc.renderer.toneMappingExposure = 1.2; break; case "Custom": wc.renderer.toneMapping = 5, wc.renderer.toneMappingExposure = 1 } })), Mc.add(wc.renderer, "toneMappingExposure", 0, 2).step(.01).listen().onChange((function (t) { wc.renderer.toneMappingExposure = t })) } function Ic(t) { Mc || (Mc = new Ac); const e = { viewerSetting: Mc.addFolder("Viewer Setting") }, i = wc.OPTIONS.modelName, n = t.productData.compareAll, s = document.querySelector(".v3d-controls__product-wrap"), r = document.querySelector(".v3d-controls__btns-wrap"); e.viewerSetting.add({ "UI hidden": !1 }, "UI hidden").onChange((t => { t ? (s.style.display = "none", r.style.display = "none") : (s.style.display = "block", r.style.display = "block") })), e.viewerSetting.add({ "Model Change": `${i}` }, "Model Change").options(n.reverse()).onChange((t => { location.href = location.href.replace(`model_name=${wc.viewerModel.modelName}`, `model_name=${t}`) })) } class Rc { constructor(t, e, i) { this.OPTIONS = new Fh(e, i), this.uiControls = new nl(this.OPTIONS, this), this.scene = new da, this.camera = new bn, this.renderer = new ca({ antialias: !0, alpha: !0 }), this.domElement = this.renderer.domElement, this.container = t || document.getElementById("viewer-container"), this.observer = null, this.isInViewport = !1, this.isInitialRendered = !1, this.initialRender = null, this.hideDimTimeout = null, this.viewerModel = null, this.devTool = null, this.isInit = !1, this.init() } async init() { this.container.appendChild(this.renderer.domElement), this.setViewerBGColor(this.OPTIONS.bgcolor), this.setDefaultSettings(), this.productData = new Uh(this.OPTIONS, this.localData), await this.productData.loadViewerData(), this.eventManager = new lc(this), this.viewerModel = new zh(this.OPTIONS, this.productData, { container: this.container, scene: this.scene, renderer: this.renderer, camera: this.camera, uiControls: this.uiControls, viewer: this }), this.controls = new Yh(this.OPTIONS, this.camera, this.viewerModel.modelGroup, this.container), this.controls.enableZoom = this.OPTIONS.useZoom, this.controls.enablePan = this.OPTIONS.usePan, this.onResize(), this.setObserver() } initAfter() { if (window.location.host.indexOf("samsung.com") { try { uh.setDecoderPath(t) } catch (t) { uh.setDecoderPath("./assets/libs/draco/") } uh.setDecoderConfig({ type: "js" }), ph.setDRACOLoader(uh) })(this.OPTIONS.backupPath + "libs/draco/") } setViewerBGColor(t) { const e = RegExp(/([0-9a-fA-F]{3}|[0-9a-fA-F]{6})/).test(t) ? "#" : ""; this.container.style.backgroundColor = e + t } setObserver() { const t = w ? 0 : 1e3; this.observer = new IntersectionObserver((e => { e.forEach((async e => { e.isIntersecting ? (this.isInViewport = !0, this.isInitialRendered ? this.render() : (this.uiControls.showLoader(), await this.viewerModel.loadViewerModels(), this.initialRender = setTimeout((() => { this.isInViewport && (this.isInitialRendered = !0, this.render(), this.viewerActive(), this.initialRender = null, clearTimeout(this.initialRender)) }), t))) : (this.isInViewport = !1, this.isInitialRendered ? this.stopRender() : (this.initialRender = null, clearTimeout(this.initialRender))) })) })), this.observer.observe(this.container) } update() { this.isInViewport && this.controls && !this.initialRender && (this.viewerModel.update(), this.controls.update()) } render() { this.isInViewport && this.isInitialRendered && (this.renderer.setAnimationLoop((t => { this.update(), this.renderer.render(this.scene, this.camera) })), this.isRender = !0, !this.isInit && this.viewerModel.isInit && this.scene.children.length > 0 && (w ? this.initAfter() : this.uiControls.els.loaderDimmed.addEventListener("transitionend", this.initAfter.bind(this)), this.uiControls.hideLoader(), this.uiControls.hideDimmed())) } stopRender() { this.renderer.setAnimationLoop(null), this.isRender = !1 } } return { makeViewer: function (e, n, s) { t || (n.modelOptionList = n, t = new i(e, n, s)) }, getViewer: function () { if (t) return t } }
}(); return t
}));