- Update three in static file
This commit is contained in:
Kum1ta
2024-08-24 20:46:11 +02:00
parent 7c14bf2836
commit b5d13ccf6f
2000 changed files with 2010104 additions and 59 deletions

View File

@ -3,15 +3,15 @@
/* ::: :::::::: */
/* Screen.js :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: marvin <marvin@student.42.fr> +#+ +:+ +#+ */
/* By: edbernar <edbernar@student.42angouleme. +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2024/08/22 23:13:53 by edbernar #+# #+# */
/* Updated: 2024/08/24 11:39:09 by marvin ### ########.fr */
/* Updated: 2024/08/24 20:44:14 by edbernar ### ########.fr */
/* */
/* ************************************************************************** */
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import * as THREE from 'three'
import * as THREE from '/static/three/build/three.module.js'
import { GLTFLoader } from '/static/three/examples/jsm/loaders/GLTFLoader.js';
const loader = new GLTFLoader();
@ -26,7 +26,7 @@ class Screen
{
this.screen = this.#createScreen(scene);
loader.load( './modeles/tv.glb', (gltf) => {
loader.load( '/static/modeles/tv.glb', (gltf) => {
const tv = gltf.scene.children[0];
const boundingBox = new THREE.Box3().setFromObject(tv);
const center = boundingBox.getCenter(new THREE.Vector3());
@ -44,7 +44,7 @@ class Screen
console.error( error );
throw Error("Can't open file 'tv.glb'");
} );
this.#showVideo('/modeles/pong.mp4')
this.#showVideo('/static/modeles/pong.mp4')
}
#createScreen(scene)

View File

@ -3,19 +3,19 @@
/* ::: :::::::: */
/* home3D.js :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: marvin <marvin@student.42.fr> +#+ +:+ +#+ */
/* By: edbernar <edbernar@student.42angouleme. +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2024/08/22 17:19:17 by edbernar #+# #+# */
/* Updated: 2024/08/24 11:41:18 by marvin ### ########.fr */
/* Updated: 2024/08/24 20:44:31 by edbernar ### ########.fr */
/* */
/* ************************************************************************** */
import * as THREE from 'three'
import { Screen } from './Screen.js'
import * as THREE from '/static/three/build/three.module.js'
import { Screen } from '/static/home3D/Screen.js'
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';
import { BokehPass } from 'three/examples/jsm/postprocessing/BokehPass.js';
import { EffectComposer } from '/static/three/examples/jsm/postprocessing/EffectComposer.js';
import { RenderPass } from '/static/three/examples/jsm/postprocessing/RenderPass.js';
import { BokehPass } from '/static/three/examples/jsm/postprocessing/BokehPass.js';
const scene = new THREE.Scene();
const renderer = new THREE.WebGLRenderer({antialias: true});

View File

@ -3,16 +3,16 @@
/* ::: :::::::: */
/* main.js :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: marvin <marvin@student.42.fr> +#+ +:+ +#+ */
/* By: edbernar <edbernar@student.42angouleme. +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2024/07/30 13:50:35 by edbernar #+# #+# */
/* Updated: 2024/08/24 11:57:47 by marvin ### ########.fr */
/* Updated: 2024/08/24 20:40:04 by edbernar ### ########.fr */
/* */
/* ************************************************************************** */
import { liveChat } from "./liveChat/main.js";
import { login } from "./login/main.js";
import { home3D } from "./home3D/home3D.js"
import { liveChat } from "/static/liveChat/main.js";
import { login } from "/static/login/main.js";
import { home3D } from "/static/home3D/home3D.js"
window.addEventListener('scroll', () => {
const scrollPosition = window.scrollY;

View File

@ -0,0 +1,14 @@
# http://editorconfig.org
root = true
[*]
end_of_line = lf
insert_final_newline = true
[*.{js,ts,html}]
charset = utf-8
indent_style = tab
[*.{js,ts}]
trim_trailing_whitespace = true

View File

@ -0,0 +1,62 @@
{
"root": true,
"env": {
"browser": true,
"node": true,
"es2018": true
},
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module"
},
"extends": [
"mdcs",
"plugin:compat/recommended"
],
"plugins": [
"html",
"import"
],
"settings": {
"polyfills": [
"WebGL2RenderingContext"
]
},
"globals": {
"__THREE_DEVTOOLS__": "readonly",
"potpack": "readonly",
"fflate": "readonly",
"Stats": "readonly",
"XRWebGLBinding": "readonly",
"XRWebGLLayer": "readonly",
"GPUShaderStage": "readonly",
"GPUBufferUsage": "readonly",
"GPUTextureUsage": "readonly",
"GPUTexture": "readonly",
"GPUMapMode": "readonly",
"QUnit": "readonly",
"Ammo": "readonly",
"XRRigidTransform": "readonly",
"XRMediaBinding": "readonly",
"CodeMirror": "readonly",
"esprima": "readonly",
"jsonlint": "readonly",
"VideoFrame": "readonly"
},
"rules": {
"no-throw-literal": [
"error"
],
"quotes": [
"error",
"single"
],
"prefer-const": [
"error",
{
"destructuring": "any",
"ignoreReadBeforeAssign": false
}
]
}
}

View File

@ -0,0 +1,3 @@
# Set the default behavior, in case people don't have core.autocrlf set.
* text=auto

View File

@ -0,0 +1,76 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at hello@mrdoob.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq

View File

@ -0,0 +1,79 @@
# Contribution
## Introduction
It is assumed that you know a little about Node.js and Git. If not, [here's some help to get started with Git](https://help.github.com/en/github/using-git) and [heres some help to get started with Node.js.](https://nodejs.org/en/docs/guides/getting-started-guide/)
* Install [Node.js](https://nodejs.org/)
* Install [Git](https://git-scm.com/)
* [Fork](https://help.github.com/en/github/getting-started-with-github/fork-a-repo) three.js
* Open your OSs terminal
* Change into the directory youd like
* Clone your forked repo
git clone https://github.com/[yourgithubname]/three.js.git
* Go into the three.js directory.
cd ./three.js
* Install the dependencies
npm install
## Next Steps
As per the npm standard, start is the place to begin the package.
npm start
This script will start a local server similar to [threejs.org](https://threejs.org/), but instead will be hosted on your local machine. Browse to https://localhost:8080/ to check it out. It also automatically creates the `build/three.module.js` script anytime there is a change `src` directory.
Next scripts run all the appropriate testing.
- `npm run test` - Lint testing and unit testing (individually being `npm run lint` and `npm run test-unit`)
- `npm run test-e2e` - E2E testing. This one can take quite a long time and installs ~200 MB Chromium browser - it is primarily intended to be run only by GitHub Actions
The linting is there to keep a consistent code style across all of the code and the testing is there to help catch bugs and check that the code behaves as expected. It is important that neither of these steps comes up with any errors due to your changes.
Most linting errors can be fixed automatically by running
npm run lint-fix
If youd like to make a build of the source files (e.g. `build/three.module.js`) run:
npm run build
## Making changes
When youve decided to make changes, start with the following:
* Update your local repo
git pull https://github.com/mrdoob/three.js.git
git push
* Make a new branch from the dev branch
git checkout dev
git branch [mychangesbranch]
git checkout [mychangesbranch]
* Add your changes to your commit.
* Push the changes to your forked repo.
* Open a Pull Request (PR)
## Important notes:
* Don't include any build files in your commit.
* Not all new features will need a new example. Simpler features could be incorporated into an existing example. Bigger features may be asked to add an example demonstrating the feature.
* Making changes may require changes to the documentation. To update the docs in other languages, simply copy the English to begin with.
* It's good to also add an example and screenshot for it, for showing how it's used and for end-to-end testing.
* If you modify existing code, run relevant examples to check they didn't break and there wasn't performance regress.
* If you add some assets for the examples (models, textures, sounds, etc), make sure they have a proper license allowing for their use here, less restrictive the better. It is unlikely for large assets to be accepted.
* If some issue is relevant to the patch/feature, please mention it with a hash (e.g. #2774) in a commit message to get cross-reference in GitHub.
* If the end-to-end test failed and you are sure that all is correct, follow the instructions it outputs.
* Once done with a patch/feature do not add more commits to a feature branch.
* Create separate branches per patch or feature.
* If you make a PR but it is not actually ready to be pulled into the dev branch then please [convert it to a draft PR](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/changing-the-stage-of-a-pull-request#converting-a-pull-request-to-a-draft).
This project is currently contributed mostly via everyone's spare time. Please keep that in mind as it may take some time for the appropriate feedback to get to you. If you are unsure about adding a new feature, it might be better to ask first to see whether other people think it's a good idea.

View File

@ -0,0 +1,3 @@
# These are supported funding model platforms
github: [mrdoob, HumanInteractive, donmccurdy, gkjohnson, WestLangley]

View File

@ -0,0 +1,86 @@
name: Bug Report
description: File a reproducible bug or regression.
body:
- type: textarea
id: description
attributes:
label: Description
description: A clear and concise description of what the bug is.
validations:
required: true
- type: textarea
id: repro
attributes:
label: Reproduction steps
description: How do you trigger this bug? Please walk us through it step by step.
value: |
1.
2.
3.
validations:
required: true
- type: textarea
id: code
attributes:
label: Code
value: |
```js
// code goes here
```
validations:
required: true
- type: textarea
id: example
attributes:
label: Live example
value: |
* [jsfiddle-latest-release](https://jsfiddle.net/g3atw6k5/)
* [jsfiddle-dev](https://jsfiddle.net/hjqw94c5/)
validations:
required: true
- type: textarea
id: screenshots
attributes:
label: Screenshots
description: If applicable, add screenshots to help explain your problem (drag and drop the image).
validations:
required: false
- type: input
id: version
attributes:
label: Version
description: What version of the library are you using?
placeholder: r
validations:
required: true
- type: dropdown
id: device
attributes:
label: Device
multiple: true
options:
- Desktop
- Mobile
- Headset
- type: dropdown
id: browser
attributes:
label: Browser
multiple: true
options:
- Chrome
- Firefox
- Safari
- Edge
- type: dropdown
id: os
attributes:
label: OS
multiple: true
options:
- Windows
- MacOS
- Linux
- ChromeOS
- Android
- iOS

View File

@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Help and Support
url: https://discourse.threejs.org/
about: Please use the forum if you have questions or need help.

View File

@ -0,0 +1,34 @@
name: Feature request
description: Suggest an idea for the project.
body:
- type: textarea
id: description
attributes:
label: Description
description: Is your feature request related to a problem? Please describe.
placeholder: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
validations:
required: true
- type: textarea
id: solution
attributes:
label: Solution
description: Describe the solution you'd like.
placeholder: A clear and concise description of what you want to happen.
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternatives
description: Describe alternatives you've considered.
placeholder: A clear and concise description of any alternative solutions or features you've considered.
validations:
required: true
- type: textarea
id: additional
attributes:
label: Additional context
description: Add any other context about the feature request here.
validations:
required: false

View File

@ -0,0 +1,7 @@
paths-ignore:
- "docs/prettify/**/*.*"
- "editor/js/libs/**/*.*"
- "examples/jsm/libs/**/*.*"
- "examples/jsm/loaders/ifc/**/*.*"
- "build/*.*"
- "manual/3rdparty/**/*.*"

View File

@ -0,0 +1,9 @@
Related issue: #XXXX
**Description**
A clear and concise description of what the problem was and how this pull request solves it.
<!-- Remove the line below if is not relevant -->
*This contribution is funded by [Example](https://example.com)*

View File

@ -0,0 +1,23 @@
{
"extends": [
"config:base",
":disableDependencyDashboard",
"helpers:pinGitHubActionDigests"
],
"timezone": "Asia/Tokyo",
"schedule": ["after 1am and before 7am every monday"],
"packageRules": [
{
"matchDepTypes": ["devDependencies"],
"matchUpdateTypes": ["patch", "minor", "pin", "digest"],
"groupName": "devDependencies (non-major)",
"automerge": true
},
{
"description": "ESLint v9 requires flat configs, not yet supported by our plugins. See https://github.com/mrdoob/three.js/pull/28354#issuecomment-2106528332",
"matchPackageNames": ["eslint"],
"matchUpdateTypes": ["major"],
"enabled": false
}
]
}

View File

@ -0,0 +1,114 @@
name: CI
on:
pull_request:
paths-ignore:
- 'build/**'
- 'docs/**'
- 'files/**'
permissions:
contents: read
jobs:
lint:
name: Lint testing
runs-on: ubuntu-latest
steps:
- name: Git checkout
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
- name: Install Node
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4
with:
node-version: 18
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: === Lint testing ===
run: npm run lint
unit:
name: Unit testing
runs-on: ubuntu-latest
steps:
- name: Git checkout
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
- name: Install Node
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4
with:
node-version: 18
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: === Unit testing ===
run: npm run test-unit
circular:
name: Circular dependencies testing
runs-on: ubuntu-latest
steps:
- name: Git checkout
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
- name: Install Node
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4
with:
node-version: 18
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: === Circular dependencies testing ===
run: npm run test-circular-deps
e2e:
name: E2E testing
runs-on: ${{ matrix.os }}
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
os: [ windows-latest ]
CI: [ 0, 1, 2, 3 ]
env:
CI: ${{ matrix.CI }}
steps:
- name: Git checkout
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
- name: Install Node
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4
with:
node-version: 18
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build-module
- name: === E2E testing ===
run: npm run test-e2e
- name: Upload output screenshots
uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4
if: always()
with:
name: Output screenshots-${{ matrix.os }}-${{ matrix.CI }}
path: test/e2e/output-screenshots
if-no-files-found: ignore
e2e-cov:
name: Examples ready for release
runs-on: ubuntu-latest
steps:
- name: Git checkout
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
- name: Install Node
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4
with:
node-version: 18
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: === Examples ready for release ===
run: npm run test-e2e-cov

View File

@ -0,0 +1,45 @@
name: "CodeQL"
on:
push:
branches: [ "dev" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "dev" ]
schedule:
- cron: '29 23 * * 0'
workflow_dispatch:
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'javascript' ]
steps:
- name: Checkout repository
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@2d790406f505036ef40ecba973cc774a50395aac # v3
with:
languages: ${{ matrix.language }}
config-file: ./.github/codeql-config.yml
queries: security-and-quality
- name: Autobuild
uses: github/codeql-action/autobuild@2d790406f505036ef40ecba973cc774a50395aac # v3
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@2d790406f505036ef40ecba973cc774a50395aac # v3
with:
category: "/language:${{matrix.language}}"

View File

@ -0,0 +1,52 @@
name: Read size
on:
pull_request:
paths:
- 'src/**'
- 'package.json'
- 'utils/build/**'
# This workflow runs in a read-only environment. We can safely checkout
# the PR code here.
# Reference:
# https://securitylab.github.com/research/github-actions-preventing-pwn-requests/
permissions:
contents: read
jobs:
read-size:
name: Tree-shaking
runs-on: ubuntu-latest
steps:
- name: Git checkout
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
- name: Install Node
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4
with:
node-version: 18
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: === Test tree-shaking ===
run: npm run test-treeshake
- name: Read bundle sizes
id: read-size
run: |
FILESIZE=$(stat --format=%s build/three.module.min.js)
gzip -k build/three.module.min.js
FILESIZE_GZIP=$(stat --format=%s build/three.module.min.js.gz)
TREESHAKEN=$(stat --format=%s test/treeshake/index.bundle.min.js)
gzip -k test/treeshake/index.bundle.min.js
TREESHAKEN_GZIP=$(stat --format=%s test/treeshake/index.bundle.min.js.gz)
PR=${{ github.event.pull_request.number }}
# write the output in a json file to upload it as artifact
node -pe "JSON.stringify({ filesize: $FILESIZE, gzip: $FILESIZE_GZIP, treeshaken: $TREESHAKEN, treeshakenGzip: $TREESHAKEN_GZIP, pr: $PR })" > sizes.json
- name: Upload artifact
uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4
with:
name: sizes
path: sizes.json

View File

@ -0,0 +1,156 @@
name: Report size
on:
workflow_run:
workflows: ["Read size"]
types:
- completed
# This workflow needs to be run with "pull-requests: write" permissions to
# be able to comment on the pull request. We can't checkout the PR code
# in this workflow.
# Reference:
# https://securitylab.github.com/research/github-actions-preventing-pwn-requests/
permissions:
pull-requests: write
jobs:
report-size:
name: Comment on PR
runs-on: ubuntu-latest
if: github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion == 'success'
steps:
- name: Log GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
# Using actions/download-artifact doesn't work here
# https://github.com/actions/download-artifact/issues/60
- name: Download artifact
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
id: download-artifact
with:
result-encoding: string
script: |
const fs = require('fs/promises');
const artifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.payload.workflow_run.id,
});
const matchArtifact = artifacts.data.artifacts.find((artifact) => artifact.name === 'sizes');
const download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: matchArtifact.id,
archive_format: 'zip',
});
await fs.writeFile('sizes.zip', Buffer.from(download.data));
await exec.exec('unzip sizes.zip');
const json = await fs.readFile('sizes.json', 'utf8');
return json;
# This runs on the base branch of the PR, meaning "dev"
- name: Git checkout
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
- name: Install Node
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4
with:
node-version: 18
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: === Test tree-shaking ===
run: npm run test-treeshake
- name: Read sizes
id: read-size
run: |
FILESIZE_BASE=$(stat --format=%s build/three.module.min.js)
gzip -k build/three.module.min.js
FILESIZE_BASE_GZIP=$(stat --format=%s build/three.module.min.js.gz)
TREESHAKEN_BASE=$(stat --format=%s test/treeshake/index.bundle.min.js)
gzip -k test/treeshake/index.bundle.min.js
TREESHAKEN_BASE_GZIP=$(stat --format=%s test/treeshake/index.bundle.min.js.gz)
# log to console
echo "FILESIZE_BASE=$FILESIZE_BASE"
echo "FILESIZE_BASE_GZIP=$FILESIZE_BASE_GZIP"
echo "TREESHAKEN_BASE=$TREESHAKEN_BASE"
echo "TREESHAKEN_BASE_GZIP=$TREESHAKEN_BASE_GZIP"
echo "FILESIZE_BASE=$FILESIZE_BASE" >> $GITHUB_OUTPUT
echo "FILESIZE_BASE_GZIP=$FILESIZE_BASE_GZIP" >> $GITHUB_OUTPUT
echo "TREESHAKEN_BASE=$TREESHAKEN_BASE" >> $GITHUB_OUTPUT
echo "TREESHAKEN_BASE_GZIP=$TREESHAKEN_BASE_GZIP" >> $GITHUB_OUTPUT
- name: Format sizes
id: format
# It's important these are passed as env variables.
# https://securitylab.github.com/research/github-actions-untrusted-input/
env:
FILESIZE: ${{ fromJSON(steps.download-artifact.outputs.result).filesize }}
FILESIZE_GZIP: ${{ fromJSON(steps.download-artifact.outputs.result).gzip }}
FILESIZE_BASE: ${{ steps.read-size.outputs.FILESIZE_BASE }}
FILESIZE_BASE_GZIP: ${{ steps.read-size.outputs.FILESIZE_BASE_GZIP }}
TREESHAKEN: ${{ fromJSON(steps.download-artifact.outputs.result).treeshaken }}
TREESHAKEN_GZIP: ${{ fromJSON(steps.download-artifact.outputs.result).treeshakenGzip }}
TREESHAKEN_BASE: ${{ steps.read-size.outputs.TREESHAKEN_BASE }}
TREESHAKEN_BASE_GZIP: ${{ steps.read-size.outputs.TREESHAKEN_BASE_GZIP }}
run: |
FILESIZE_FORM=$(node ./test/treeshake/utils/format-size.js "$FILESIZE")
FILESIZE_GZIP_FORM=$(node ./test/treeshake/utils/format-size.js "$FILESIZE_GZIP")
FILESIZE_BASE_FORM=$(node ./test/treeshake/utils/format-size.js "$FILESIZE_BASE")
FILESIZE_BASE_GZIP_FORM=$(node ./test/treeshake/utils/format-size.js "$FILESIZE_BASE_GZIP")
FILESIZE_DIFF=$(node ./test/treeshake/utils/format-diff.js "$FILESIZE" "$FILESIZE_BASE")
TREESHAKEN_FORM=$(node ./test/treeshake/utils/format-size.js "$TREESHAKEN")
TREESHAKEN_GZIP_FORM=$(node ./test/treeshake/utils/format-size.js "$TREESHAKEN_GZIP")
TREESHAKEN_BASE_FORM=$(node ./test/treeshake/utils/format-size.js "$TREESHAKEN_BASE")
TREESHAKEN_BASE_GZIP_FORM=$(node ./test/treeshake/utils/format-size.js "$TREESHAKEN_BASE_GZIP")
TREESHAKEN_DIFF=$(node ./test/treeshake/utils/format-diff.js "$TREESHAKEN" "$TREESHAKEN_BASE")
echo "FILESIZE=$FILESIZE_FORM" >> $GITHUB_OUTPUT
echo "FILESIZE_GZIP=$FILESIZE_GZIP_FORM" >> $GITHUB_OUTPUT
echo "FILESIZE_BASE=$FILESIZE_BASE_FORM" >> $GITHUB_OUTPUT
echo "FILESIZE_BASE_GZIP=$FILESIZE_BASE_GZIP_FORM" >> $GITHUB_OUTPUT
echo "FILESIZE_DIFF=$FILESIZE_DIFF" >> $GITHUB_OUTPUT
echo "TREESHAKEN=$TREESHAKEN_FORM" >> $GITHUB_OUTPUT
echo "TREESHAKEN_GZIP=$TREESHAKEN_GZIP_FORM" >> $GITHUB_OUTPUT
echo "TREESHAKEN_BASE=$TREESHAKEN_BASE_FORM" >> $GITHUB_OUTPUT
echo "TREESHAKEN_BASE_GZIP=$TREESHAKEN_BASE_GZIP_FORM" >> $GITHUB_OUTPUT
echo "TREESHAKEN_DIFF=$TREESHAKEN_DIFF" >> $GITHUB_OUTPUT
- name: Find existing comment
uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3
id: find-comment
with:
issue-number: ${{ fromJSON(steps.download-artifact.outputs.result).pr }}
comment-author: 'github-actions[bot]'
body-includes: Bundle size
- name: Comment on PR
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4
with:
issue-number: ${{ fromJSON(steps.download-artifact.outputs.result).pr }}
comment-id: ${{ steps.find-comment.outputs.comment-id }}
edit-mode: replace
body: |
### 📦 Bundle size
_Full ESM build, minified and gzipped._
| Filesize `${{ github.ref_name }}` | Filesize PR | Diff |
|----------|---------|------|
| ${{ steps.format.outputs.FILESIZE_BASE }} (${{ steps.format.outputs.FILESIZE_BASE_GZIP }}) | ${{ steps.format.outputs.FILESIZE }} (${{ steps.format.outputs.FILESIZE_GZIP }}) | ${{ steps.format.outputs.FILESIZE_DIFF }} |
### 🌳 Bundle size after tree-shaking
_Minimal build including a renderer, camera, empty scene, and dependencies._
| Filesize `${{ github.ref_name }}` | Filesize PR | Diff |
|----------|---------|------|
| ${{ steps.format.outputs.TREESHAKEN_BASE }} (${{ steps.format.outputs.TREESHAKEN_BASE_GZIP }}) | ${{ steps.format.outputs.TREESHAKEN }} (${{ steps.format.outputs.TREESHAKEN_GZIP }}) | ${{ steps.format.outputs.TREESHAKEN_DIFF }} |

View File

@ -0,0 +1,18 @@
.DS_Store
*.swp
.project
.idea/
.vscode/
npm-debug.log
.jshintrc
.vs/
test/unit/build
test/treeshake/index.bundle.js
test/treeshake/index.bundle.min.js
test/treeshake/index-src.bundle.min.js
test/treeshake/stats.html
test/e2e/chromium
test/e2e/output-screenshots
**/node_modules

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,239 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<title>three.js css2d - label</title>
<link type="text/css" rel="stylesheet" href="main.css">
<style>
.label {
color: #FFF;
font-family: sans-serif;
padding: 2px;
background: rgba( 0, 0, 0, .6 );
}
</style>
</head>
<body>
<div id="info"><a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> css2d - label</div>
<script type="importmap">
{
"imports": {
"three": "../build/three.module.js",
"three/addons/": "./jsm/"
}
}
</script>
<script type="module">
import * as THREE from '/static/three/build/three.module.js';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { CSS2DRenderer, CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js';
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
let gui;
let camera, scene, renderer, labelRenderer;
const layers = {
'Toggle Name': function () {
camera.layers.toggle( 0 );
},
'Toggle Mass': function () {
camera.layers.toggle( 1 );
},
'Enable All': function () {
camera.layers.enableAll();
},
'Disable All': function () {
camera.layers.disableAll();
}
};
const clock = new THREE.Clock();
const textureLoader = new THREE.TextureLoader();
let moon;
init();
animate();
function init() {
const EARTH_RADIUS = 1;
const MOON_RADIUS = 0.27;
camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 200 );
camera.position.set( 10, 5, 20 );
camera.layers.enableAll();
scene = new THREE.Scene();
const dirLight = new THREE.DirectionalLight( 0xffffff, 3 );
dirLight.position.set( 0, 0, 1 );
dirLight.layers.enableAll();
scene.add( dirLight );
const axesHelper = new THREE.AxesHelper( 5 );
axesHelper.layers.enableAll();
scene.add( axesHelper );
//
const earthGeometry = new THREE.SphereGeometry( EARTH_RADIUS, 16, 16 );
const earthMaterial = new THREE.MeshPhongMaterial( {
specular: 0x333333,
shininess: 5,
map: textureLoader.load( 'textures/planets/earth_atmos_2048.jpg' ),
specularMap: textureLoader.load( 'textures/planets/earth_specular_2048.jpg' ),
normalMap: textureLoader.load( 'textures/planets/earth_normal_2048.jpg' ),
normalScale: new THREE.Vector2( 0.85, 0.85 )
} );
earthMaterial.map.colorSpace = THREE.SRGBColorSpace;
const earth = new THREE.Mesh( earthGeometry, earthMaterial );
scene.add( earth );
const moonGeometry = new THREE.SphereGeometry( MOON_RADIUS, 16, 16 );
const moonMaterial = new THREE.MeshPhongMaterial( {
shininess: 5,
map: textureLoader.load( 'textures/planets/moon_1024.jpg' )
} );
moonMaterial.map.colorSpace = THREE.SRGBColorSpace;
moon = new THREE.Mesh( moonGeometry, moonMaterial );
scene.add( moon );
//
earth.layers.enableAll();
moon.layers.enableAll();
const earthDiv = document.createElement( 'div' );
earthDiv.className = 'label';
earthDiv.textContent = 'Earth';
earthDiv.style.backgroundColor = 'transparent';
const earthLabel = new CSS2DObject( earthDiv );
earthLabel.position.set( 1.5 * EARTH_RADIUS, 0, 0 );
earthLabel.center.set( 0, 1 );
earth.add( earthLabel );
earthLabel.layers.set( 0 );
const earthMassDiv = document.createElement( 'div' );
earthMassDiv.className = 'label';
earthMassDiv.textContent = '5.97237e24 kg';
earthMassDiv.style.backgroundColor = 'transparent';
const earthMassLabel = new CSS2DObject( earthMassDiv );
earthMassLabel.position.set( 1.5 * EARTH_RADIUS, 0, 0 );
earthMassLabel.center.set( 0, 0 );
earth.add( earthMassLabel );
earthMassLabel.layers.set( 1 );
const moonDiv = document.createElement( 'div' );
moonDiv.className = 'label';
moonDiv.textContent = 'Moon';
moonDiv.style.backgroundColor = 'transparent';
const moonLabel = new CSS2DObject( moonDiv );
moonLabel.position.set( 1.5 * MOON_RADIUS, 0, 0 );
moonLabel.center.set( 0, 1 );
moon.add( moonLabel );
moonLabel.layers.set( 0 );
const moonMassDiv = document.createElement( 'div' );
moonMassDiv.className = 'label';
moonMassDiv.textContent = '7.342e22 kg';
moonMassDiv.style.backgroundColor = 'transparent';
const moonMassLabel = new CSS2DObject( moonMassDiv );
moonMassLabel.position.set( 1.5 * MOON_RADIUS, 0, 0 );
moonMassLabel.center.set( 0, 0 );
moon.add( moonMassLabel );
moonMassLabel.layers.set( 1 );
//
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
labelRenderer = new CSS2DRenderer();
labelRenderer.setSize( window.innerWidth, window.innerHeight );
labelRenderer.domElement.style.position = 'absolute';
labelRenderer.domElement.style.top = '0px';
document.body.appendChild( labelRenderer.domElement );
const controls = new OrbitControls( camera, labelRenderer.domElement );
controls.minDistance = 5;
controls.maxDistance = 100;
//
window.addEventListener( 'resize', onWindowResize );
initGui();
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
labelRenderer.setSize( window.innerWidth, window.innerHeight );
}
function animate() {
requestAnimationFrame( animate );
const elapsed = clock.getElapsedTime();
moon.position.set( Math.sin( elapsed ) * 5, 0, Math.cos( elapsed ) * 5 );
renderer.render( scene, camera );
labelRenderer.render( scene, camera );
}
//
function initGui() {
gui = new GUI();
gui.title( 'Camera Layers' );
gui.add( layers, 'Toggle Name' );
gui.add( layers, 'Toggle Mass' );
gui.add( layers, 'Enable All' );
gui.add( layers, 'Disable All' );
gui.open();
}
</script>
</body>
</html>

View File

@ -0,0 +1,444 @@
<!DOCTYPE html>
<html>
<head>
<title>three.js css3d - molecules</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link type="text/css" rel="stylesheet" href="main.css">
<style>
body {
background-color: #050505;
background: radial-gradient(ellipse at center, rgba(43,45,48,1) 0%,rgba(0,0,0,1) 100%);
}
.bond {
width: 5px;
height: 10px;
background: #eee;
display: block;
}
</style>
</head>
<body>
<div id="container"></div>
<div id="info"><a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> css3d - molecules</div>
<script type="importmap">
{
"imports": {
"three": "../build/three.module.js",
"three/addons/": "./jsm/"
}
}
</script>
<script type="module">
import * as THREE from '/static/three/build/three.module.js';
import { TrackballControls } from 'three/addons/controls/TrackballControls.js';
import { PDBLoader } from 'three/addons/loaders/PDBLoader.js';
import { CSS3DRenderer, CSS3DObject, CSS3DSprite } from 'three/addons/renderers/CSS3DRenderer.js';
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
let camera, scene, renderer;
let controls;
let root;
const objects = [];
const tmpVec1 = new THREE.Vector3();
const tmpVec2 = new THREE.Vector3();
const tmpVec3 = new THREE.Vector3();
const tmpVec4 = new THREE.Vector3();
const offset = new THREE.Vector3();
const VIZ_TYPE = {
'Atoms': 0,
'Bonds': 1,
'Atoms + Bonds': 2
};
const MOLECULES = {
'Ethanol': 'ethanol.pdb',
'Aspirin': 'aspirin.pdb',
'Caffeine': 'caffeine.pdb',
'Nicotine': 'nicotine.pdb',
'LSD': 'lsd.pdb',
'Cocaine': 'cocaine.pdb',
'Cholesterol': 'cholesterol.pdb',
'Lycopene': 'lycopene.pdb',
'Glucose': 'glucose.pdb',
'Aluminium oxide': 'Al2O3.pdb',
'Cubane': 'cubane.pdb',
'Copper': 'cu.pdb',
'Fluorite': 'caf2.pdb',
'Salt': 'nacl.pdb',
'YBCO superconductor': 'ybco.pdb',
'Buckyball': 'buckyball.pdb',
// 'Diamond': 'diamond.pdb',
'Graphite': 'graphite.pdb'
};
const params = {
vizType: 2,
molecule: 'caffeine.pdb'
};
const loader = new PDBLoader();
const colorSpriteMap = {};
const baseSprite = document.createElement( 'img' );
init();
animate();
function init() {
camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 5000 );
camera.position.z = 1000;
scene = new THREE.Scene();
root = new THREE.Object3D();
scene.add( root );
//
renderer = new CSS3DRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.getElementById( 'container' ).appendChild( renderer.domElement );
//
controls = new TrackballControls( camera, renderer.domElement );
controls.rotateSpeed = 0.5;
//
baseSprite.onload = function () {
loadMolecule( params.molecule );
};
baseSprite.src = 'textures/sprites/ball.png';
//
window.addEventListener( 'resize', onWindowResize );
//
const gui = new GUI();
gui.add( params, 'vizType', VIZ_TYPE ).onChange( changeVizType );
gui.add( params, 'molecule', MOLECULES ).onChange( loadMolecule );
gui.open();
}
function changeVizType( value ) {
if ( value === 0 ) showAtoms();
else if ( value === 1 ) showBonds();
else showAtomsBonds();
}
//
function showAtoms() {
for ( let i = 0; i < objects.length; i ++ ) {
const object = objects[ i ];
if ( object instanceof CSS3DSprite ) {
object.element.style.display = '';
object.visible = true;
} else {
object.element.style.display = 'none';
object.visible = false;
}
}
}
function showBonds() {
for ( let i = 0; i < objects.length; i ++ ) {
const object = objects[ i ];
if ( object instanceof CSS3DSprite ) {
object.element.style.display = 'none';
object.visible = false;
} else {
object.element.style.display = '';
object.element.style.height = object.userData.bondLengthFull;
object.visible = true;
}
}
}
function showAtomsBonds() {
for ( let i = 0; i < objects.length; i ++ ) {
const object = objects[ i ];
object.element.style.display = '';
object.visible = true;
if ( ! ( object instanceof CSS3DSprite ) ) {
object.element.style.height = object.userData.bondLengthShort;
}
}
}
//
function colorify( ctx, width, height, color ) {
const r = color.r, g = color.g, b = color.b;
const imageData = ctx.getImageData( 0, 0, width, height );
const data = imageData.data;
for ( let i = 0, l = data.length; i < l; i += 4 ) {
data[ i + 0 ] *= r;
data[ i + 1 ] *= g;
data[ i + 2 ] *= b;
}
ctx.putImageData( imageData, 0, 0 );
}
function imageToCanvas( image ) {
const width = image.width;
const height = image.height;
const canvas = document.createElement( 'canvas' );
canvas.width = width;
canvas.height = height;
const context = canvas.getContext( '2d' );
context.drawImage( image, 0, 0, width, height );
return canvas;
}
//
function loadMolecule( model ) {
const url = 'models/pdb/' + model;
for ( let i = 0; i < objects.length; i ++ ) {
const object = objects[ i ];
object.parent.remove( object );
}
objects.length = 0;
loader.load( url, function ( pdb ) {
const geometryAtoms = pdb.geometryAtoms;
const geometryBonds = pdb.geometryBonds;
const json = pdb.json;
geometryAtoms.computeBoundingBox();
geometryAtoms.boundingBox.getCenter( offset ).negate();
geometryAtoms.translate( offset.x, offset.y, offset.z );
geometryBonds.translate( offset.x, offset.y, offset.z );
const positionAtoms = geometryAtoms.getAttribute( 'position' );
const colorAtoms = geometryAtoms.getAttribute( 'color' );
const position = new THREE.Vector3();
const color = new THREE.Color();
for ( let i = 0; i < positionAtoms.count; i ++ ) {
position.fromBufferAttribute( positionAtoms, i );
color.fromBufferAttribute( colorAtoms, i );
const atomJSON = json.atoms[ i ];
const element = atomJSON[ 4 ];
if ( ! colorSpriteMap[ element ] ) {
const canvas = imageToCanvas( baseSprite );
const context = canvas.getContext( '2d' );
colorify( context, canvas.width, canvas.height, color );
const dataUrl = canvas.toDataURL();
colorSpriteMap[ element ] = dataUrl;
}
const colorSprite = colorSpriteMap[ element ];
const atom = document.createElement( 'img' );
atom.src = colorSprite;
const object = new CSS3DSprite( atom );
object.position.copy( position );
object.position.multiplyScalar( 75 );
object.matrixAutoUpdate = false;
object.updateMatrix();
root.add( object );
objects.push( object );
}
const positionBonds = geometryBonds.getAttribute( 'position' );
const start = new THREE.Vector3();
const end = new THREE.Vector3();
for ( let i = 0; i < positionBonds.count; i += 2 ) {
start.fromBufferAttribute( positionBonds, i );
end.fromBufferAttribute( positionBonds, i + 1 );
start.multiplyScalar( 75 );
end.multiplyScalar( 75 );
tmpVec1.subVectors( end, start );
const bondLength = tmpVec1.length() - 50;
//
let bond = document.createElement( 'div' );
bond.className = 'bond';
bond.style.height = bondLength + 'px';
let object = new CSS3DObject( bond );
object.position.copy( start );
object.position.lerp( end, 0.5 );
object.userData.bondLengthShort = bondLength + 'px';
object.userData.bondLengthFull = ( bondLength + 55 ) + 'px';
//
const axis = tmpVec2.set( 0, 1, 0 ).cross( tmpVec1 );
const radians = Math.acos( tmpVec3.set( 0, 1, 0 ).dot( tmpVec4.copy( tmpVec1 ).normalize() ) );
const objMatrix = new THREE.Matrix4().makeRotationAxis( axis.normalize(), radians );
object.matrix.copy( objMatrix );
object.quaternion.setFromRotationMatrix( object.matrix );
object.matrixAutoUpdate = false;
object.updateMatrix();
root.add( object );
objects.push( object );
//
const joint = new THREE.Object3D();
joint.position.copy( start );
joint.position.lerp( end, 0.5 );
joint.matrix.copy( objMatrix );
joint.quaternion.setFromRotationMatrix( joint.matrix );
joint.matrixAutoUpdate = false;
joint.updateMatrix();
bond = document.createElement( 'div' );
bond.className = 'bond';
bond.style.height = bondLength + 'px';
object = new CSS3DObject( bond );
object.rotation.y = Math.PI / 2;
object.matrixAutoUpdate = false;
object.updateMatrix();
object.userData.bondLengthShort = bondLength + 'px';
object.userData.bondLengthFull = ( bondLength + 55 ) + 'px';
object.userData.joint = joint;
joint.add( object );
root.add( joint );
objects.push( object );
}
//console.log( "CSS3DObjects:", objects.length );
changeVizType( params.vizType );
} );
}
//
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
function animate() {
requestAnimationFrame( animate );
controls.update();
const time = Date.now() * 0.0004;
root.rotation.x = time;
root.rotation.y = time * 0.7;
render();
}
function render() {
renderer.render( scene, camera );
}
</script>
</body>
</html>

View File

@ -0,0 +1,223 @@
<!DOCTYPE html>
<html>
<head>
<title>three.js css3d - orthographic</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link type="text/css" rel="stylesheet" href="main.css">
<style>
body {
background-color: #f0f0f0;
}
a {
color: #f00;
}
#info {
color: #000000;
}
</style>
</head>
<body>
<div id="info"><a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> css3d - orthographic</div>
<script type="importmap">
{
"imports": {
"three": "../build/three.module.js",
"three/addons/": "./jsm/"
}
}
</script>
<script type="module">
import * as THREE from '/static/three/build/three.module.js';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { CSS3DRenderer, CSS3DObject } from 'three/addons/renderers/CSS3DRenderer.js';
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
let camera, scene, renderer;
let scene2, renderer2;
const frustumSize = 500;
init();
animate();
function init() {
const aspect = window.innerWidth / window.innerHeight;
camera = new THREE.OrthographicCamera( frustumSize * aspect / - 2, frustumSize * aspect / 2, frustumSize / 2, frustumSize / - 2, 1, 1000 );
camera.position.set( - 200, 200, 200 );
scene = new THREE.Scene();
scene.background = new THREE.Color( 0xf0f0f0 );
scene2 = new THREE.Scene();
const material = new THREE.MeshBasicMaterial( { color: 0x000000, wireframe: true, wireframeLinewidth: 1, side: THREE.DoubleSide } );
// left
createPlane(
100, 100,
'chocolate',
new THREE.Vector3( - 50, 0, 0 ),
new THREE.Euler( 0, - 90 * THREE.MathUtils.DEG2RAD, 0 )
);
// right
createPlane(
100, 100,
'saddlebrown',
new THREE.Vector3( 0, 0, 50 ),
new THREE.Euler( 0, 0, 0 )
);
// top
createPlane(
100, 100,
'yellowgreen',
new THREE.Vector3( 0, 50, 0 ),
new THREE.Euler( - 90 * THREE.MathUtils.DEG2RAD, 0, 0 )
);
// bottom
createPlane(
300, 300,
'seagreen',
new THREE.Vector3( 0, - 50, 0 ),
new THREE.Euler( - 90 * THREE.MathUtils.DEG2RAD, 0, 0 )
);
//
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
renderer2 = new CSS3DRenderer();
renderer2.setSize( window.innerWidth, window.innerHeight );
renderer2.domElement.style.position = 'absolute';
renderer2.domElement.style.top = 0;
document.body.appendChild( renderer2.domElement );
const controls = new OrbitControls( camera, renderer2.domElement );
controls.minZoom = 0.5;
controls.maxZoom = 2;
function createPlane( width, height, cssColor, pos, rot ) {
const element = document.createElement( 'div' );
element.style.width = width + 'px';
element.style.height = height + 'px';
element.style.opacity = 0.75;
element.style.background = cssColor;
const object = new CSS3DObject( element );
object.position.copy( pos );
object.rotation.copy( rot );
scene2.add( object );
const geometry = new THREE.PlaneGeometry( width, height );
const mesh = new THREE.Mesh( geometry, material );
mesh.position.copy( object.position );
mesh.rotation.copy( object.rotation );
scene.add( mesh );
}
window.addEventListener( 'resize', onWindowResize );
createPanel();
}
function onWindowResize() {
const aspect = window.innerWidth / window.innerHeight;
camera.left = - frustumSize * aspect / 2;
camera.right = frustumSize * aspect / 2;
camera.top = frustumSize / 2;
camera.bottom = - frustumSize / 2;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
renderer2.setSize( window.innerWidth, window.innerHeight );
}
function animate() {
requestAnimationFrame( animate );
renderer.render( scene, camera );
renderer2.render( scene2, camera );
}
function createPanel() {
const panel = new GUI();
const folder1 = panel.addFolder( 'camera setViewOffset' ).close();
const settings = {
'setViewOffset'() {
folder1.children[ 1 ].enable().setValue( window.innerWidth );
folder1.children[ 2 ].enable().setValue( window.innerHeight );
folder1.children[ 3 ].enable().setValue( 0 );
folder1.children[ 4 ].enable().setValue( 0 );
folder1.children[ 5 ].enable().setValue( window.innerWidth );
folder1.children[ 6 ].enable().setValue( window.innerHeight );
},
'fullWidth': 0,
'fullHeight': 0,
'offsetX': 0,
'offsetY': 0,
'width': 0,
'height': 0,
'clearViewOffset'() {
folder1.children[ 1 ].setValue( 0 ).disable();
folder1.children[ 2 ].setValue( 0 ).disable();
folder1.children[ 3 ].setValue( 0 ).disable();
folder1.children[ 4 ].setValue( 0 ).disable();
folder1.children[ 5 ].setValue( 0 ).disable();
folder1.children[ 6 ].setValue( 0 ).disable();
camera.clearViewOffset();
}
};
folder1.add( settings, 'setViewOffset' );
folder1.add( settings, 'fullWidth', window.screen.width / 4, window.screen.width * 2, 1 ).onChange( ( val ) => updateCameraViewOffset( { fullWidth: val } ) ).disable();
folder1.add( settings, 'fullHeight', window.screen.height / 4, window.screen.height * 2, 1 ).onChange( ( val ) => updateCameraViewOffset( { fullHeight: val } ) ).disable();
folder1.add( settings, 'offsetX', 0, 256, 1 ).onChange( ( val ) => updateCameraViewOffset( { x: val } ) ).disable();
folder1.add( settings, 'offsetY', 0, 256, 1 ).onChange( ( val ) => updateCameraViewOffset( { y: val } ) ).disable();
folder1.add( settings, 'width', window.screen.width / 4, window.screen.width * 2, 1 ).onChange( ( val ) => updateCameraViewOffset( { width: val } ) ).disable();
folder1.add( settings, 'height', window.screen.height / 4, window.screen.height * 2, 1 ).onChange( ( val ) => updateCameraViewOffset( { height: val } ) ).disable();
folder1.add( settings, 'clearViewOffset' );
}
function updateCameraViewOffset( { fullWidth, fullHeight, x, y, width, height } ) {
if ( ! camera.view ) {
camera.setViewOffset( fullWidth || window.innerWidth, fullHeight || window.innerHeight, x || 0, y || 0, width || window.innerWidth, height || window.innerHeight );
} else {
camera.setViewOffset( fullWidth || camera.view.fullWidth, fullHeight || camera.view.fullHeight, x || camera.view.offsetX, y || camera.view.offsetY, width || camera.view.width, height || camera.view.height );
}
}
</script>
</body>
</html>

View File

@ -0,0 +1,451 @@
<!DOCTYPE html>
<html>
<head>
<title>three.js css3d - periodic table</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link type="text/css" rel="stylesheet" href="main.css">
<style>
a {
color: #8ff;
}
#menu {
position: absolute;
bottom: 20px;
width: 100%;
text-align: center;
}
.element {
width: 120px;
height: 160px;
box-shadow: 0px 0px 12px rgba(0,255,255,0.5);
border: 1px solid rgba(127,255,255,0.25);
font-family: Helvetica, sans-serif;
text-align: center;
line-height: normal;
cursor: default;
}
.element:hover {
box-shadow: 0px 0px 12px rgba(0,255,255,0.75);
border: 1px solid rgba(127,255,255,0.75);
}
.element .number {
position: absolute;
top: 20px;
right: 20px;
font-size: 12px;
color: rgba(127,255,255,0.75);
}
.element .symbol {
position: absolute;
top: 40px;
left: 0px;
right: 0px;
font-size: 60px;
font-weight: bold;
color: rgba(255,255,255,0.75);
text-shadow: 0 0 10px rgba(0,255,255,0.95);
}
.element .details {
position: absolute;
bottom: 15px;
left: 0px;
right: 0px;
font-size: 12px;
color: rgba(127,255,255,0.75);
}
button {
color: rgba(127,255,255,0.75);
background: transparent;
outline: 1px solid rgba(127,255,255,0.75);
border: 0px;
padding: 5px 10px;
cursor: pointer;
}
button:hover {
background-color: rgba(0,255,255,0.5);
}
button:active {
color: #000000;
background-color: rgba(0,255,255,0.75);
}
</style>
</head>
<body>
<div id="info"><a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> css3d - periodic table.</div>
<div id="container"></div>
<div id="menu">
<button id="table">TABLE</button>
<button id="sphere">SPHERE</button>
<button id="helix">HELIX</button>
<button id="grid">GRID</button>
</div>
<script type="importmap">
{
"imports": {
"three": "../build/three.module.js",
"three/addons/": "./jsm/"
}
}
</script>
<script type="module">
import * as THREE from '/static/three/build/three.module.js';
import TWEEN from 'three/addons/libs/tween.module.js';
import { TrackballControls } from 'three/addons/controls/TrackballControls.js';
import { CSS3DRenderer, CSS3DObject } from 'three/addons/renderers/CSS3DRenderer.js';
const table = [
'H', 'Hydrogen', '1.00794', 1, 1,
'He', 'Helium', '4.002602', 18, 1,
'Li', 'Lithium', '6.941', 1, 2,
'Be', 'Beryllium', '9.012182', 2, 2,
'B', 'Boron', '10.811', 13, 2,
'C', 'Carbon', '12.0107', 14, 2,
'N', 'Nitrogen', '14.0067', 15, 2,
'O', 'Oxygen', '15.9994', 16, 2,
'F', 'Fluorine', '18.9984032', 17, 2,
'Ne', 'Neon', '20.1797', 18, 2,
'Na', 'Sodium', '22.98976...', 1, 3,
'Mg', 'Magnesium', '24.305', 2, 3,
'Al', 'Aluminium', '26.9815386', 13, 3,
'Si', 'Silicon', '28.0855', 14, 3,
'P', 'Phosphorus', '30.973762', 15, 3,
'S', 'Sulfur', '32.065', 16, 3,
'Cl', 'Chlorine', '35.453', 17, 3,
'Ar', 'Argon', '39.948', 18, 3,
'K', 'Potassium', '39.948', 1, 4,
'Ca', 'Calcium', '40.078', 2, 4,
'Sc', 'Scandium', '44.955912', 3, 4,
'Ti', 'Titanium', '47.867', 4, 4,
'V', 'Vanadium', '50.9415', 5, 4,
'Cr', 'Chromium', '51.9961', 6, 4,
'Mn', 'Manganese', '54.938045', 7, 4,
'Fe', 'Iron', '55.845', 8, 4,
'Co', 'Cobalt', '58.933195', 9, 4,
'Ni', 'Nickel', '58.6934', 10, 4,
'Cu', 'Copper', '63.546', 11, 4,
'Zn', 'Zinc', '65.38', 12, 4,
'Ga', 'Gallium', '69.723', 13, 4,
'Ge', 'Germanium', '72.63', 14, 4,
'As', 'Arsenic', '74.9216', 15, 4,
'Se', 'Selenium', '78.96', 16, 4,
'Br', 'Bromine', '79.904', 17, 4,
'Kr', 'Krypton', '83.798', 18, 4,
'Rb', 'Rubidium', '85.4678', 1, 5,
'Sr', 'Strontium', '87.62', 2, 5,
'Y', 'Yttrium', '88.90585', 3, 5,
'Zr', 'Zirconium', '91.224', 4, 5,
'Nb', 'Niobium', '92.90628', 5, 5,
'Mo', 'Molybdenum', '95.96', 6, 5,
'Tc', 'Technetium', '(98)', 7, 5,
'Ru', 'Ruthenium', '101.07', 8, 5,
'Rh', 'Rhodium', '102.9055', 9, 5,
'Pd', 'Palladium', '106.42', 10, 5,
'Ag', 'Silver', '107.8682', 11, 5,
'Cd', 'Cadmium', '112.411', 12, 5,
'In', 'Indium', '114.818', 13, 5,
'Sn', 'Tin', '118.71', 14, 5,
'Sb', 'Antimony', '121.76', 15, 5,
'Te', 'Tellurium', '127.6', 16, 5,
'I', 'Iodine', '126.90447', 17, 5,
'Xe', 'Xenon', '131.293', 18, 5,
'Cs', 'Caesium', '132.9054', 1, 6,
'Ba', 'Barium', '132.9054', 2, 6,
'La', 'Lanthanum', '138.90547', 4, 9,
'Ce', 'Cerium', '140.116', 5, 9,
'Pr', 'Praseodymium', '140.90765', 6, 9,
'Nd', 'Neodymium', '144.242', 7, 9,
'Pm', 'Promethium', '(145)', 8, 9,
'Sm', 'Samarium', '150.36', 9, 9,
'Eu', 'Europium', '151.964', 10, 9,
'Gd', 'Gadolinium', '157.25', 11, 9,
'Tb', 'Terbium', '158.92535', 12, 9,
'Dy', 'Dysprosium', '162.5', 13, 9,
'Ho', 'Holmium', '164.93032', 14, 9,
'Er', 'Erbium', '167.259', 15, 9,
'Tm', 'Thulium', '168.93421', 16, 9,
'Yb', 'Ytterbium', '173.054', 17, 9,
'Lu', 'Lutetium', '174.9668', 18, 9,
'Hf', 'Hafnium', '178.49', 4, 6,
'Ta', 'Tantalum', '180.94788', 5, 6,
'W', 'Tungsten', '183.84', 6, 6,
'Re', 'Rhenium', '186.207', 7, 6,
'Os', 'Osmium', '190.23', 8, 6,
'Ir', 'Iridium', '192.217', 9, 6,
'Pt', 'Platinum', '195.084', 10, 6,
'Au', 'Gold', '196.966569', 11, 6,
'Hg', 'Mercury', '200.59', 12, 6,
'Tl', 'Thallium', '204.3833', 13, 6,
'Pb', 'Lead', '207.2', 14, 6,
'Bi', 'Bismuth', '208.9804', 15, 6,
'Po', 'Polonium', '(209)', 16, 6,
'At', 'Astatine', '(210)', 17, 6,
'Rn', 'Radon', '(222)', 18, 6,
'Fr', 'Francium', '(223)', 1, 7,
'Ra', 'Radium', '(226)', 2, 7,
'Ac', 'Actinium', '(227)', 4, 10,
'Th', 'Thorium', '232.03806', 5, 10,
'Pa', 'Protactinium', '231.0588', 6, 10,
'U', 'Uranium', '238.02891', 7, 10,
'Np', 'Neptunium', '(237)', 8, 10,
'Pu', 'Plutonium', '(244)', 9, 10,
'Am', 'Americium', '(243)', 10, 10,
'Cm', 'Curium', '(247)', 11, 10,
'Bk', 'Berkelium', '(247)', 12, 10,
'Cf', 'Californium', '(251)', 13, 10,
'Es', 'Einstenium', '(252)', 14, 10,
'Fm', 'Fermium', '(257)', 15, 10,
'Md', 'Mendelevium', '(258)', 16, 10,
'No', 'Nobelium', '(259)', 17, 10,
'Lr', 'Lawrencium', '(262)', 18, 10,
'Rf', 'Rutherfordium', '(267)', 4, 7,
'Db', 'Dubnium', '(268)', 5, 7,
'Sg', 'Seaborgium', '(271)', 6, 7,
'Bh', 'Bohrium', '(272)', 7, 7,
'Hs', 'Hassium', '(270)', 8, 7,
'Mt', 'Meitnerium', '(276)', 9, 7,
'Ds', 'Darmstadium', '(281)', 10, 7,
'Rg', 'Roentgenium', '(280)', 11, 7,
'Cn', 'Copernicium', '(285)', 12, 7,
'Nh', 'Nihonium', '(286)', 13, 7,
'Fl', 'Flerovium', '(289)', 14, 7,
'Mc', 'Moscovium', '(290)', 15, 7,
'Lv', 'Livermorium', '(293)', 16, 7,
'Ts', 'Tennessine', '(294)', 17, 7,
'Og', 'Oganesson', '(294)', 18, 7
];
let camera, scene, renderer;
let controls;
const objects = [];
const targets = { table: [], sphere: [], helix: [], grid: [] };
init();
animate();
function init() {
camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.z = 3000;
scene = new THREE.Scene();
// table
for ( let i = 0; i < table.length; i += 5 ) {
const element = document.createElement( 'div' );
element.className = 'element';
element.style.backgroundColor = 'rgba(0,127,127,' + ( Math.random() * 0.5 + 0.25 ) + ')';
const number = document.createElement( 'div' );
number.className = 'number';
number.textContent = ( i / 5 ) + 1;
element.appendChild( number );
const symbol = document.createElement( 'div' );
symbol.className = 'symbol';
symbol.textContent = table[ i ];
element.appendChild( symbol );
const details = document.createElement( 'div' );
details.className = 'details';
details.innerHTML = table[ i + 1 ] + '<br>' + table[ i + 2 ];
element.appendChild( details );
const objectCSS = new CSS3DObject( element );
objectCSS.position.x = Math.random() * 4000 - 2000;
objectCSS.position.y = Math.random() * 4000 - 2000;
objectCSS.position.z = Math.random() * 4000 - 2000;
scene.add( objectCSS );
objects.push( objectCSS );
//
const object = new THREE.Object3D();
object.position.x = ( table[ i + 3 ] * 140 ) - 1330;
object.position.y = - ( table[ i + 4 ] * 180 ) + 990;
targets.table.push( object );
}
// sphere
const vector = new THREE.Vector3();
for ( let i = 0, l = objects.length; i < l; i ++ ) {
const phi = Math.acos( - 1 + ( 2 * i ) / l );
const theta = Math.sqrt( l * Math.PI ) * phi;
const object = new THREE.Object3D();
object.position.setFromSphericalCoords( 800, phi, theta );
vector.copy( object.position ).multiplyScalar( 2 );
object.lookAt( vector );
targets.sphere.push( object );
}
// helix
for ( let i = 0, l = objects.length; i < l; i ++ ) {
const theta = i * 0.175 + Math.PI;
const y = - ( i * 8 ) + 450;
const object = new THREE.Object3D();
object.position.setFromCylindricalCoords( 900, theta, y );
vector.x = object.position.x * 2;
vector.y = object.position.y;
vector.z = object.position.z * 2;
object.lookAt( vector );
targets.helix.push( object );
}
// grid
for ( let i = 0; i < objects.length; i ++ ) {
const object = new THREE.Object3D();
object.position.x = ( ( i % 5 ) * 400 ) - 800;
object.position.y = ( - ( Math.floor( i / 5 ) % 5 ) * 400 ) + 800;
object.position.z = ( Math.floor( i / 25 ) ) * 1000 - 2000;
targets.grid.push( object );
}
//
renderer = new CSS3DRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.getElementById( 'container' ).appendChild( renderer.domElement );
//
controls = new TrackballControls( camera, renderer.domElement );
controls.minDistance = 500;
controls.maxDistance = 6000;
controls.addEventListener( 'change', render );
const buttonTable = document.getElementById( 'table' );
buttonTable.addEventListener( 'click', function () {
transform( targets.table, 2000 );
} );
const buttonSphere = document.getElementById( 'sphere' );
buttonSphere.addEventListener( 'click', function () {
transform( targets.sphere, 2000 );
} );
const buttonHelix = document.getElementById( 'helix' );
buttonHelix.addEventListener( 'click', function () {
transform( targets.helix, 2000 );
} );
const buttonGrid = document.getElementById( 'grid' );
buttonGrid.addEventListener( 'click', function () {
transform( targets.grid, 2000 );
} );
transform( targets.table, 2000 );
//
window.addEventListener( 'resize', onWindowResize );
}
function transform( targets, duration ) {
TWEEN.removeAll();
for ( let i = 0; i < objects.length; i ++ ) {
const object = objects[ i ];
const target = targets[ i ];
new TWEEN.Tween( object.position )
.to( { x: target.position.x, y: target.position.y, z: target.position.z }, Math.random() * duration + duration )
.easing( TWEEN.Easing.Exponential.InOut )
.start();
new TWEEN.Tween( object.rotation )
.to( { x: target.rotation.x, y: target.rotation.y, z: target.rotation.z }, Math.random() * duration + duration )
.easing( TWEEN.Easing.Exponential.InOut )
.start();
}
new TWEEN.Tween( this )
.to( {}, duration * 2 )
.onUpdate( render )
.start();
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
render();
}
function animate() {
requestAnimationFrame( animate );
TWEEN.update();
controls.update();
}
function render() {
renderer.render( scene, camera );
}
</script>
</body>
</html>

View File

@ -0,0 +1,196 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link type="text/css" rel="stylesheet" href="main.css">
<style>
body {
color: #000;
}
a {
color: #080;
}
</style>
</head>
<body>
<div id="info"><a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> css3d - sandbox</div>
<script type="importmap">
{
"imports": {
"three": "../build/three.module.js",
"three/addons/": "./jsm/"
}
}
</script>
<script type="module">
import * as THREE from '/static/three/build/three.module.js';
import { TrackballControls } from 'three/addons/controls/TrackballControls.js';
import { CSS3DRenderer, CSS3DObject } from 'three/addons/renderers/CSS3DRenderer.js';
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
let camera, scene, renderer;
let scene2, renderer2;
let controls;
init();
animate();
function init() {
camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 1000 );
camera.position.set( 200, 200, 200 );
scene = new THREE.Scene();
scene.background = new THREE.Color( 0xf0f0f0 );
scene2 = new THREE.Scene();
const material = new THREE.MeshBasicMaterial( { color: 0x000000, wireframe: true, wireframeLinewidth: 1, side: THREE.DoubleSide } );
//
for ( let i = 0; i < 10; i ++ ) {
const element = document.createElement( 'div' );
element.style.width = '100px';
element.style.height = '100px';
element.style.opacity = ( i < 5 ) ? 0.5 : 1;
element.style.background = new THREE.Color( Math.random() * 0xffffff ).getStyle();
const object = new CSS3DObject( element );
object.position.x = Math.random() * 200 - 100;
object.position.y = Math.random() * 200 - 100;
object.position.z = Math.random() * 200 - 100;
object.rotation.x = Math.random();
object.rotation.y = Math.random();
object.rotation.z = Math.random();
object.scale.x = Math.random() + 0.5;
object.scale.y = Math.random() + 0.5;
scene2.add( object );
const geometry = new THREE.PlaneGeometry( 100, 100 );
const mesh = new THREE.Mesh( geometry, material );
mesh.position.copy( object.position );
mesh.rotation.copy( object.rotation );
mesh.scale.copy( object.scale );
scene.add( mesh );
}
//
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
renderer2 = new CSS3DRenderer();
renderer2.setSize( window.innerWidth, window.innerHeight );
renderer2.domElement.style.position = 'absolute';
renderer2.domElement.style.top = 0;
document.body.appendChild( renderer2.domElement );
controls = new TrackballControls( camera, renderer2.domElement );
window.addEventListener( 'resize', onWindowResize );
createPanel();
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
renderer2.setSize( window.innerWidth, window.innerHeight );
}
function animate() {
requestAnimationFrame( animate );
controls.update();
renderer.render( scene, camera );
renderer2.render( scene2, camera );
}
function createPanel() {
const panel = new GUI();
const folder1 = panel.addFolder( 'camera setViewOffset' ).close();
const settings = {
'setViewOffset'() {
folder1.children[ 1 ].enable().setValue( window.innerWidth );
folder1.children[ 2 ].enable().setValue( window.innerHeight );
folder1.children[ 3 ].enable().setValue( 0 );
folder1.children[ 4 ].enable().setValue( 0 );
folder1.children[ 5 ].enable().setValue( window.innerWidth );
folder1.children[ 6 ].enable().setValue( window.innerHeight );
},
'fullWidth': 0,
'fullHeight': 0,
'offsetX': 0,
'offsetY': 0,
'width': 0,
'height': 0,
'clearViewOffset'() {
folder1.children[ 1 ].setValue( 0 ).disable();
folder1.children[ 2 ].setValue( 0 ).disable();
folder1.children[ 3 ].setValue( 0 ).disable();
folder1.children[ 4 ].setValue( 0 ).disable();
folder1.children[ 5 ].setValue( 0 ).disable();
folder1.children[ 6 ].setValue( 0 ).disable();
camera.clearViewOffset();
}
};
folder1.add( settings, 'setViewOffset' );
folder1.add( settings, 'fullWidth', window.screen.width / 4, window.screen.width * 2, 1 ).onChange( ( val ) => updateCameraViewOffset( { fullWidth: val } ) ).disable();
folder1.add( settings, 'fullHeight', window.screen.height / 4, window.screen.height * 2, 1 ).onChange( ( val ) => updateCameraViewOffset( { fullHeight: val } ) ).disable();
folder1.add( settings, 'offsetX', 0, 256, 1 ).onChange( ( val ) => updateCameraViewOffset( { x: val } ) ).disable();
folder1.add( settings, 'offsetY', 0, 256, 1 ).onChange( ( val ) => updateCameraViewOffset( { y: val } ) ).disable();
folder1.add( settings, 'width', window.screen.width / 4, window.screen.width * 2, 1 ).onChange( ( val ) => updateCameraViewOffset( { width: val } ) ).disable();
folder1.add( settings, 'height', window.screen.height / 4, window.screen.height * 2, 1 ).onChange( ( val ) => updateCameraViewOffset( { height: val } ) ).disable();
folder1.add( settings, 'clearViewOffset' );
}
function updateCameraViewOffset( { fullWidth, fullHeight, x, y, width, height } ) {
if ( ! camera.view ) {
camera.setViewOffset( fullWidth || window.innerWidth, fullHeight || window.innerHeight, x || 0, y || 0, width || window.innerWidth, height || window.innerHeight );
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
} else {
camera.setViewOffset( fullWidth || camera.view.fullWidth, fullHeight || camera.view.fullHeight, x || camera.view.offsetX, y || camera.view.offsetY, width || camera.view.width, height || camera.view.height );
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
}
}
</script>
</body>
</html>

View File

@ -0,0 +1,219 @@
<!DOCTYPE html>
<html>
<head>
<title>three.js css3d - sprites</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link type="text/css" rel="stylesheet" href="main.css">
<style>
body {
background-color: #fff;
color: #000;
}
a {
color: #48f;
}
</style>
</head>
<body>
<div id="info"><a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> css3d - sprites</div>
<div id="container"></div>
<script type="importmap">
{
"imports": {
"three": "../build/three.module.js",
"three/addons/": "./jsm/"
}
}
</script>
<script type="module">
import * as THREE from '/static/three/build/three.module.js';
import TWEEN from 'three/addons/libs/tween.module.js';
import { TrackballControls } from 'three/addons/controls/TrackballControls.js';
import { CSS3DRenderer, CSS3DSprite } from 'three/addons/renderers/CSS3DRenderer.js';
let camera, scene, renderer;
let controls;
const particlesTotal = 512;
const positions = [];
const objects = [];
let current = 0;
init();
animate();
function init() {
camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 5000 );
camera.position.set( 600, 400, 1500 );
camera.lookAt( 0, 0, 0 );
scene = new THREE.Scene();
const image = document.createElement( 'img' );
image.addEventListener( 'load', function () {
for ( let i = 0; i < particlesTotal; i ++ ) {
const object = new CSS3DSprite( image.cloneNode() );
object.position.x = Math.random() * 4000 - 2000,
object.position.y = Math.random() * 4000 - 2000,
object.position.z = Math.random() * 4000 - 2000;
scene.add( object );
objects.push( object );
}
transition();
} );
image.src = 'textures/sprite.png';
// Plane
const amountX = 16;
const amountZ = 32;
const separationPlane = 150;
const offsetX = ( ( amountX - 1 ) * separationPlane ) / 2;
const offsetZ = ( ( amountZ - 1 ) * separationPlane ) / 2;
for ( let i = 0; i < particlesTotal; i ++ ) {
const x = ( i % amountX ) * separationPlane;
const z = Math.floor( i / amountX ) * separationPlane;
const y = ( Math.sin( x * 0.5 ) + Math.sin( z * 0.5 ) ) * 200;
positions.push( x - offsetX, y, z - offsetZ );
}
// Cube
const amount = 8;
const separationCube = 150;
const offset = ( ( amount - 1 ) * separationCube ) / 2;
for ( let i = 0; i < particlesTotal; i ++ ) {
const x = ( i % amount ) * separationCube;
const y = Math.floor( ( i / amount ) % amount ) * separationCube;
const z = Math.floor( i / ( amount * amount ) ) * separationCube;
positions.push( x - offset, y - offset, z - offset );
}
// Random
for ( let i = 0; i < particlesTotal; i ++ ) {
positions.push(
Math.random() * 4000 - 2000,
Math.random() * 4000 - 2000,
Math.random() * 4000 - 2000
);
}
// Sphere
const radius = 750;
for ( let i = 0; i < particlesTotal; i ++ ) {
const phi = Math.acos( - 1 + ( 2 * i ) / particlesTotal );
const theta = Math.sqrt( particlesTotal * Math.PI ) * phi;
positions.push(
radius * Math.cos( theta ) * Math.sin( phi ),
radius * Math.sin( theta ) * Math.sin( phi ),
radius * Math.cos( phi )
);
}
//
renderer = new CSS3DRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.getElementById( 'container' ).appendChild( renderer.domElement );
//
controls = new TrackballControls( camera, renderer.domElement );
//
window.addEventListener( 'resize', onWindowResize );
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
function transition() {
const offset = current * particlesTotal * 3;
const duration = 2000;
for ( let i = 0, j = offset; i < particlesTotal; i ++, j += 3 ) {
const object = objects[ i ];
new TWEEN.Tween( object.position )
.to( {
x: positions[ j ],
y: positions[ j + 1 ],
z: positions[ j + 2 ]
}, Math.random() * duration + duration )
.easing( TWEEN.Easing.Exponential.InOut )
.start();
}
new TWEEN.Tween( this )
.to( {}, duration * 3 )
.onComplete( transition )
.start();
current = ( current + 1 ) % 4;
}
function animate() {
requestAnimationFrame( animate );
TWEEN.update();
controls.update();
const time = performance.now();
for ( let i = 0, l = objects.length; i < l; i ++ ) {
const object = objects[ i ];
const scale = Math.sin( ( Math.floor( object.position.x ) + time ) * 0.002 ) * 0.3 + 1;
object.scale.set( scale, scale, scale );
}
renderer.render( scene, camera );
}
</script>
</body>
</html>

View File

@ -0,0 +1,131 @@
<!DOCTYPE html>
<html>
<head>
<title>three.js css3d - youtube</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link type="text/css" rel="stylesheet" href="main.css">
<style>
body {
background-color: #ffffff;
}
#blocker {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
}
</style>
</head>
<body>
<div id="container"></div>
<div id="blocker"></div>
<script type="importmap">
{
"imports": {
"three": "../build/three.module.js",
"three/addons/": "./jsm/"
}
}
</script>
<script type="module">
import * as THREE from '/static/three/build/three.module.js';
import { TrackballControls } from 'three/addons/controls/TrackballControls.js';
import { CSS3DRenderer, CSS3DObject } from 'three/addons/renderers/CSS3DRenderer.js';
let camera, scene, renderer;
let controls;
function Element( id, x, y, z, ry ) {
const div = document.createElement( 'div' );
div.style.width = '480px';
div.style.height = '360px';
div.style.backgroundColor = '#000';
const iframe = document.createElement( 'iframe' );
iframe.style.width = '480px';
iframe.style.height = '360px';
iframe.style.border = '0px';
iframe.src = [ 'https://www.youtube.com/embed/', id, '?rel=0' ].join( '' );
div.appendChild( iframe );
const object = new CSS3DObject( div );
object.position.set( x, y, z );
object.rotation.y = ry;
return object;
}
init();
animate();
function init() {
const container = document.getElementById( 'container' );
camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 1, 5000 );
camera.position.set( 500, 350, 750 );
scene = new THREE.Scene();
renderer = new CSS3DRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild( renderer.domElement );
const group = new THREE.Group();
group.add( new Element( 'SJOz3qjfQXU', 0, 0, 240, 0 ) );
group.add( new Element( 'Y2-xZ-1HE-Q', 240, 0, 0, Math.PI / 2 ) );
group.add( new Element( 'IrydklNpcFI', 0, 0, - 240, Math.PI ) );
group.add( new Element( '9ubytEsCaS0', - 240, 0, 0, - Math.PI / 2 ) );
scene.add( group );
controls = new TrackballControls( camera, renderer.domElement );
controls.rotateSpeed = 4;
window.addEventListener( 'resize', onWindowResize );
// Block iframe events when dragging camera
const blocker = document.getElementById( 'blocker' );
blocker.style.display = 'none';
controls.addEventListener( 'start', function () {
blocker.style.display = '';
} );
controls.addEventListener( 'end', function () {
blocker.style.display = 'none';
} );
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
function animate() {
requestAnimationFrame( animate );
controls.update();
renderer.render( scene, camera );
}
</script>
</body>
</html>

View File

@ -0,0 +1,497 @@
{
"webgl": [
"webgl_animation_keyframes",
"webgl_animation_skinning_blending",
"webgl_animation_skinning_additive_blending",
"webgl_animation_skinning_ik",
"webgl_animation_skinning_morph",
"webgl_animation_multiple",
"webgl_camera",
"webgl_camera_array",
"webgl_camera_cinematic",
"webgl_camera_logarithmicdepthbuffer",
"webgl_clipping",
"webgl_clipping_advanced",
"webgl_clipping_intersection",
"webgl_clipping_stencil",
"webgl_decals",
"webgl_depth_texture",
"webgl_effects_anaglyph",
"webgl_effects_ascii",
"webgl_effects_parallaxbarrier",
"webgl_effects_peppersghost",
"webgl_effects_stereo",
"webgl_framebuffer_texture",
"webgl_geometries",
"webgl_geometries_parametric",
"webgl_geometry_colors",
"webgl_geometry_colors_lookuptable",
"webgl_geometry_convex",
"webgl_geometry_csg",
"webgl_geometry_cube",
"webgl_geometry_dynamic",
"webgl_geometry_extrude_shapes",
"webgl_geometry_extrude_splines",
"webgl_geometry_minecraft",
"webgl_geometry_nurbs",
"webgl_geometry_sdf",
"webgl_geometry_shapes",
"webgl_geometry_spline_editor",
"webgl_geometry_teapot",
"webgl_geometry_terrain",
"webgl_geometry_terrain_raycast",
"webgl_geometry_text",
"webgl_geometry_text_shapes",
"webgl_geometry_text_stroke",
"webgl_helpers",
"webgl_instancing_morph",
"webgl_instancing_dynamic",
"webgl_instancing_performance",
"webgl_instancing_raycast",
"webgl_instancing_scatter",
"webgl_interactive_buffergeometry",
"webgl_interactive_cubes",
"webgl_interactive_cubes_gpu",
"webgl_interactive_cubes_ortho",
"webgl_interactive_lines",
"webgl_interactive_points",
"webgl_interactive_raycasting_points",
"webgl_interactive_voxelpainter",
"webgl_layers",
"webgl_lensflares",
"webgl_lightprobe",
"webgl_lightprobe_cubecamera",
"webgl_lights_hemisphere",
"webgl_lights_physical",
"webgl_lights_pointlights",
"webgl_lights_spotlight",
"webgl_lights_spotlights",
"webgl_lights_rectarealight",
"webgl_lines_colors",
"webgl_lines_dashed",
"webgl_lines_fat",
"webgl_lines_fat_raycasting",
"webgl_lines_fat_wireframe",
"webgl_loader_3dm",
"webgl_loader_3ds",
"webgl_loader_3mf",
"webgl_loader_3mf_materials",
"webgl_loader_amf",
"webgl_loader_bvh",
"webgl_loader_collada",
"webgl_loader_collada_kinematics",
"webgl_loader_collada_skinning",
"webgl_loader_draco",
"webgl_loader_fbx",
"webgl_loader_fbx_nurbs",
"webgl_loader_gcode",
"webgl_loader_gltf",
"webgl_loader_gltf_avif",
"webgl_loader_gltf_compressed",
"webgl_loader_gltf_dispersion",
"webgl_loader_gltf_instancing",
"webgl_loader_gltf_iridescence",
"webgl_loader_gltf_lights",
"webgl_loader_gltf_sheen",
"webgl_loader_gltf_transmission",
"webgl_loader_gltf_variants",
"webgl_loader_gltf_anisotropy",
"webgl_loader_ifc",
"webgl_loader_imagebitmap",
"webgl_loader_kmz",
"webgl_loader_ldraw",
"webgl_loader_lwo",
"webgl_loader_md2",
"webgl_loader_md2_control",
"webgl_loader_mdd",
"webgl_loader_mmd",
"webgl_loader_mmd_pose",
"webgl_loader_mmd_audio",
"webgl_loader_nrrd",
"webgl_loader_obj",
"webgl_loader_obj_mtl",
"webgl_loader_pcd",
"webgl_loader_pdb",
"webgl_loader_ply",
"webgl_loader_stl",
"webgl_loader_svg",
"webgl_loader_tilt",
"webgl_loader_texture_dds",
"webgl_loader_texture_exr",
"webgl_loader_texture_ultrahdr",
"webgl_loader_texture_hdr",
"webgl_loader_texture_ktx",
"webgl_loader_texture_ktx2",
"webgl_loader_texture_logluv",
"webgl_loader_texture_lottie",
"webgl_loader_texture_pvrtc",
"webgl_loader_texture_rgbm",
"webgl_loader_texture_tga",
"webgl_loader_texture_tiff",
"webgl_loader_ttf",
"webgl_loader_usdz",
"webgl_loader_vox",
"webgl_loader_vrml",
"webgl_loader_vtk",
"webgl_loader_xyz",
"webgl_lod",
"webgl_marchingcubes",
"webgl_materials_alphahash",
"webgl_materials_blending",
"webgl_materials_blending_custom",
"webgl_materials_bumpmap",
"webgl_materials_car",
"webgl_materials_channels",
"webgl_materials_cubemap",
"webgl_materials_cubemap_dynamic",
"webgl_materials_cubemap_refraction",
"webgl_materials_cubemap_mipmaps",
"webgl_materials_cubemap_render_to_mipmaps",
"webgl_materials_curvature",
"webgl_materials_displacementmap",
"webgl_materials_envmaps",
"webgl_materials_envmaps_exr",
"webgl_materials_envmaps_groundprojected",
"webgl_materials_envmaps_hdr",
"webgl_materials_matcap",
"webgl_materials_normalmap",
"webgl_materials_normalmap_object_space",
"webgl_materials_physical_clearcoat",
"webgl_materials_physical_transmission",
"webgl_materials_physical_transmission_alpha",
"webgl_materials_subsurface_scattering",
"webgl_materials_texture_anisotropy",
"webgl_materials_texture_canvas",
"webgl_materials_texture_filters",
"webgl_materials_texture_manualmipmap",
"webgl_materials_texture_partialupdate",
"webgl_materials_texture_rotation",
"webgl_materials_toon",
"webgl_materials_video",
"webgl_materials_video_webcam",
"webgl_materials_wireframe",
"webgl_math_obb",
"webgl_math_orientation_transform",
"webgl_mesh_batch",
"webgl_mirror",
"webgl_modifier_curve",
"webgl_modifier_curve_instanced",
"webgl_modifier_edgesplit",
"webgl_modifier_simplifier",
"webgl_modifier_subdivision",
"webgl_modifier_tessellation",
"webgl_morphtargets",
"webgl_morphtargets_face",
"webgl_morphtargets_horse",
"webgl_morphtargets_sphere",
"webgl_morphtargets_webcam",
"webgl_multiple_elements",
"webgl_multiple_elements_text",
"webgl_multiple_scenes_comparison",
"webgl_multiple_views",
"webgl_panorama_cube",
"webgl_panorama_equirectangular",
"webgl_points_billboards",
"webgl_points_dynamic",
"webgl_points_sprites",
"webgl_points_waves",
"webgl_portal",
"webgl_raycaster_bvh",
"webgl_raycaster_sprite",
"webgl_raycaster_texture",
"webgl_read_float_buffer",
"webgl_renderer_pathtracer",
"webgl_refraction",
"webgl_rtt",
"webgl_shader",
"webgl_shader_lava",
"webgl_shaders_ocean",
"webgl_shaders_sky",
"webgl_shadow_contact",
"webgl_shadowmap",
"webgl_shadowmap_performance",
"webgl_shadowmap_pointlight",
"webgl_shadowmap_viewer",
"webgl_shadowmap_vsm",
"webgl_shadowmesh",
"webgl_sprites",
"webgl_test_memory",
"webgl_test_memory2",
"webgl_test_wide_gamut",
"webgl_tonemapping",
"webgl_video_kinect",
"webgl_video_panorama_equirectangular",
"webgl_water",
"webgl_water_flowmap"
],
"webgl / postprocessing": [
"webgl_postprocessing",
"webgl_postprocessing_3dlut",
"webgl_postprocessing_advanced",
"webgl_postprocessing_afterimage",
"webgl_postprocessing_backgrounds",
"webgl_postprocessing_transition",
"webgl_postprocessing_dof",
"webgl_postprocessing_dof2",
"webgl_postprocessing_fxaa",
"webgl_postprocessing_glitch",
"webgl_postprocessing_godrays",
"webgl_postprocessing_gtao",
"webgl_postprocessing_rgb_halftone",
"webgl_postprocessing_masking",
"webgl_postprocessing_material_ao",
"webgl_postprocessing_ssaa",
"webgl_postprocessing_outline",
"webgl_postprocessing_pixel",
"webgl_postprocessing_procedural",
"webgl_postprocessing_sao",
"webgl_postprocessing_smaa",
"webgl_postprocessing_sobel",
"webgl_postprocessing_ssao",
"webgl_postprocessing_ssr",
"webgl_postprocessing_taa",
"webgl_postprocessing_unreal_bloom",
"webgl_postprocessing_unreal_bloom_selective"
],
"webgl / advanced": [
"webgl_buffergeometry",
"webgl_buffergeometry_attributes_integer",
"webgl_buffergeometry_attributes_none",
"webgl_buffergeometry_compression",
"webgl_buffergeometry_custom_attributes_particles",
"webgl_buffergeometry_drawrange",
"webgl_buffergeometry_glbufferattribute",
"webgl_buffergeometry_indexed",
"webgl_buffergeometry_instancing",
"webgl_buffergeometry_instancing_billboards",
"webgl_buffergeometry_instancing_interleaved",
"webgl_buffergeometry_lines",
"webgl_buffergeometry_lines_indexed",
"webgl_buffergeometry_points",
"webgl_buffergeometry_points_interleaved",
"webgl_buffergeometry_rawshader",
"webgl_buffergeometry_selective_draw",
"webgl_buffergeometry_uint",
"webgl_clipculldistance",
"webgl_custom_attributes",
"webgl_custom_attributes_lines",
"webgl_custom_attributes_points",
"webgl_custom_attributes_points2",
"webgl_custom_attributes_points3",
"webgl_gpgpu_birds",
"webgl_gpgpu_birds_gltf",
"webgl_gpgpu_water",
"webgl_gpgpu_protoplanet",
"webgl_materials_modified",
"webgl_multiple_rendertargets",
"webgl_multisampled_renderbuffers",
"webgl_raymarching_reflect",
"webgl_rendertarget_texture2darray",
"webgl_shadowmap_csm",
"webgl_shadowmap_pcss",
"webgl_shadowmap_progressive",
"webgl_simple_gi",
"webgl_texture2darray",
"webgl_texture2darray_compressed",
"webgl_texture2darray_layerupdate",
"webgl_texture3d",
"webgl_texture3d_partialupdate",
"webgl_ubo",
"webgl_ubo_arrays",
"webgl_volume_cloud",
"webgl_volume_instancing",
"webgl_volume_perlin",
"webgl_worker_offscreencanvas"
],
"webgpu (wip)": [
"webgpu_backdrop",
"webgpu_backdrop_area",
"webgpu_backdrop_water",
"webgpu_camera_logarithmicdepthbuffer",
"webgpu_clearcoat",
"webgpu_clipping",
"webgpu_compute_audio",
"webgpu_compute_geometry",
"webgpu_compute_particles",
"webgpu_compute_particles_rain",
"webgpu_compute_particles_snow",
"webgpu_compute_points",
"webgpu_compute_texture",
"webgpu_compute_texture_pingpong",
"webgpu_cubemap_adjustments",
"webgpu_cubemap_dynamic",
"webgpu_cubemap_mix",
"webgpu_custom_fog",
"webgpu_custom_fog_background",
"webgpu_depth_texture",
"webgpu_equirectangular",
"webgpu_instance_mesh",
"webgpu_instance_points",
"webgpu_instance_uniform",
"webgpu_instancing_morph",
"webgpu_lights_custom",
"webgpu_lights_ies_spotlight",
"webgpu_lights_phong",
"webgpu_lights_rectarealight",
"webgpu_lights_selective",
"webgpu_lines_fat",
"webgpu_loader_gltf",
"webgpu_loader_gltf_anisotropy",
"webgpu_loader_gltf_compressed",
"webgpu_loader_gltf_dispersion",
"webgpu_loader_gltf_iridescence",
"webgpu_loader_gltf_sheen",
"webgpu_loader_gltf_transmission",
"webgpu_loader_materialx",
"webgpu_materials",
"webgpu_materials_basic",
"webgpu_materials_displacementmap",
"webgpu_materials_lightmap",
"webgpu_materials_matcap",
"webgpu_materials_sss",
"webgpu_materials_transmission",
"webgpu_materials_toon",
"webgpu_materials_video",
"webgpu_materialx_noise",
"webgpu_mesh_batch",
"webgpu_mirror",
"webgpu_morphtargets",
"webgpu_morphtargets_face",
"webgpu_mrt",
"webgpu_mrt_mask",
"webgpu_multiple_rendertargets",
"webgpu_multiple_rendertargets_readback",
"webgpu_multisampled_renderbuffers",
"webgpu_occlusion",
"webgpu_parallax_uv",
"webgpu_particles",
"webgpu_performance_renderbundle",
"webgpu_pmrem_cubemap",
"webgpu_pmrem_equirectangular",
"webgpu_pmrem_scene",
"webgpu_portal",
"webgpu_postprocessing_3dlut",
"webgpu_postprocessing_afterimage",
"webgpu_postprocessing_anamorphic",
"webgpu_postprocessing_ao",
"webgpu_postprocessing_bloom",
"webgpu_postprocessing_bloom_emissive",
"webgpu_postprocessing_bloom_selective",
"webgpu_postprocessing_dof",
"webgpu_postprocessing_pixel",
"webgpu_postprocessing_fxaa",
"webgpu_postprocessing_sobel",
"webgpu_postprocessing_transition",
"webgpu_postprocessing",
"webgpu_procedural_texture",
"webgpu_reflection",
"webgpu_refraction",
"webgpu_rtt",
"webgpu_sandbox",
"webgpu_shadertoy",
"webgpu_tsl_interoperability",
"webgpu_shadowmap",
"webgpu_skinning",
"webgpu_skinning_instancing",
"webgpu_skinning_points",
"webgpu_sprites",
"webgpu_storage_buffer",
"webgpu_texturegrad",
"webgpu_textures_2d-array",
"webgpu_textures_anisotropy",
"webgpu_textures_partialupdate",
"webgpu_tsl_editor",
"webgpu_tsl_galaxy",
"webgpu_tsl_transpiler",
"webgpu_video_panorama",
"webgpu_volume_cloud",
"webgpu_volume_perlin"
],
"webaudio": [
"webaudio_orientation",
"webaudio_sandbox",
"webaudio_timing",
"webaudio_visualizer"
],
"webxr": [
"webxr_ar_cones",
"webxr_ar_hittest",
"webxr_ar_lighting",
"webxr_ar_plane_detection",
"webxr_vr_handinput",
"webxr_vr_handinput_cubes",
"webxr_vr_handinput_profiles",
"webxr_vr_handinput_pointerclick",
"webxr_vr_handinput_pointerdrag",
"webxr_vr_handinput_pressbutton",
"webxr_vr_layers",
"webxr_vr_panorama",
"webxr_vr_panorama_depth",
"webxr_vr_rollercoaster",
"webxr_vr_sandbox",
"webxr_vr_teleport",
"webxr_vr_video",
"webxr_xr_ballshooter",
"webxr_xr_controls_transform",
"webxr_xr_cubes",
"webxr_xr_dragging",
"webxr_xr_dragging_custom_depth",
"webxr_xr_haptics",
"webxr_xr_paint",
"webxr_xr_sculpt"
],
"games": [
"games_fps"
],
"physics": [
"physics_ammo_break",
"physics_ammo_cloth",
"physics_ammo_instancing",
"physics_ammo_rope",
"physics_ammo_terrain",
"physics_ammo_volume",
"physics_jolt_instancing",
"physics_rapier_instancing"
],
"misc": [
"misc_animation_groups",
"misc_animation_keys",
"misc_boxselection",
"misc_controls_arcball",
"misc_controls_drag",
"misc_controls_fly",
"misc_controls_map",
"misc_controls_orbit",
"misc_controls_pointerlock",
"misc_controls_trackball",
"misc_controls_transform",
"misc_exporter_draco",
"misc_exporter_gltf",
"misc_exporter_obj",
"misc_exporter_ply",
"misc_exporter_stl",
"misc_exporter_usdz",
"misc_exporter_exr",
"misc_lookat"
],
"css2d": [
"css2d_label"
],
"css3d": [
"css3d_molecules",
"css3d_orthographic",
"css3d_periodictable",
"css3d_sandbox",
"css3d_sprites",
"css3d_youtube"
],
"svg": [
"svg_lines",
"svg_sandbox"
],
"tests": [
"webgl_furnace_test",
"webgl_pmrem_test",
"misc_uv_tests"
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
<g fill="none" stroke="#000" stroke-linecap="round" stroke-width="35">
<path d="m308.67 108.63h-194.68c-27.7 0-50 22.3-50 50v194.68m142.66 55.332h194.59c27.7 0 50-22.3 50-50v-194.59"/>
<path d="m66.583 449.71 379.6-379.6"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 358 B

View File

@ -0,0 +1,13 @@
Copyright @ 2004 by MAGENTA Ltd. All Rights Reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions:
The above copyright and this permission notice shall be included in all copies of one or more of the Font Software typefaces.
The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing the word "MgOpen", or if the modifications are accepted for inclusion in the Font Software itself by the each appointed Administrator.
This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "MgOpen" name.
The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself.
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL MAGENTA OR PERSONS OR BODIES IN CHARGE OF ADMINISTRATION AND MAINTENANCE OF THE FONT SOFTWARE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.

View File

@ -0,0 +1,11 @@
## MgOpen typefaces
# Source and License
https://web.archive.org/web/20050528114140/https://ellak.gr/fonts/mgopen/index.en
# Usage
Use Facetype.js to generate typeface.json fonts: https://gero3.github.io/facetype.js/
Collection of Google fonts as typeface data for usage with three.js: https://github.com/components-ai/typefaces

View File

@ -0,0 +1,190 @@
Copyright (c) 2005-2008, The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View File

@ -0,0 +1,18 @@
Copyright (C) 2008 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
##########
This directory contains the fonts for the platform. They are licensed
under the Apache 2 license.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,9 @@
# Kenney Fonts
## Source
https://www.kenney.nl/assets/kenney-fonts
## License
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication (https://creativecommons.org/publicdomain/zero/1.0/)

View File

@ -0,0 +1,495 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js - misc - octree collisions</title>
<meta charset=utf-8 />
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link type="text/css" rel="stylesheet" href="main.css">
</head>
<body>
<div id="info">
Octree threejs demo - basic collisions with static triangle mesh<br />
MOUSE to look around and to throw balls<br/>
WASD to move and SPACE to jump
</div>
<div id="container"></div>
<script type="importmap">
{
"imports": {
"three": "../build/three.module.js",
"three/addons/": "./jsm/"
}
}
</script>
<script type="module">
import * as THREE from '/static/three/build/three.module.js';
import Stats from 'three/addons/libs/stats.module.js';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { Octree } from 'three/addons/math/Octree.js';
import { OctreeHelper } from 'three/addons/helpers/OctreeHelper.js';
import { Capsule } from 'three/addons/math/Capsule.js';
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
const clock = new THREE.Clock();
const scene = new THREE.Scene();
scene.background = new THREE.Color( 0x88ccee );
scene.fog = new THREE.Fog( 0x88ccee, 0, 50 );
const camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.1, 1000 );
camera.rotation.order = 'YXZ';
const fillLight1 = new THREE.HemisphereLight( 0x8dc1de, 0x00668d, 1.5 );
fillLight1.position.set( 2, 1, 1 );
scene.add( fillLight1 );
const directionalLight = new THREE.DirectionalLight( 0xffffff, 2.5 );
directionalLight.position.set( - 5, 25, - 1 );
directionalLight.castShadow = true;
directionalLight.shadow.camera.near = 0.01;
directionalLight.shadow.camera.far = 500;
directionalLight.shadow.camera.right = 30;
directionalLight.shadow.camera.left = - 30;
directionalLight.shadow.camera.top = 30;
directionalLight.shadow.camera.bottom = - 30;
directionalLight.shadow.mapSize.width = 1024;
directionalLight.shadow.mapSize.height = 1024;
directionalLight.shadow.radius = 4;
directionalLight.shadow.bias = - 0.00006;
scene.add( directionalLight );
const container = document.getElementById( 'container' );
const renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setAnimationLoop( animate );
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.VSMShadowMap;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
container.appendChild( renderer.domElement );
const stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.top = '0px';
container.appendChild( stats.domElement );
const GRAVITY = 30;
const NUM_SPHERES = 100;
const SPHERE_RADIUS = 0.2;
const STEPS_PER_FRAME = 5;
const sphereGeometry = new THREE.IcosahedronGeometry( SPHERE_RADIUS, 5 );
const sphereMaterial = new THREE.MeshLambertMaterial( { color: 0xdede8d } );
const spheres = [];
let sphereIdx = 0;
for ( let i = 0; i < NUM_SPHERES; i ++ ) {
const sphere = new THREE.Mesh( sphereGeometry, sphereMaterial );
sphere.castShadow = true;
sphere.receiveShadow = true;
scene.add( sphere );
spheres.push( {
mesh: sphere,
collider: new THREE.Sphere( new THREE.Vector3( 0, - 100, 0 ), SPHERE_RADIUS ),
velocity: new THREE.Vector3()
} );
}
const worldOctree = new Octree();
const playerCollider = new Capsule( new THREE.Vector3( 0, 0.35, 0 ), new THREE.Vector3( 0, 1, 0 ), 0.35 );
const playerVelocity = new THREE.Vector3();
const playerDirection = new THREE.Vector3();
let playerOnFloor = false;
let mouseTime = 0;
const keyStates = {};
const vector1 = new THREE.Vector3();
const vector2 = new THREE.Vector3();
const vector3 = new THREE.Vector3();
document.addEventListener( 'keydown', ( event ) => {
keyStates[ event.code ] = true;
} );
document.addEventListener( 'keyup', ( event ) => {
keyStates[ event.code ] = false;
} );
container.addEventListener( 'mousedown', () => {
document.body.requestPointerLock();
mouseTime = performance.now();
} );
document.addEventListener( 'mouseup', () => {
if ( document.pointerLockElement !== null ) throwBall();
} );
document.body.addEventListener( 'mousemove', ( event ) => {
if ( document.pointerLockElement === document.body ) {
camera.rotation.y -= event.movementX / 500;
camera.rotation.x -= event.movementY / 500;
}
} );
window.addEventListener( 'resize', onWindowResize );
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
function throwBall() {
const sphere = spheres[ sphereIdx ];
camera.getWorldDirection( playerDirection );
sphere.collider.center.copy( playerCollider.end ).addScaledVector( playerDirection, playerCollider.radius * 1.5 );
// throw the ball with more force if we hold the button longer, and if we move forward
const impulse = 15 + 30 * ( 1 - Math.exp( ( mouseTime - performance.now() ) * 0.001 ) );
sphere.velocity.copy( playerDirection ).multiplyScalar( impulse );
sphere.velocity.addScaledVector( playerVelocity, 2 );
sphereIdx = ( sphereIdx + 1 ) % spheres.length;
}
function playerCollisions() {
const result = worldOctree.capsuleIntersect( playerCollider );
playerOnFloor = false;
if ( result ) {
playerOnFloor = result.normal.y > 0;
if ( ! playerOnFloor ) {
playerVelocity.addScaledVector( result.normal, - result.normal.dot( playerVelocity ) );
}
if ( result.depth >= 1e-10 ) {
playerCollider.translate( result.normal.multiplyScalar( result.depth ) );
}
}
}
function updatePlayer( deltaTime ) {
let damping = Math.exp( - 4 * deltaTime ) - 1;
if ( ! playerOnFloor ) {
playerVelocity.y -= GRAVITY * deltaTime;
// small air resistance
damping *= 0.1;
}
playerVelocity.addScaledVector( playerVelocity, damping );
const deltaPosition = playerVelocity.clone().multiplyScalar( deltaTime );
playerCollider.translate( deltaPosition );
playerCollisions();
camera.position.copy( playerCollider.end );
}
function playerSphereCollision( sphere ) {
const center = vector1.addVectors( playerCollider.start, playerCollider.end ).multiplyScalar( 0.5 );
const sphere_center = sphere.collider.center;
const r = playerCollider.radius + sphere.collider.radius;
const r2 = r * r;
// approximation: player = 3 spheres
for ( const point of [ playerCollider.start, playerCollider.end, center ] ) {
const d2 = point.distanceToSquared( sphere_center );
if ( d2 < r2 ) {
const normal = vector1.subVectors( point, sphere_center ).normalize();
const v1 = vector2.copy( normal ).multiplyScalar( normal.dot( playerVelocity ) );
const v2 = vector3.copy( normal ).multiplyScalar( normal.dot( sphere.velocity ) );
playerVelocity.add( v2 ).sub( v1 );
sphere.velocity.add( v1 ).sub( v2 );
const d = ( r - Math.sqrt( d2 ) ) / 2;
sphere_center.addScaledVector( normal, - d );
}
}
}
function spheresCollisions() {
for ( let i = 0, length = spheres.length; i < length; i ++ ) {
const s1 = spheres[ i ];
for ( let j = i + 1; j < length; j ++ ) {
const s2 = spheres[ j ];
const d2 = s1.collider.center.distanceToSquared( s2.collider.center );
const r = s1.collider.radius + s2.collider.radius;
const r2 = r * r;
if ( d2 < r2 ) {
const normal = vector1.subVectors( s1.collider.center, s2.collider.center ).normalize();
const v1 = vector2.copy( normal ).multiplyScalar( normal.dot( s1.velocity ) );
const v2 = vector3.copy( normal ).multiplyScalar( normal.dot( s2.velocity ) );
s1.velocity.add( v2 ).sub( v1 );
s2.velocity.add( v1 ).sub( v2 );
const d = ( r - Math.sqrt( d2 ) ) / 2;
s1.collider.center.addScaledVector( normal, d );
s2.collider.center.addScaledVector( normal, - d );
}
}
}
}
function updateSpheres( deltaTime ) {
spheres.forEach( sphere => {
sphere.collider.center.addScaledVector( sphere.velocity, deltaTime );
const result = worldOctree.sphereIntersect( sphere.collider );
if ( result ) {
sphere.velocity.addScaledVector( result.normal, - result.normal.dot( sphere.velocity ) * 1.5 );
sphere.collider.center.add( result.normal.multiplyScalar( result.depth ) );
} else {
sphere.velocity.y -= GRAVITY * deltaTime;
}
const damping = Math.exp( - 1.5 * deltaTime ) - 1;
sphere.velocity.addScaledVector( sphere.velocity, damping );
playerSphereCollision( sphere );
} );
spheresCollisions();
for ( const sphere of spheres ) {
sphere.mesh.position.copy( sphere.collider.center );
}
}
function getForwardVector() {
camera.getWorldDirection( playerDirection );
playerDirection.y = 0;
playerDirection.normalize();
return playerDirection;
}
function getSideVector() {
camera.getWorldDirection( playerDirection );
playerDirection.y = 0;
playerDirection.normalize();
playerDirection.cross( camera.up );
return playerDirection;
}
function controls( deltaTime ) {
// gives a bit of air control
const speedDelta = deltaTime * ( playerOnFloor ? 25 : 8 );
if ( keyStates[ 'KeyW' ] ) {
playerVelocity.add( getForwardVector().multiplyScalar( speedDelta ) );
}
if ( keyStates[ 'KeyS' ] ) {
playerVelocity.add( getForwardVector().multiplyScalar( - speedDelta ) );
}
if ( keyStates[ 'KeyA' ] ) {
playerVelocity.add( getSideVector().multiplyScalar( - speedDelta ) );
}
if ( keyStates[ 'KeyD' ] ) {
playerVelocity.add( getSideVector().multiplyScalar( speedDelta ) );
}
if ( playerOnFloor ) {
if ( keyStates[ 'Space' ] ) {
playerVelocity.y = 15;
}
}
}
const loader = new GLTFLoader().setPath( './models/gltf/' );
loader.load( 'collision-world.glb', ( gltf ) => {
scene.add( gltf.scene );
worldOctree.fromGraphNode( gltf.scene );
gltf.scene.traverse( child => {
if ( child.isMesh ) {
child.castShadow = true;
child.receiveShadow = true;
if ( child.material.map ) {
child.material.map.anisotropy = 4;
}
}
} );
const helper = new OctreeHelper( worldOctree );
helper.visible = false;
scene.add( helper );
const gui = new GUI( { width: 200 } );
gui.add( { debug: false }, 'debug' )
.onChange( function ( value ) {
helper.visible = value;
} );
} );
function teleportPlayerIfOob() {
if ( camera.position.y <= - 25 ) {
playerCollider.start.set( 0, 0.35, 0 );
playerCollider.end.set( 0, 1, 0 );
playerCollider.radius = 0.35;
camera.position.copy( playerCollider.end );
camera.rotation.set( 0, 0, 0 );
}
}
function animate() {
const deltaTime = Math.min( 0.05, clock.getDelta() ) / STEPS_PER_FRAME;
// we look for collisions in substeps to mitigate the risk of
// an object traversing another too quickly for detection.
for ( let i = 0; i < STEPS_PER_FRAME; i ++ ) {
controls( deltaTime );
updatePlayer( deltaTime );
updateSpheres( deltaTime );
teleportPlayerIfOob();
}
renderer.render( scene, camera );
stats.update();
}
</script>
</body>
</html>

View File

@ -0,0 +1,28 @@
IESNA:LM-63-1995
[TEST]
[MANUFAC] BEGA
[MORE] Copyright LUMCat V
[LUMCAT]
[LUMINAIRE] 50975.6K3 (Preliminary)
[LAMPCAT] LED 7,9W
[LAMP] 321 lm,9 W
TILT=NONE
1 -1 1.0 73 1 1 2 -0.080 0.000 0.000
1.0 1.0 9
0.0 2.5 5.0 7.5 10.0 12.5 15.0 17.5 20.0 22.5 25.0 27.5 30.0
32.5 35.0 37.5 40.0 42.5 45.0 47.5 50.0 52.5 55.0 57.5 60.0 62.5
65.0 67.5 70.0 72.5 75.0 77.5 80.0 82.5 85.0 87.5 90.0 92.5 95.0
97.5 100.0 102.5 105.0 107.5 110.0 112.5 115.0 117.5 120.0 122.5 125.0 127.5
130.0 132.5 135.0 137.5 140.0 142.5 145.0 147.5 150.0 152.5 155.0 157.5 160.0
162.5 165.0 167.5 170.0 172.5 175.0 177.5 180.0
0.0
330.8 333.2 335.2 336.2 336.8 337.1 337.2 336.7
331.4 302.1 238.1 159.9 90.7 51.3 38.9 36.0
34.8 34.2 33.5 32.7 31.6 29.3 25.2 19.8
15.4 12.3 9.9 8.1 6.4 5.1 3.9 3.0
2.1 1.4 0.9 0.4 0.1 0.1 0.1 0.0
0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0

View File

@ -0,0 +1,202 @@
IESNA:LM-63-1995
[TEST]
[MANUFAC] BEGA
[MORE] Copyright LUMCat V
[LUMCAT]
[LUMINAIRE] 84659K4 (Preliminary)
[LAMPCAT] LED 62W
[LAMP] 9600 lm,68 W
TILT=NONE
1 -1 1.0 37 37 1 2 0.240 0.270 0.000
1.0 1.0 68
0.0 2.5 5.0 7.5 10.0 12.5 15.0 17.5 20.0 22.5 25.0 27.5 30.0
32.5 35.0 37.5 40.0 42.5 45.0 47.5 50.0 52.5 55.0 57.5 60.0 62.5
65.0 67.5 70.0 72.5 75.0 77.5 80.0 82.5 85.0 87.5 90.0
90.0 95.0 100.0 105.0 110.0 115.0 120.0 125.0 130.0 135.0 140.0 145.0 150.0
155.0 160.0 165.0 170.0 175.0 180.0 185.0 190.0 195.0 200.0 205.0 210.0 215.0
220.0 225.0 230.0 235.0 240.0 245.0 250.0 255.0 260.0 265.0 270.0
1739.8 1748.5 1754.2 1758.7 1775.5 1813.8 1888.7 2057.8
2268.7 2530.0 2653.0 2727.6 2847.9 2943.6 3012.0 3010.7
2925.3 2695.3 2336.5 1929.2 1501.3 1136.6 808.5 532.9
280.7 122.6 64.3 37.0 26.4 18.3 12.9 8.2
5.1 2.4 0.3 0.0 0.0
1739.8 1748.6 1755.1 1761.4 1779.4 1810.3 1897.9 2049.0
2262.6 2522.0 2673.8 2761.2 2883.7 2983.2 3028.5 3050.3
2988.3 2746.1 2362.9 1967.1 1541.8 1172.7 839.4 558.2
309.8 141.1 80.4 50.8 35.6 24.5 16.9 10.8
7.1 4.0 1.2 0.0 0.0
1739.8 1749.1 1757.4 1767.4 1785.7 1823.3 1899.7 2048.4
2256.2 2498.9 2694.0 2773.9 2844.4 2922.8 2988.7 3031.0
3027.4 2881.6 2567.2 2188.7 1761.0 1375.4 1018.7 699.3
400.3 196.2 113.3 71.3 47.6 32.5 22.1 14.8
9.7 5.2 2.1 0.1 0.0
1739.8 1749.8 1760.2 1773.9 1795.1 1832.1 1900.1 2045.2
2239.3 2482.5 2660.9 2744.7 2861.5 3034.0 3184.8 3305.3
3327.2 3210.1 2878.7 2442.9 1979.1 1517.9 1151.7 808.6
498.3 261.5 152.9 99.1 66.3 42.9 26.9 18.1
11.5 6.5 2.5 0.0 0.0
1739.8 1750.6 1763.0 1780.3 1803.0 1833.0 1894.3 2008.2
2199.0 2444.7 2696.9 2888.1 3087.5 3267.2 3389.1 3432.1
3418.0 3298.7 3024.6 2609.8 2146.7 1683.6 1305.7 944.7
626.2 363.9 214.5 137.1 87.4 54.7 34.9 23.2
15.2 8.4 2.5 0.0 0.0
1739.8 1751.6 1766.6 1786.2 1806.4 1826.5 1860.2 1973.2
2177.0 2478.9 2808.4 3035.9 3161.6 3281.4 3384.3 3448.8
3438.0 3361.0 3159.2 2800.0 2365.0 1909.0 1518.8 1156.2
839.7 537.7 333.0 229.1 153.9 98.5 60.9 38.2
22.2 10.8 3.6 0.0 0.0
1739.8 1752.7 1771.7 1792.3 1802.7 1808.7 1832.9 1970.3
2231.9 2557.8 2848.8 3046.8 3145.9 3246.5 3374.8 3453.3
3465.7 3410.5 3291.8 3011.3 2624.9 2203.5 1792.9 1450.0
1136.5 840.7 606.6 481.8 364.3 246.9 150.7 88.9
48.1 22.0 6.9 0.3 0.0
1739.8 1753.8 1777.0 1796.5 1794.8 1791.9 1841.4 2021.7
2280.7 2542.2 2771.2 2975.5 3121.1 3248.2 3391.1 3511.0
3531.5 3484.4 3392.6 3203.7 2913.7 2548.4 2163.0 1821.9
1540.7 1327.8 1160.0 1022.5 800.9 563.3 370.4 228.6
125.4 51.8 15.1 1.2 0.1
1739.8 1754.8 1781.0 1795.7 1781.8 1792.6 1886.5 2075.7
2261.2 2450.6 2649.5 2876.7 3093.7 3285.2 3444.9 3600.0
3677.9 3661.2 3584.7 3460.9 3265.7 2991.9 2661.8 2352.5
2166.2 2101.0 2042.7 1810.4 1435.5 1092.7 774.0 501.6
276.8 103.1 26.9 2.6 0.0
1739.8 1755.4 1782.8 1790.5 1772.5 1813.9 1953.0 2096.5
2208.0 2334.6 2514.0 2750.9 3033.0 3305.6 3497.4 3638.5
3780.3 3871.8 3869.9 3803.3 3693.5 3534.0 3313.6 3128.5
3083.1 3162.1 3100.2 2758.2 2262.6 1809.0 1341.3 899.8
500.0 187.6 46.7 4.3 0.0
1739.8 1755.7 1782.8 1783.3 1768.3 1848.9 2006.2 2097.5
2154.6 2236.7 2392.4 2608.1 2899.4 3231.2 3514.3 3713.1
3856.4 3983.7 4070.7 4087.8 4080.7 4098.0 4077.7 4091.0
4192.6 4272.4 4144.6 3754.2 3197.0 2607.4 2005.6 1386.9
778.1 293.9 79.5 7.5 0.0
1739.8 1755.4 1782.2 1774.3 1772.9 1890.9 2037.3 2090.6
2116.9 2172.1 2286.5 2481.7 2751.7 3076.9 3413.4 3703.8
3904.5 4053.3 4183.4 4293.1 4376.2 4499.1 4733.8 5032.2
5271.9 5261.5 5017.0 4611.3 4064.9 3373.1 2651.0 1878.4
1053.3 386.3 107.3 13.3 0.0
1739.8 1754.8 1781.2 1763.5 1782.8 1925.4 2052.3 2085.7
2109.9 2148.2 2230.5 2373.6 2592.0 2872.4 3193.1 3524.8
3813.4 4054.3 4243.0 4393.5 4537.7 4758.7 5191.2 5738.1
6099.8 6004.9 5742.4 5359.5 4807.0 4117.3 3298.1 2376.1
1374.9 499.6 137.2 19.0 0.1
1739.8 1754.2 1779.0 1753.3 1795.5 1954.6 2057.7 2081.8
2106.0 2140.8 2198.8 2293.9 2431.6 2638.7 2872.8 3146.3
3442.6 3729.7 4009.0 4277.9 4534.4 4841.2 5334.3 5971.2
6458.4 6427.8 6214.9 5945.5 5405.8 4726.0 3924.8 2910.2
1731.6 647.9 172.7 24.4 0.2
1739.8 1753.7 1775.5 1747.2 1811.0 1975.6 2058.3 2078.1
2100.7 2143.1 2192.8 2252.8 2332.3 2443.4 2578.3 2735.2
2900.4 3095.8 3322.8 3611.9 3979.1 4473.8 5057.7 5592.9
6039.2 6254.0 6274.6 6086.8 5637.3 5050.5 4315.5 3337.6
2052.6 777.2 188.6 27.0 0.1
1739.8 1753.5 1772.1 1743.4 1825.3 1988.7 2055.7 2075.2
2104.0 2147.1 2182.2 2230.3 2278.6 2338.0 2407.8 2488.4
2561.1 2649.0 2758.2 2933.2 3221.1 3689.9 4315.9 4887.8
5217.3 5403.3 5578.3 5593.9 5285.6 4859.4 4234.2 3286.5
2092.1 833.6 192.9 25.7 0.1
1739.8 1753.4 1770.3 1741.1 1834.5 1998.4 2060.2 2074.5
2102.5 2137.9 2177.5 2212.0 2239.4 2273.8 2324.5 2374.0
2422.9 2478.7 2555.6 2672.0 2890.8 3264.9 3773.6 4189.1
4395.8 4538.9 4636.8 4609.0 4417.1 4064.6 3531.1 2726.5
1743.9 712.5 163.7 22.3 0.0
1739.8 1753.2 1769.9 1740.7 1844.3 2003.6 2059.3 2074.3
2101.9 2140.5 2174.2 2200.8 2225.4 2242.9 2274.6 2316.0
2375.6 2439.1 2522.0 2651.4 2890.5 3269.0 3713.2 4051.6
4168.6 4152.3 4098.0 3932.4 3645.8 3237.7 2704.4 2024.6
1266.2 505.7 118.2 17.2 0.2
1739.8 1752.7 1768.9 1739.4 1848.0 2005.0 2057.8 2076.1
2105.3 2138.8 2172.1 2193.3 2208.4 2232.2 2264.7 2306.5
2370.7 2456.0 2566.0 2716.8 2996.1 3411.2 3879.6 4247.8
4401.7 4315.5 4107.0 3814.8 3470.1 3017.1 2489.7 1881.1
1180.9 457.8 104.1 14.4 0.2
1739.8 1751.9 1766.5 1736.6 1841.4 1999.8 2051.3 2064.5
2091.3 2125.1 2159.2 2184.1 2191.0 2207.5 2233.7 2281.7
2347.3 2428.6 2529.8 2693.0 2968.7 3391.4 3882.6 4271.4
4468.1 4373.3 4162.7 3880.4 3514.7 2965.6 2326.5 1745.4
1081.3 418.7 96.3 13.4 1.0
1739.8 1750.6 1763.4 1733.9 1826.0 1984.8 2039.0 2046.3
2062.0 2088.3 2113.7 2133.3 2141.8 2161.8 2185.5 2224.5
2273.4 2341.5 2413.9 2555.7 2779.9 3119.7 3534.7 3883.8
4018.8 3886.8 3657.9 3358.8 2911.8 2312.5 1679.0 1171.2
650.0 219.8 51.6 7.1 0.1
1739.8 1748.8 1761.1 1728.9 1802.9 1961.8 2019.1 2024.3
2035.5 2051.2 2063.9 2066.3 2069.3 2082.4 2104.1 2127.4
2161.4 2207.3 2257.9 2335.9 2458.5 2688.2 2954.9 3149.0
3185.9 2991.3 2712.6 2412.9 1981.8 1449.2 948.1 615.6
320.2 97.1 23.3 3.3 0.2
1739.8 1746.5 1759.4 1723.6 1777.0 1932.0 1998.8 1995.7
1994.2 2001.1 2002.5 1998.9 1987.0 1981.7 1985.9 1995.0
1999.0 1996.4 1977.9 1933.3 1936.8 2021.7 2181.6 2300.9
2256.1 2053.8 1778.0 1471.1 1135.3 799.3 514.5 323.3
164.0 54.3 15.1 1.6 0.1
1739.8 1744.0 1757.0 1721.8 1748.6 1894.2 1976.7 1971.4
1956.9 1944.0 1936.1 1924.3 1898.2 1872.1 1842.7 1794.3
1718.2 1646.3 1576.3 1483.8 1417.8 1401.1 1440.9 1460.8
1416.0 1272.8 1065.0 836.9 632.9 445.5 286.8 178.6
94.5 37.6 12.6 1.2 0.3
1739.8 1741.7 1753.4 1722.7 1722.0 1842.1 1944.5 1941.7
1918.0 1891.7 1868.5 1851.0 1816.6 1752.6 1640.2 1519.0
1434.8 1324.8 1200.9 1070.9 944.0 869.6 846.3 831.3
790.4 737.8 618.3 463.7 343.7 247.9 165.4 107.8
60.6 28.2 10.6 1.1 0.1
1739.8 1739.8 1748.9 1724.2 1698.3 1788.1 1901.0 1907.5
1882.0 1856.4 1822.8 1785.5 1710.2 1563.1 1442.7 1331.9
1185.6 1054.8 883.0 738.7 614.5 520.0 465.7 457.4
447.6 406.0 346.2 275.1 205.1 152.5 114.7 80.8
48.6 23.4 9.0 1.0 0.0
1739.8 1738.1 1743.9 1725.3 1681.1 1728.5 1845.0 1873.7
1849.8 1813.1 1773.9 1679.5 1531.6 1417.5 1288.7 1126.7
980.5 804.6 659.8 523.7 411.4 329.1 333.5 349.1
341.6 296.6 255.7 218.4 169.0 130.9 111.9 85.7
54.1 24.0 7.9 0.6 0.4
1739.8 1736.4 1738.6 1726.7 1675.0 1674.9 1765.5 1832.5
1821.7 1779.7 1679.0 1525.9 1406.7 1277.8 1119.9 973.6
776.8 629.6 489.6 373.0 292.8 297.0 338.4 373.3
360.6 319.8 289.8 241.5 177.2 139.2 116.7 85.4
50.4 21.6 5.9 0.6 0.2
1739.8 1734.9 1733.0 1726.6 1678.5 1639.2 1676.6 1759.3
1781.8 1728.1 1560.6 1415.1 1296.8 1138.6 994.1 791.0
639.3 488.9 362.5 271.5 278.4 342.3 430.8 470.2
433.8 400.4 375.7 296.9 194.9 131.9 99.3 72.7
46.8 21.9 6.2 0.3 0.1
1739.8 1733.4 1726.2 1721.4 1683.0 1628.9 1608.0 1656.3
1704.4 1642.1 1461.3 1338.6 1178.5 1036.7 842.3 665.5
509.1 370.1 248.1 237.4 299.4 424.0 538.7 550.9
505.8 511.8 478.4 333.3 185.0 107.6 77.9 62.2
39.9 17.1 4.6 0.4 0.7
1739.8 1731.9 1718.6 1711.6 1685.5 1634.5 1578.3 1566.8
1578.6 1503.9 1391.7 1255.9 1088.7 918.0 716.7 559.7
401.4 255.8 187.5 211.9 297.5 428.9 500.2 478.8
461.1 488.9 417.1 244.3 117.7 65.4 46.5 33.1
19.9 8.9 2.5 0.5 0.7
1739.8 1730.4 1712.0 1698.4 1680.5 1643.5 1584.6 1522.5
1448.6 1328.1 1281.5 1153.1 1024.0 806.2 630.3 450.3
309.5 184.1 158.9 180.9 253.0 347.8 373.9 338.1
321.6 318.9 245.2 135.1 69.1 41.8 29.2 21.0
13.5 6.3 1.6 0.2 0.5
1739.8 1728.7 1707.5 1686.4 1669.4 1640.1 1597.6 1534.1
1390.3 1215.9 1131.7 1016.4 911.0 711.6 558.9 394.2
239.7 152.3 141.0 158.0 216.4 290.9 300.0 255.6
228.7 224.1 172.4 88.1 36.6 18.1 12.7 8.7
5.8 3.1 0.9 0.3 0.3
1739.8 1727.3 1704.7 1677.5 1654.2 1625.8 1594.5 1538.4
1391.4 1223.5 1086.3 937.0 788.1 619.2 476.3 340.5
195.2 136.2 124.3 136.7 186.6 250.5 253.2 209.8
198.9 210.2 154.6 57.6 19.1 7.5 2.6 0.4
0.3 0.1 0.3 0.1 0.1
1739.8 1726.1 1702.3 1671.4 1641.1 1613.2 1577.6 1520.4
1376.1 1245.4 1102.8 950.4 762.0 587.5 432.1 303.3
173.0 126.7 111.5 113.1 144.8 183.9 180.1 150.1
148.3 152.5 110.7 58.0 29.4 14.6 6.5 3.5
2.2 1.2 0.3 0.2 0.3
1739.8 1725.3 1699.4 1669.8 1633.6 1599.7 1563.7 1498.5
1345.3 1223.2 1074.8 935.8 757.2 592.9 431.9 290.9
166.9 125.1 107.4 101.7 108.6 116.4 100.5 77.0
69.3 66.1 49.9 31.9 17.8 9.2 6.1 5.1
3.8 2.4 0.4 0.2 0.1
1739.8 1725.1 1697.8 1670.7 1629.3 1595.2 1555.4 1496.7
1324.7 1209.7 1067.7 938.0 736.8 576.8 423.8 288.7
163.9 124.3 105.5 94.1 90.2 83.9 64.6 44.4
32.4 26.8 19.6 12.9 7.9 4.1 2.8 2.2
1.1 0.2 0.2 0.1 0.2

View File

@ -0,0 +1,87 @@
IESNA:LM-63-1995
[TEST]
[MANUFAC] BEGA
[MORE] Copyright LUMCat V
[LUMCAT]
[LUMINAIRE] 50899.2K3
[LAMPCAT] LED 11,5W
[LAMP] 1221 lm,14 W
TILT=NONE
1 -1 1.0 19 24 1 2 -0.120 0.000 0.000
1.0 1.0 14
0.0 5.0 10.0 15.0 20.0 25.0 30.0 35.0 40.0 45.0 50.0 55.0 60.0
65.0 70.0 75.0 80.0 85.0 90.0
0.0 15.0 30.0 45.0 60.0 75.0 90.0 105.0 120.0 135.0 150.0 165.0 180.0
195.0 210.0 225.0 240.0 255.0 270.0 285.0 300.0 315.0 330.0 345.0
2160.3 1896.0 1447.3 1122.5 882.7 695.9 546.9 529.2
284.6 41.6 27.5 23.8 21.4 20.2 18.5 10.0
4.9 1.7 0.0
2160.3 1896.0 1447.3 1122.5 882.7 695.9 546.9 529.2
284.6 41.6 27.5 23.8 21.4 20.2 18.5 10.0
4.9 1.7 0.0
2160.3 1896.0 1447.3 1122.5 882.7 695.9 546.9 529.2
284.6 41.6 27.5 23.8 21.4 20.2 18.5 10.0
4.9 1.7 0.0
2160.3 1896.0 1447.3 1122.5 882.7 695.9 546.9 529.2
284.6 41.6 27.5 23.8 21.4 20.2 18.5 10.0
4.9 1.7 0.0
2160.3 1896.0 1447.3 1122.5 882.7 695.9 546.9 529.2
284.6 41.6 27.5 23.8 21.4 20.2 18.5 10.0
4.9 1.7 0.0
2160.3 1896.0 1447.3 1122.5 882.7 695.9 546.9 529.2
284.6 41.6 27.5 23.8 21.4 20.2 18.5 10.0
4.9 1.7 0.0
2160.3 1896.0 1447.3 1122.5 882.7 695.9 546.9 529.2
284.6 41.6 27.5 23.8 21.4 20.2 18.5 10.0
4.9 1.7 0.0
2160.3 1896.0 1447.3 1122.5 882.7 695.9 546.9 529.2
284.6 41.6 27.5 23.8 21.4 20.2 18.5 10.0
4.9 1.7 0.0
2160.3 1896.0 1447.3 1122.5 882.7 695.9 546.9 529.2
284.6 41.6 27.5 23.8 21.4 20.2 18.5 10.0
4.9 1.7 0.0
2160.3 1896.0 1447.3 1122.5 882.7 695.9 546.9 529.2
284.6 41.6 27.5 23.8 21.4 20.2 18.5 10.0
4.9 1.7 0.0
2160.3 1896.0 1447.3 1122.5 882.7 695.9 546.9 529.2
284.6 41.6 27.5 23.8 21.4 20.2 18.5 10.0
4.9 1.7 0.0
2160.3 1896.0 1447.3 1122.5 882.7 695.9 546.9 529.2
284.6 41.6 27.5 23.8 21.4 20.2 18.5 10.0
4.9 1.7 0.0
2160.3 1896.0 1447.3 1122.5 882.7 695.9 546.9 529.2
284.6 41.6 27.5 23.8 21.4 20.2 18.5 10.0
4.9 1.7 0.0
2160.3 1896.0 1447.3 1122.5 882.7 695.9 546.9 529.2
284.6 41.6 27.5 23.8 21.4 20.2 18.5 10.0
4.9 1.7 0.0
2160.3 1896.0 1447.3 1122.5 882.7 695.9 546.9 529.2
284.6 41.6 27.5 23.8 21.4 20.2 18.5 10.0
4.9 1.7 0.0
2160.3 1896.0 1447.3 1122.5 882.7 695.9 546.9 529.2
284.6 41.6 27.5 23.8 21.4 20.2 18.5 10.0
4.9 1.7 0.0
2160.3 1896.0 1447.3 1122.5 882.7 695.9 546.9 529.2
284.6 41.6 27.5 23.8 21.4 20.2 18.5 10.0
4.9 1.7 0.0
2160.3 1896.0 1447.3 1122.5 882.7 695.9 546.9 529.2
284.6 41.6 27.5 23.8 21.4 20.2 18.5 10.0
4.9 1.7 0.0
2160.3 1896.0 1447.3 1122.5 882.7 695.9 546.9 529.2
284.6 41.6 27.5 23.8 21.4 20.2 18.5 10.0
4.9 1.7 0.0
2160.3 1896.0 1447.3 1122.5 882.7 695.9 546.9 529.2
284.6 41.6 27.5 23.8 21.4 20.2 18.5 10.0
4.9 1.7 0.0
2160.3 1896.0 1447.3 1122.5 882.7 695.9 546.9 529.2
284.6 41.6 27.5 23.8 21.4 20.2 18.5 10.0
4.9 1.7 0.0
2160.3 1896.0 1447.3 1122.5 882.7 695.9 546.9 529.2
284.6 41.6 27.5 23.8 21.4 20.2 18.5 10.0
4.9 1.7 0.0
2160.3 1896.0 1447.3 1122.5 882.7 695.9 546.9 529.2
284.6 41.6 27.5 23.8 21.4 20.2 18.5 10.0
4.9 1.7 0.0
2160.3 1896.0 1447.3 1122.5 882.7 695.9 546.9 529.2
284.6 41.6 27.5 23.8 21.4 20.2 18.5 10.0
4.9 1.7 0.0

View File

@ -0,0 +1,30 @@
IESNA:LM-63-2002
[TEST] LightLab International Test Report No. LL20433-R01-S1
[MANUFAC] Efficient Lighting Systems,
[MORE] Brunswick. VIC. 3056.
[LUMINAIRE] Efficient Lighting Systems LED Display Track Light. Product ID: DT106.XTM10.N.94.61.
[MORE] Cylindrical cast aluminium body and rectangular gear housing, 150 x 165 x 140 mm deep.
[MORE] Recessed glass lens. Luminous opening of 92 mm diameter. Specular multifaceted “15 degree” reflector
[MORE] about LED. One "Xicato XTM19954030CCA 0D" LED centred 60 mm above L/O.
[MORE] One Harvard Technology CL40-1050S2D 220-240V 50/60Hz electronic driver set to “1050mA”
[OTHER] Absolute test - lamp lumens value set to -1
[MORE] NA conventions used for C0 plane alignment and C-plane rotation direction.
[MORE] The sample was tested at a distance of 8m.
[MORE] This IES file created by LightLab/LSA Report program version 3.803a.
[DATE] This file created: Tuesday, 26 September 2017 4:14:01 PM
[LUMCAT] DT106.XTM10.N.94.61
[TESTLAB] LightLab International
[ISSUEDATE] 26/09/2017
TILT=NONE
1 -1 1.498 181 1 1 2 -0.092 -0.092 0
1 1 39
0 0.5 1 1.5 2 2.5 3 3.5 4 4.5 5 5.5 6 6.5 7 7.5 8 8.5 9 9.5 10 10.5 11 11.5 12 12.5 13 13.5 14 14.5 15 15.5 16 16.5 17 17.5 18 18.5 19 19.5 20 20.5 21 21.5 22 22.5 23 23.5 24 24.5 25 25.5 26 26.5 27 27.5 28 28.5 29 29.5 30 30.5 31 31.5 32 32.5 33 33.5 34
34.5 35 35.5 36 36.5 37 37.5 38 38.5 39 39.5 40 40.5 41 41.5 42 42.5 43 43.5 44 44.5 45 45.5 46 46.5 47 47.5 48 48.5 49 49.5 50 50.5 51 51.5 52 52.5 53 53.5 54 54.5 55 55.5 56 56.5 57 57.5 58 58.5 59 59.5 60 60.5 61 61.5 62 62.5 63 63.5 64 64.5 65 65.5 66
66.5 67 67.5 68 68.5 69 69.5 70 70.5 71 71.5 72 72.5 73 73.5 74 74.5 75 75.5 76 76.5 77 77.5 78 78.5 79 79.5 80 80.5 81 81.5 82 82.5 83 83.5 84 84.5 85 85.5 86 86.5 87 87.5 88 88.5 89 89.5 90
0
9769.798 9766.099 9733.675 9662.947 9575.834 9442.665 9280.229 9118.304 8886.575 8613.292 8341.374 7954.059 7478.424 7037.077 6451.128 5813.887 5183.818 4670.251 4124.621 3645.885 3310.188 2945.517 2631.518 2403.866 2164.416 1958.844 1816.224 1663.451
1524.167 1419.652 1306.115 1202.535 1115.449 1047.913 981.195 921.649 876.95 831.168 790.026 760.98 730.924 706.114 687.086 667.788 650.355 637.35 624.922 614.813 604.786 598.047 591.914 586.282 582.085 577.621 574.641 572.333 569.62 567.557 565.539
563.204 559.745 553.524 544.477 534.858 527.80 521.81 511.364 498.373 483.43 463.707 439.765 418.717 389.445 360.335 336.512 304.879 270.151 238.945 213.729 181.877 150.681 128.811 105.391 82.645 66.833 52.248 42.075 37.054 34.538 33.947 33.94 34.272
34.798 35.093 35.396 35.972 36.688 37.212 38.144 38.89 40.057 40.797 41.463 40.917 39.831 37.592 34.888 32.145 28.847 25.431 22.88 20.927 19.271 18.25 17.761 17.155 16.807 16.327 16.267 15.817 15.558 14.90 14.597 14.109 13.879 13.68 13.192 13.221 12.815
12.386 12.512 12.193 12.364 11.876 11.735 11.684 11.387 11.248 11.307 11.121 10.967 10.93 10.766 10.818 10.663 10.766 10.522 10.745 10.486 10.56 10.308 10.079 10.064 9.99 9.813 9.724 9.354 9.303 9.111 8.586 8.408 8.061 7.779 7.129 6.693 6.123 5.287 4.541
3.646 3.513 2.833 2.678 2.33 1.916 1.709 1.178 0.814 0.502 0.396 0.059 0.00

View File

@ -0,0 +1,3 @@
Profiles from the [IES Library](https://ieslibrary.com/en/home) website.
New profiles can be created via [CNDL](https://cndl.io/).

View File

@ -0,0 +1,429 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>three.js examples</title>
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link rel="shortcut icon" href="../files/favicon_white.ico" media="(prefers-color-scheme: dark)"/>
<link rel="shortcut icon" href="../files/favicon.ico" media="(prefers-color-scheme: light)" />
<link rel="stylesheet" type="text/css" href="../files/main.css">
</head>
<body>
<div id="panel">
<div id="header">
<h1><a href="https://threejs.org">three.js</a></h1>
<div id="sections">
<a href="../docs/index.html#manual/introduction/Creating-a-scene">docs</a>
<span class="selected">examples</span>
</div>
<div id="expandButton"></div>
</div>
<div id="panelScrim"></div>
<div id="contentWrapper">
<div id="inputWrapper">
<input placeholder="" type="text" id="filterInput" autocorrect="off" autocapitalize="off" spellcheck="false" />
<div id="clearSearchButton"></div>
</div>
<div id="content">
<img id="previewsToggler" src="./files/thumbnails.svg" width="20" height="20" />
</div>
</div>
</div>
<iframe id="viewer" name="viewer" allow="fullscreen; xr-spatial-tracking;"></iframe>
<a id="button" target="_blank"><img src="../files/ic_code_black_24dp.svg"></a>
<script>
const panel = document.getElementById( 'panel' );
const content = document.getElementById( 'content' );
const viewer = document.getElementById( 'viewer' );
const filterInput = document.getElementById( 'filterInput' );
const clearSearchButton = document.getElementById( 'clearSearchButton' );
const expandButton = document.getElementById( 'expandButton' );
const viewSrcButton = document.getElementById( 'button' );
const panelScrim = document.getElementById( 'panelScrim' );
const previewsToggler = document.getElementById( 'previewsToggler' );
const sectionLink = document.querySelector( '#sections > a' );
const sectionDefaultHref = sectionLink.href;
const links = {};
const validRedirects = new Map();
const container = document.createElement( 'div' );
let selected = null;
init();
async function init() {
content.appendChild( container );
viewSrcButton.style.display = 'none';
const files = await ( await fetch( 'files.json' ) ).json();
const tags = await ( await fetch( 'tags.json' ) ).json();
for ( const key in files ) {
const category = files[ key ];
const header = document.createElement( 'h2' );
header.textContent = key;
header.setAttribute( 'data-category', key );
container.appendChild( header );
for ( let i = 0; i < category.length; i ++ ) {
const file = category[ i ];
const link = createLink( file, tags[ file ] );
container.appendChild( link );
links[ file ] = link;
validRedirects.set( file, file + '.html' );
}
}
if ( window.location.hash !== '' ) {
const file = window.location.hash.substring( 1 );
// use a predefined map of redirects to avoid untrusted URL redirection due to user-provided value
if ( validRedirects.has( file ) === true ) {
content.querySelector( `a[href="${ file }.html"]` ).scrollIntoView();
selectFile( file );
viewer.src = validRedirects.get( file );
viewer.style.display = 'unset';
}
}
if ( viewer.src === '' ) {
viewer.srcdoc = document.getElementById( 'PlaceholderHTML' ).innerHTML;
viewer.style.display = 'unset';
}
filterInput.value = extractQuery();
if ( filterInput.value !== '' ) {
panel.classList.add( 'searchFocused' );
updateFilter( files, tags );
} else {
updateLink( '' );
}
// Events
filterInput.onfocus = function ( ) {
panel.classList.add( 'searchFocused' );
};
filterInput.onblur = function ( ) {
if ( filterInput.value === '' ) {
panel.classList.remove( 'searchFocused' );
}
};
clearSearchButton.onclick = function ( ) {
filterInput.value = '';
updateFilter( files, tags );
filterInput.focus();
};
filterInput.addEventListener( 'input', function () {
updateFilter( files, tags );
} );
expandButton.addEventListener( 'click', function ( event ) {
event.preventDefault();
panel.classList.toggle( 'open' );
} );
panelScrim.onclick = function ( event ) {
event.preventDefault();
panel.classList.toggle( 'open' );
};
previewsToggler.onclick = function ( event ) {
event.preventDefault();
content.classList.toggle( 'minimal' );
};
// iOS iframe auto-resize workaround
if ( /(iPad|iPhone|iPod)/g.test( navigator.userAgent ) ) {
viewer.style.width = getComputedStyle( viewer ).width;
viewer.style.height = getComputedStyle( viewer ).height;
viewer.setAttribute( 'scrolling', 'no' );
}
}
function createLink( file, tags ) {
const external = Array.isArray( tags ) && tags.includes( 'external' ) ? ' <span class="tag">external</span>' : '';
const template = `
<div class="card">
<a href="${ file }.html" target="viewer">
<div class="cover">
<img src="screenshots/${ file }.jpg" loading="lazy" width="400" />
</div>
<div class="title">${ getName( file ) }${ external }</div>
</a>
</div>
`;
const link = createElementFromHTML( template );
link.querySelector( 'a[target="viewer"]' ).addEventListener( 'click', function ( event ) {
if ( event.button !== 0 || event.ctrlKey || event.altKey || event.metaKey ) return;
selectFile( file );
} );
return link;
}
function selectFile( file ) {
if ( selected !== null ) links[ selected ].classList.remove( 'selected' );
links[ file ].classList.add( 'selected' );
window.location.hash = file;
viewer.focus();
viewer.style.display = 'unset';
panel.classList.remove( 'open' );
selected = file;
// Reveal "View source" button and set attributes to this example
viewSrcButton.style.display = '';
viewSrcButton.href = 'https://github.com/mrdoob/three.js/blob/master/examples/' + selected + '.html';
viewSrcButton.title = 'View source code for ' + getName( selected ) + ' on GitHub';
}
function escapeRegExp( string ) {
string = string.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' ); // https://stackoverflow.com/a/6969486/5250847
return '(?=.*' + string.split( ' ' ).join( ')(?=.*' ) + ')'; // match all words, in any order
}
function updateFilter( files, tags ) {
let v = filterInput.value.trim();
v = v.replace( /\s+/gi, ' ' ); // replace multiple whitespaces with a single one
if ( v !== '' ) {
window.history.replaceState( {}, '', '?q=' + v + window.location.hash );
} else {
window.history.replaceState( {}, '', window.location.pathname + window.location.hash );
}
const exp = new RegExp( escapeRegExp( v ), 'gi' );
for ( const key in files ) {
const section = files[ key ];
for ( let i = 0; i < section.length; i ++ ) {
filterExample( section[ i ], exp, tags );
}
}
layoutList( files );
updateLink( v );
}
function updateLink( search ) {
// update docs link
if ( search ) {
const link = sectionLink.href.split( /[?#]/ )[ 0 ];
sectionLink.href = `${link}?q=${search}`;
} else {
sectionLink.href = sectionDefaultHref;
}
}
function filterExample( file, exp, tags ) {
const link = links[ file ];
if ( file in tags ) file += ' ' + tags[ file ].join( ' ' );
const res = file.replace( /_+/g, ' ' ).match( exp );
if ( res && res.length > 0 ) {
link.classList.remove( 'hidden' );
} else {
link.classList.add( 'hidden' );
}
}
function getName( file ) {
const name = file.split( '_' );
name.shift();
return name.join( ' / ' );
}
function layoutList( files ) {
for ( const key in files ) {
let collapsed = true;
const section = files[ key ];
for ( let i = 0; i < section.length; i ++ ) {
const file = section[ i ];
if ( links[ file ].classList.contains( 'hidden' ) === false ) {
collapsed = false;
break;
}
}
const element = document.querySelector( 'h2[data-category="' + key + '"]' );
if ( collapsed ) {
element.classList.add( 'hidden' );
} else {
element.classList.remove( 'hidden' );
}
}
}
function extractQuery() {
const search = window.location.search;
if ( search.indexOf( '?q=' ) !== - 1 ) {
return decodeURI( search.slice( 3 ) );
}
return '';
}
function createElementFromHTML( htmlString ) {
const div = document.createElement( 'div' );
div.innerHTML = htmlString.trim();
return div.firstChild;
}
</script>
<template id="PlaceholderHTML">
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>three.js examples</title>
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link rel="stylesheet" type="text/css" href="../files/main.css">
<style>
html, body {
height: 100%;
}
body {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
user-select: none;
}
</style>
</head>
<body>
Select an example from the sidebar
</body>
</html>
</template>
</body>
</html>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,28 @@
import { MOUSE, TOUCH } from '/static/three/build/three.module.js';
import { OrbitControls } from './OrbitControls.js';
// MapControls performs orbiting, dollying (zooming), and panning.
// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
//
// Orbit - right mouse, or left mouse + ctrl/meta/shiftKey / touch: two-finger rotate
// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
// Pan - left mouse, or arrow keys / touch: one-finger move
class MapControls extends OrbitControls {
constructor( object, domElement ) {
super( object, domElement );
this.screenSpacePanning = false; // pan orthogonal to world-space direction camera.up
this.mouseButtons = { LEFT: MOUSE.PAN, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.ROTATE };
this.touches = { ONE: TOUCH.PAN, TWO: TOUCH.DOLLY_ROTATE };
}
}
export { MapControls };

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,292 @@
import {
FloatType,
HalfFloatType,
UnsignedByteType,
RGBAFormat,
RGFormat,
RGIntegerFormat,
RedFormat,
RedIntegerFormat,
NoColorSpace,
LinearSRGBColorSpace,
SRGBColorSpace,
DataTexture,
REVISION,
} from '/static/three/build/three.module.js';
import {
write,
KTX2Container,
KHR_DF_CHANNEL_RGBSDA_ALPHA,
KHR_DF_CHANNEL_RGBSDA_BLUE,
KHR_DF_CHANNEL_RGBSDA_GREEN,
KHR_DF_CHANNEL_RGBSDA_RED,
KHR_DF_MODEL_RGBSDA,
KHR_DF_PRIMARIES_BT709,
KHR_DF_PRIMARIES_UNSPECIFIED,
KHR_DF_SAMPLE_DATATYPE_FLOAT,
KHR_DF_SAMPLE_DATATYPE_LINEAR,
KHR_DF_SAMPLE_DATATYPE_SIGNED,
KHR_DF_TRANSFER_LINEAR,
KHR_DF_TRANSFER_SRGB,
VK_FORMAT_R16_SFLOAT,
VK_FORMAT_R16G16_SFLOAT,
VK_FORMAT_R16G16B16A16_SFLOAT,
VK_FORMAT_R32_SFLOAT,
VK_FORMAT_R32G32_SFLOAT,
VK_FORMAT_R32G32B32A32_SFLOAT,
VK_FORMAT_R8_SRGB,
VK_FORMAT_R8_UNORM,
VK_FORMAT_R8G8_SRGB,
VK_FORMAT_R8G8_UNORM,
VK_FORMAT_R8G8B8A8_SRGB,
VK_FORMAT_R8G8B8A8_UNORM,
} from '../libs/ktx-parse.module.js';
const VK_FORMAT_MAP = {
[ RGBAFormat ]: {
[ FloatType ]: {
[ NoColorSpace ]: VK_FORMAT_R32G32B32A32_SFLOAT,
[ LinearSRGBColorSpace ]: VK_FORMAT_R32G32B32A32_SFLOAT,
},
[ HalfFloatType ]: {
[ NoColorSpace ]: VK_FORMAT_R16G16B16A16_SFLOAT,
[ LinearSRGBColorSpace ]: VK_FORMAT_R16G16B16A16_SFLOAT,
},
[ UnsignedByteType ]: {
[ NoColorSpace ]: VK_FORMAT_R8G8B8A8_UNORM,
[ LinearSRGBColorSpace ]: VK_FORMAT_R8G8B8A8_UNORM,
[ SRGBColorSpace ]: VK_FORMAT_R8G8B8A8_SRGB,
},
},
[ RGFormat ]: {
[ FloatType ]: {
[ NoColorSpace ]: VK_FORMAT_R32G32_SFLOAT,
[ LinearSRGBColorSpace ]: VK_FORMAT_R32G32_SFLOAT,
},
[ HalfFloatType ]: {
[ NoColorSpace ]: VK_FORMAT_R16G16_SFLOAT,
[ LinearSRGBColorSpace ]: VK_FORMAT_R16G16_SFLOAT,
},
[ UnsignedByteType ]: {
[ NoColorSpace ]: VK_FORMAT_R8G8_UNORM,
[ LinearSRGBColorSpace ]: VK_FORMAT_R8G8_UNORM,
[ SRGBColorSpace ]: VK_FORMAT_R8G8_SRGB,
},
},
[ RedFormat ]: {
[ FloatType ]: {
[ NoColorSpace ]: VK_FORMAT_R32_SFLOAT,
[ LinearSRGBColorSpace ]: VK_FORMAT_R32_SFLOAT,
},
[ HalfFloatType ]: {
[ NoColorSpace ]: VK_FORMAT_R16_SFLOAT,
[ LinearSRGBColorSpace ]: VK_FORMAT_R16_SFLOAT,
},
[ UnsignedByteType ]: {
[ NoColorSpace ]: VK_FORMAT_R8_UNORM,
[ LinearSRGBColorSpace ]: VK_FORMAT_R8_UNORM,
[ SRGBColorSpace ]: VK_FORMAT_R8_SRGB,
},
},
};
const KHR_DF_CHANNEL_MAP = {
0: KHR_DF_CHANNEL_RGBSDA_RED,
1: KHR_DF_CHANNEL_RGBSDA_GREEN,
2: KHR_DF_CHANNEL_RGBSDA_BLUE,
3: KHR_DF_CHANNEL_RGBSDA_ALPHA,
};
const ERROR_INPUT = 'THREE.KTX2Exporter: Supported inputs are DataTexture, Data3DTexture, or WebGLRenderer and WebGLRenderTarget.';
const ERROR_FORMAT = 'THREE.KTX2Exporter: Supported formats are RGBAFormat, RGFormat, or RedFormat.';
const ERROR_TYPE = 'THREE.KTX2Exporter: Supported types are FloatType, HalfFloatType, or UnsignedByteType."';
const ERROR_COLOR_SPACE = 'THREE.KTX2Exporter: Supported color spaces are SRGBColorSpace (UnsignedByteType only), LinearSRGBColorSpace, or NoColorSpace.';
export class KTX2Exporter {
parse( arg1, arg2 ) {
let texture;
if ( arg1.isDataTexture || arg1.isData3DTexture ) {
texture = arg1;
} else if ( arg1.isWebGLRenderer && arg2.isWebGLRenderTarget ) {
texture = toDataTexture( arg1, arg2 );
} else {
throw new Error( ERROR_INPUT );
}
if ( VK_FORMAT_MAP[ texture.format ] === undefined ) {
throw new Error( ERROR_FORMAT );
}
if ( VK_FORMAT_MAP[ texture.format ][ texture.type ] === undefined ) {
throw new Error( ERROR_TYPE );
}
if ( VK_FORMAT_MAP[ texture.format ][ texture.type ][ texture.colorSpace ] === undefined ) {
throw new Error( ERROR_COLOR_SPACE );
}
//
const array = texture.image.data;
const channelCount = getChannelCount( texture );
const container = new KTX2Container();
container.vkFormat = VK_FORMAT_MAP[ texture.format ][ texture.type ][ texture.colorSpace ];
container.typeSize = array.BYTES_PER_ELEMENT;
container.pixelWidth = texture.image.width;
container.pixelHeight = texture.image.height;
if ( texture.isData3DTexture ) {
container.pixelDepth = texture.image.depth;
}
//
const basicDesc = container.dataFormatDescriptor[ 0 ];
basicDesc.colorModel = KHR_DF_MODEL_RGBSDA;
basicDesc.colorPrimaries = texture.colorSpace === NoColorSpace
? KHR_DF_PRIMARIES_UNSPECIFIED
: KHR_DF_PRIMARIES_BT709;
basicDesc.transferFunction = texture.colorSpace === SRGBColorSpace
? KHR_DF_TRANSFER_SRGB
: KHR_DF_TRANSFER_LINEAR;
basicDesc.texelBlockDimension = [ 0, 0, 0, 0 ];
basicDesc.bytesPlane = [
container.typeSize * channelCount, 0, 0, 0, 0, 0, 0, 0,
];
for ( let i = 0; i < channelCount; ++ i ) {
let channelType = KHR_DF_CHANNEL_MAP[ i ];
if ( texture.colorSpace === LinearSRGBColorSpace || texture.colorSpace === NoColorSpace ) {
channelType |= KHR_DF_SAMPLE_DATATYPE_LINEAR;
}
if ( texture.type === FloatType || texture.type === HalfFloatType ) {
channelType |= KHR_DF_SAMPLE_DATATYPE_FLOAT;
channelType |= KHR_DF_SAMPLE_DATATYPE_SIGNED;
}
basicDesc.samples.push( {
channelType: channelType,
bitOffset: i * array.BYTES_PER_ELEMENT,
bitLength: array.BYTES_PER_ELEMENT * 8 - 1,
samplePosition: [ 0, 0, 0, 0 ],
sampleLower: texture.type === UnsignedByteType ? 0 : - 1,
sampleUpper: texture.type === UnsignedByteType ? 255 : 1,
} );
}
//
container.levels = [ {
levelData: new Uint8Array( array.buffer, array.byteOffset, array.byteLength ),
uncompressedByteLength: array.byteLength,
} ];
//
container.keyValue[ 'KTXwriter' ] = `three.js ${ REVISION }`;
//
return write( container, { keepWriter: true } );
}
}
function toDataTexture( renderer, rtt ) {
const channelCount = getChannelCount( rtt.texture );
let view;
if ( rtt.texture.type === FloatType ) {
view = new Float32Array( rtt.width * rtt.height * channelCount );
} else if ( rtt.texture.type === HalfFloatType ) {
view = new Uint16Array( rtt.width * rtt.height * channelCount );
} else if ( rtt.texture.type === UnsignedByteType ) {
view = new Uint8Array( rtt.width * rtt.height * channelCount );
} else {
throw new Error( ERROR_TYPE );
}
renderer.readRenderTargetPixels( rtt, 0, 0, rtt.width, rtt.height, view );
return new DataTexture( view, rtt.width, rtt.height, rtt.texture.format, rtt.texture.type );
}
function getChannelCount( texture ) {
switch ( texture.format ) {
case RGBAFormat:
return 4;
case RGFormat:
case RGIntegerFormat:
return 2;
case RedFormat:
case RedIntegerFormat:
return 1;
default:
throw new Error( ERROR_FORMAT );
}
}

View File

@ -0,0 +1,217 @@
import {
Matrix4,
Quaternion,
Vector3
} from '/static/three/build/three.module.js';
import { MMDParser } from '../libs/mmdparser.module.js';
/**
* Dependencies
* - mmd-parser https://github.com/takahirox/mmd-parser
*/
class MMDExporter {
/* TODO: implement
// mesh -> pmd
this.parsePmd = function ( object ) {
};
*/
/* TODO: implement
// mesh -> pmx
this.parsePmx = function ( object ) {
};
*/
/* TODO: implement
// animation + skeleton -> vmd
this.parseVmd = function ( object ) {
};
*/
/*
* skeleton -> vpd
* Returns Shift_JIS encoded Uint8Array. Otherwise return strings.
*/
parseVpd( skin, outputShiftJis, useOriginalBones ) {
if ( skin.isSkinnedMesh !== true ) {
console.warn( 'THREE.MMDExporter: parseVpd() requires SkinnedMesh instance.' );
return null;
}
function toStringsFromNumber( num ) {
if ( Math.abs( num ) < 1e-6 ) num = 0;
let a = num.toString();
if ( a.indexOf( '.' ) === - 1 ) {
a += '.';
}
a += '000000';
const index = a.indexOf( '.' );
const d = a.slice( 0, index );
const p = a.slice( index + 1, index + 7 );
return d + '.' + p;
}
function toStringsFromArray( array ) {
const a = [];
for ( let i = 0, il = array.length; i < il; i ++ ) {
a.push( toStringsFromNumber( array[ i ] ) );
}
return a.join( ',' );
}
skin.updateMatrixWorld( true );
const bones = skin.skeleton.bones;
const bones2 = getBindBones( skin );
const position = new Vector3();
const quaternion = new Quaternion();
const quaternion2 = new Quaternion();
const matrix = new Matrix4();
const array = [];
array.push( 'Vocaloid Pose Data file' );
array.push( '' );
array.push( ( skin.name !== '' ? skin.name.replace( /\s/g, '_' ) : 'skin' ) + '.osm;' );
array.push( bones.length + ';' );
array.push( '' );
for ( let i = 0, il = bones.length; i < il; i ++ ) {
const bone = bones[ i ];
const bone2 = bones2[ i ];
/*
* use the bone matrix saved before solving IK.
* see CCDIKSolver for the detail.
*/
if ( useOriginalBones === true &&
bone.userData.ik !== undefined &&
bone.userData.ik.originalMatrix !== undefined ) {
matrix.fromArray( bone.userData.ik.originalMatrix );
} else {
matrix.copy( bone.matrix );
}
position.setFromMatrixPosition( matrix );
quaternion.setFromRotationMatrix( matrix );
const pArray = position.sub( bone2.position ).toArray();
const qArray = quaternion2.copy( bone2.quaternion ).conjugate().multiply( quaternion ).toArray();
// right to left
pArray[ 2 ] = - pArray[ 2 ];
qArray[ 0 ] = - qArray[ 0 ];
qArray[ 1 ] = - qArray[ 1 ];
array.push( 'Bone' + i + '{' + bone.name );
array.push( ' ' + toStringsFromArray( pArray ) + ';' );
array.push( ' ' + toStringsFromArray( qArray ) + ';' );
array.push( '}' );
array.push( '' );
}
array.push( '' );
const lines = array.join( '\n' );
return ( outputShiftJis === true ) ? unicodeToShiftjis( lines ) : lines;
}
}
// Unicode to Shift_JIS table
let u2sTable;
function unicodeToShiftjis( str ) {
if ( u2sTable === undefined ) {
const encoder = new MMDParser.CharsetEncoder();
const table = encoder.s2uTable;
u2sTable = {};
const keys = Object.keys( table );
for ( let i = 0, il = keys.length; i < il; i ++ ) {
let key = keys[ i ];
const value = table[ key ];
key = parseInt( key );
u2sTable[ value ] = key;
}
}
const array = [];
for ( let i = 0, il = str.length; i < il; i ++ ) {
const code = str.charCodeAt( i );
const value = u2sTable[ code ];
if ( value === undefined ) {
throw new Error( 'cannot convert charcode 0x' + code.toString( 16 ) );
} else if ( value > 0xff ) {
array.push( ( value >> 8 ) & 0xff );
array.push( value & 0xff );
} else {
array.push( value & 0xff );
}
}
return new Uint8Array( array );
}
function getBindBones( skin ) {
// any more efficient ways?
const poseSkin = skin.clone();
poseSkin.pose();
return poseSkin.skeleton.bones;
}
export { MMDExporter };

View File

@ -0,0 +1,284 @@
import {
Color,
Matrix3,
Vector2,
Vector3
} from '/static/three/build/three.module.js';
class OBJExporter {
parse( object ) {
let output = '';
let indexVertex = 0;
let indexVertexUvs = 0;
let indexNormals = 0;
const vertex = new Vector3();
const color = new Color();
const normal = new Vector3();
const uv = new Vector2();
const face = [];
function parseMesh( mesh ) {
let nbVertex = 0;
let nbNormals = 0;
let nbVertexUvs = 0;
const geometry = mesh.geometry;
const normalMatrixWorld = new Matrix3();
// shortcuts
const vertices = geometry.getAttribute( 'position' );
const normals = geometry.getAttribute( 'normal' );
const uvs = geometry.getAttribute( 'uv' );
const indices = geometry.getIndex();
// name of the mesh object
output += 'o ' + mesh.name + '\n';
// name of the mesh material
if ( mesh.material && mesh.material.name ) {
output += 'usemtl ' + mesh.material.name + '\n';
}
// vertices
if ( vertices !== undefined ) {
for ( let i = 0, l = vertices.count; i < l; i ++, nbVertex ++ ) {
vertex.fromBufferAttribute( vertices, i );
// transform the vertex to world space
vertex.applyMatrix4( mesh.matrixWorld );
// transform the vertex to export format
output += 'v ' + vertex.x + ' ' + vertex.y + ' ' + vertex.z + '\n';
}
}
// uvs
if ( uvs !== undefined ) {
for ( let i = 0, l = uvs.count; i < l; i ++, nbVertexUvs ++ ) {
uv.fromBufferAttribute( uvs, i );
// transform the uv to export format
output += 'vt ' + uv.x + ' ' + uv.y + '\n';
}
}
// normals
if ( normals !== undefined ) {
normalMatrixWorld.getNormalMatrix( mesh.matrixWorld );
for ( let i = 0, l = normals.count; i < l; i ++, nbNormals ++ ) {
normal.fromBufferAttribute( normals, i );
// transform the normal to world space
normal.applyMatrix3( normalMatrixWorld ).normalize();
// transform the normal to export format
output += 'vn ' + normal.x + ' ' + normal.y + ' ' + normal.z + '\n';
}
}
// faces
if ( indices !== null ) {
for ( let i = 0, l = indices.count; i < l; i += 3 ) {
for ( let m = 0; m < 3; m ++ ) {
const j = indices.getX( i + m ) + 1;
face[ m ] = ( indexVertex + j ) + ( normals || uvs ? '/' + ( uvs ? ( indexVertexUvs + j ) : '' ) + ( normals ? '/' + ( indexNormals + j ) : '' ) : '' );
}
// transform the face to export format
output += 'f ' + face.join( ' ' ) + '\n';
}
} else {
for ( let i = 0, l = vertices.count; i < l; i += 3 ) {
for ( let m = 0; m < 3; m ++ ) {
const j = i + m + 1;
face[ m ] = ( indexVertex + j ) + ( normals || uvs ? '/' + ( uvs ? ( indexVertexUvs + j ) : '' ) + ( normals ? '/' + ( indexNormals + j ) : '' ) : '' );
}
// transform the face to export format
output += 'f ' + face.join( ' ' ) + '\n';
}
}
// update index
indexVertex += nbVertex;
indexVertexUvs += nbVertexUvs;
indexNormals += nbNormals;
}
function parseLine( line ) {
let nbVertex = 0;
const geometry = line.geometry;
const type = line.type;
// shortcuts
const vertices = geometry.getAttribute( 'position' );
// name of the line object
output += 'o ' + line.name + '\n';
if ( vertices !== undefined ) {
for ( let i = 0, l = vertices.count; i < l; i ++, nbVertex ++ ) {
vertex.fromBufferAttribute( vertices, i );
// transform the vertex to world space
vertex.applyMatrix4( line.matrixWorld );
// transform the vertex to export format
output += 'v ' + vertex.x + ' ' + vertex.y + ' ' + vertex.z + '\n';
}
}
if ( type === 'Line' ) {
output += 'l ';
for ( let j = 1, l = vertices.count; j <= l; j ++ ) {
output += ( indexVertex + j ) + ' ';
}
output += '\n';
}
if ( type === 'LineSegments' ) {
for ( let j = 1, k = j + 1, l = vertices.count; j < l; j += 2, k = j + 1 ) {
output += 'l ' + ( indexVertex + j ) + ' ' + ( indexVertex + k ) + '\n';
}
}
// update index
indexVertex += nbVertex;
}
function parsePoints( points ) {
let nbVertex = 0;
const geometry = points.geometry;
const vertices = geometry.getAttribute( 'position' );
const colors = geometry.getAttribute( 'color' );
output += 'o ' + points.name + '\n';
if ( vertices !== undefined ) {
for ( let i = 0, l = vertices.count; i < l; i ++, nbVertex ++ ) {
vertex.fromBufferAttribute( vertices, i );
vertex.applyMatrix4( points.matrixWorld );
output += 'v ' + vertex.x + ' ' + vertex.y + ' ' + vertex.z;
if ( colors !== undefined ) {
color.fromBufferAttribute( colors, i ).convertLinearToSRGB();
output += ' ' + color.r + ' ' + color.g + ' ' + color.b;
}
output += '\n';
}
output += 'p ';
for ( let j = 1, l = vertices.count; j <= l; j ++ ) {
output += ( indexVertex + j ) + ' ';
}
output += '\n';
}
// update index
indexVertex += nbVertex;
}
object.traverse( function ( child ) {
if ( child.isMesh === true ) {
parseMesh( child );
}
if ( child.isLine === true ) {
parseLine( child );
}
if ( child.isPoints === true ) {
parsePoints( child );
}
} );
return output;
}
}
export { OBJExporter };

View File

@ -0,0 +1,528 @@
import {
Matrix3,
Vector3,
Color
} from '/static/three/build/three.module.js';
/**
* https://github.com/gkjohnson/ply-exporter-js
*
* Usage:
* const exporter = new PLYExporter();
*
* // second argument is a list of options
* exporter.parse(mesh, data => console.log(data), { binary: true, excludeAttributes: [ 'color' ], littleEndian: true });
*
* Format Definition:
* http://paulbourke.net/dataformats/ply/
*/
class PLYExporter {
parse( object, onDone, options = {} ) {
// Iterate over the valid meshes in the object
function traverseMeshes( cb ) {
object.traverse( function ( child ) {
if ( child.isMesh === true || child.isPoints ) {
const mesh = child;
const geometry = mesh.geometry;
if ( geometry.hasAttribute( 'position' ) === true ) {
cb( mesh, geometry );
}
}
} );
}
// Default options
const defaultOptions = {
binary: false,
excludeAttributes: [], // normal, uv, color, index
littleEndian: false
};
options = Object.assign( defaultOptions, options );
const excludeAttributes = options.excludeAttributes;
let includeIndices = true;
let includeNormals = false;
let includeColors = false;
let includeUVs = false;
// count the vertices, check which properties are used,
// and cache the BufferGeometry
let vertexCount = 0;
let faceCount = 0;
object.traverse( function ( child ) {
if ( child.isMesh === true ) {
const mesh = child;
const geometry = mesh.geometry;
const vertices = geometry.getAttribute( 'position' );
const normals = geometry.getAttribute( 'normal' );
const uvs = geometry.getAttribute( 'uv' );
const colors = geometry.getAttribute( 'color' );
const indices = geometry.getIndex();
if ( vertices === undefined ) {
return;
}
vertexCount += vertices.count;
faceCount += indices ? indices.count / 3 : vertices.count / 3;
if ( normals !== undefined ) includeNormals = true;
if ( uvs !== undefined ) includeUVs = true;
if ( colors !== undefined ) includeColors = true;
} else if ( child.isPoints ) {
const mesh = child;
const geometry = mesh.geometry;
const vertices = geometry.getAttribute( 'position' );
const normals = geometry.getAttribute( 'normal' );
const colors = geometry.getAttribute( 'color' );
vertexCount += vertices.count;
if ( normals !== undefined ) includeNormals = true;
if ( colors !== undefined ) includeColors = true;
includeIndices = false;
}
} );
const tempColor = new Color();
includeIndices = includeIndices && excludeAttributes.indexOf( 'index' ) === - 1;
includeNormals = includeNormals && excludeAttributes.indexOf( 'normal' ) === - 1;
includeColors = includeColors && excludeAttributes.indexOf( 'color' ) === - 1;
includeUVs = includeUVs && excludeAttributes.indexOf( 'uv' ) === - 1;
if ( includeIndices && faceCount !== Math.floor( faceCount ) ) {
// point cloud meshes will not have an index array and may not have a
// number of vertices that is divisble by 3 (and therefore representable
// as triangles)
console.error(
'PLYExporter: Failed to generate a valid PLY file with triangle indices because the ' +
'number of indices is not divisible by 3.'
);
return null;
}
const indexByteCount = 4;
let header =
'ply\n' +
`format ${ options.binary ? ( options.littleEndian ? 'binary_little_endian' : 'binary_big_endian' ) : 'ascii' } 1.0\n` +
`element vertex ${vertexCount}\n` +
// position
'property float x\n' +
'property float y\n' +
'property float z\n';
if ( includeNormals === true ) {
// normal
header +=
'property float nx\n' +
'property float ny\n' +
'property float nz\n';
}
if ( includeUVs === true ) {
// uvs
header +=
'property float s\n' +
'property float t\n';
}
if ( includeColors === true ) {
// colors
header +=
'property uchar red\n' +
'property uchar green\n' +
'property uchar blue\n';
}
if ( includeIndices === true ) {
// faces
header +=
`element face ${faceCount}\n` +
'property list uchar int vertex_index\n';
}
header += 'end_header\n';
// Generate attribute data
const vertex = new Vector3();
const normalMatrixWorld = new Matrix3();
let result = null;
if ( options.binary === true ) {
// Binary File Generation
const headerBin = new TextEncoder().encode( header );
// 3 position values at 4 bytes
// 3 normal values at 4 bytes
// 3 color channels with 1 byte
// 2 uv values at 4 bytes
const vertexListLength = vertexCount * ( 4 * 3 + ( includeNormals ? 4 * 3 : 0 ) + ( includeColors ? 3 : 0 ) + ( includeUVs ? 4 * 2 : 0 ) );
// 1 byte shape desciptor
// 3 vertex indices at ${indexByteCount} bytes
const faceListLength = includeIndices ? faceCount * ( indexByteCount * 3 + 1 ) : 0;
const output = new DataView( new ArrayBuffer( headerBin.length + vertexListLength + faceListLength ) );
new Uint8Array( output.buffer ).set( headerBin, 0 );
let vOffset = headerBin.length;
let fOffset = headerBin.length + vertexListLength;
let writtenVertices = 0;
traverseMeshes( function ( mesh, geometry ) {
const vertices = geometry.getAttribute( 'position' );
const normals = geometry.getAttribute( 'normal' );
const uvs = geometry.getAttribute( 'uv' );
const colors = geometry.getAttribute( 'color' );
const indices = geometry.getIndex();
normalMatrixWorld.getNormalMatrix( mesh.matrixWorld );
for ( let i = 0, l = vertices.count; i < l; i ++ ) {
vertex.fromBufferAttribute( vertices, i );
vertex.applyMatrix4( mesh.matrixWorld );
// Position information
output.setFloat32( vOffset, vertex.x, options.littleEndian );
vOffset += 4;
output.setFloat32( vOffset, vertex.y, options.littleEndian );
vOffset += 4;
output.setFloat32( vOffset, vertex.z, options.littleEndian );
vOffset += 4;
// Normal information
if ( includeNormals === true ) {
if ( normals != null ) {
vertex.fromBufferAttribute( normals, i );
vertex.applyMatrix3( normalMatrixWorld ).normalize();
output.setFloat32( vOffset, vertex.x, options.littleEndian );
vOffset += 4;
output.setFloat32( vOffset, vertex.y, options.littleEndian );
vOffset += 4;
output.setFloat32( vOffset, vertex.z, options.littleEndian );
vOffset += 4;
} else {
output.setFloat32( vOffset, 0, options.littleEndian );
vOffset += 4;
output.setFloat32( vOffset, 0, options.littleEndian );
vOffset += 4;
output.setFloat32( vOffset, 0, options.littleEndian );
vOffset += 4;
}
}
// UV information
if ( includeUVs === true ) {
if ( uvs != null ) {
output.setFloat32( vOffset, uvs.getX( i ), options.littleEndian );
vOffset += 4;
output.setFloat32( vOffset, uvs.getY( i ), options.littleEndian );
vOffset += 4;
} else {
output.setFloat32( vOffset, 0, options.littleEndian );
vOffset += 4;
output.setFloat32( vOffset, 0, options.littleEndian );
vOffset += 4;
}
}
// Color information
if ( includeColors === true ) {
if ( colors != null ) {
tempColor
.fromBufferAttribute( colors, i )
.convertLinearToSRGB();
output.setUint8( vOffset, Math.floor( tempColor.r * 255 ) );
vOffset += 1;
output.setUint8( vOffset, Math.floor( tempColor.g * 255 ) );
vOffset += 1;
output.setUint8( vOffset, Math.floor( tempColor.b * 255 ) );
vOffset += 1;
} else {
output.setUint8( vOffset, 255 );
vOffset += 1;
output.setUint8( vOffset, 255 );
vOffset += 1;
output.setUint8( vOffset, 255 );
vOffset += 1;
}
}
}
if ( includeIndices === true ) {
// Create the face list
if ( indices !== null ) {
for ( let i = 0, l = indices.count; i < l; i += 3 ) {
output.setUint8( fOffset, 3 );
fOffset += 1;
output.setUint32( fOffset, indices.getX( i + 0 ) + writtenVertices, options.littleEndian );
fOffset += indexByteCount;
output.setUint32( fOffset, indices.getX( i + 1 ) + writtenVertices, options.littleEndian );
fOffset += indexByteCount;
output.setUint32( fOffset, indices.getX( i + 2 ) + writtenVertices, options.littleEndian );
fOffset += indexByteCount;
}
} else {
for ( let i = 0, l = vertices.count; i < l; i += 3 ) {
output.setUint8( fOffset, 3 );
fOffset += 1;
output.setUint32( fOffset, writtenVertices + i, options.littleEndian );
fOffset += indexByteCount;
output.setUint32( fOffset, writtenVertices + i + 1, options.littleEndian );
fOffset += indexByteCount;
output.setUint32( fOffset, writtenVertices + i + 2, options.littleEndian );
fOffset += indexByteCount;
}
}
}
// Save the amount of verts we've already written so we can offset
// the face index on the next mesh
writtenVertices += vertices.count;
} );
result = output.buffer;
} else {
// Ascii File Generation
// count the number of vertices
let writtenVertices = 0;
let vertexList = '';
let faceList = '';
traverseMeshes( function ( mesh, geometry ) {
const vertices = geometry.getAttribute( 'position' );
const normals = geometry.getAttribute( 'normal' );
const uvs = geometry.getAttribute( 'uv' );
const colors = geometry.getAttribute( 'color' );
const indices = geometry.getIndex();
normalMatrixWorld.getNormalMatrix( mesh.matrixWorld );
// form each line
for ( let i = 0, l = vertices.count; i < l; i ++ ) {
vertex.fromBufferAttribute( vertices, i );
vertex.applyMatrix4( mesh.matrixWorld );
// Position information
let line =
vertex.x + ' ' +
vertex.y + ' ' +
vertex.z;
// Normal information
if ( includeNormals === true ) {
if ( normals != null ) {
vertex.fromBufferAttribute( normals, i );
vertex.applyMatrix3( normalMatrixWorld ).normalize();
line += ' ' +
vertex.x + ' ' +
vertex.y + ' ' +
vertex.z;
} else {
line += ' 0 0 0';
}
}
// UV information
if ( includeUVs === true ) {
if ( uvs != null ) {
line += ' ' +
uvs.getX( i ) + ' ' +
uvs.getY( i );
} else {
line += ' 0 0';
}
}
// Color information
if ( includeColors === true ) {
if ( colors != null ) {
tempColor
.fromBufferAttribute( colors, i )
.convertLinearToSRGB();
line += ' ' +
Math.floor( tempColor.r * 255 ) + ' ' +
Math.floor( tempColor.g * 255 ) + ' ' +
Math.floor( tempColor.b * 255 );
} else {
line += ' 255 255 255';
}
}
vertexList += line + '\n';
}
// Create the face list
if ( includeIndices === true ) {
if ( indices !== null ) {
for ( let i = 0, l = indices.count; i < l; i += 3 ) {
faceList += `3 ${ indices.getX( i + 0 ) + writtenVertices }`;
faceList += ` ${ indices.getX( i + 1 ) + writtenVertices }`;
faceList += ` ${ indices.getX( i + 2 ) + writtenVertices }\n`;
}
} else {
for ( let i = 0, l = vertices.count; i < l; i += 3 ) {
faceList += `3 ${ writtenVertices + i } ${ writtenVertices + i + 1 } ${ writtenVertices + i + 2 }\n`;
}
}
faceCount += indices ? indices.count / 3 : vertices.count / 3;
}
writtenVertices += vertices.count;
} );
result = `${ header }${vertexList}${ includeIndices ? `${faceList}\n` : '\n' }`;
}
if ( typeof onDone === 'function' ) requestAnimationFrame( () => onDone( result ) );
return result;
}
}
export { PLYExporter };

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