mirror of
https://github.com/galacean/engine.git
synced 2026-05-06 22:23:05 +08:00
3
.gitattributes
vendored
3
.gitattributes
vendored
@@ -1,3 +1,4 @@
|
||||
# We'll let Git's auto-detection algorithm infer if a file is text. If it is,
|
||||
# enforce LF line endings regardless of OS or git configurations.
|
||||
* text=auto eol=lf
|
||||
* text=auto eol=lf
|
||||
e2e/fixtures/** filter=lfs diff=lfs merge=lfs -text
|
||||
|
||||
4
.github/HOW_TO_CONTRIBUTE.md
vendored
4
.github/HOW_TO_CONTRIBUTE.md
vendored
@@ -48,6 +48,8 @@ The following is a set of guidelines for contributing to Galacean. Please spend
|
||||
- Clone the Galacean playground repository and write a demo for your change.
|
||||
- Write an uint test in the Galacean repository and run `npm run test` to execute the uint test.
|
||||
|
||||
- [Write an e2e test](https://github.com/galacean/runtime/wiki/How-to-write-an-e2e-Test-for-runtime) in the Galacean repository and run `npm run e2e` to execute the e2e test.
|
||||
|
||||
|
||||
|
||||
### Submitting a Pull Request
|
||||
@@ -142,4 +144,4 @@ git pull --ff upstream master
|
||||
## Credits
|
||||
|
||||
<br />Thank you to all the people who have already contributed to Galacean!<br />
|
||||
<br />// WIP: Contributors
|
||||
<br />// WIP: Contributors
|
||||
|
||||
50
.github/workflows/ci.yml
vendored
50
.github/workflows/ci.yml
vendored
@@ -73,3 +73,53 @@ jobs:
|
||||
- name: Upload coverage to Codecov
|
||||
run: ./node_modules/.bin/codecov
|
||||
- run: curl -s https://codecov.io/bash
|
||||
|
||||
e2e:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [16.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
lfs: true
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: pnpm
|
||||
- name: Install dependencies
|
||||
run: pnpm i
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Run Cypress Tests
|
||||
uses: cypress-io/github-action@v5
|
||||
with:
|
||||
start: npm run e2e:case
|
||||
wait-on: 'http://localhost:5175'
|
||||
wait-on-timeout: 120
|
||||
browser: chrome
|
||||
- name: Upload Diff
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: cypress-diff
|
||||
path: e2e/diff/
|
||||
- name: Upload Origin
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: cypress-origin
|
||||
path: e2e/fixtures/originImage
|
||||
- name: Upload Screenshots
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: cypress-screenshots
|
||||
path: e2e/screenshots/
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -7,6 +7,7 @@ tmp
|
||||
/packages/*/types
|
||||
/packages/*/doc
|
||||
/tests/node_modules
|
||||
/e2e/node_modules
|
||||
/playground/node_modules
|
||||
types
|
||||
/packages/*/test/fixtures/**/node_modules
|
||||
@@ -25,3 +26,7 @@ stats.html
|
||||
tsconfig.tsbuildinfo
|
||||
api
|
||||
yarn.lock
|
||||
e2e/videos/*
|
||||
e2e/screenshots/*
|
||||
e2e/diff/*
|
||||
e2e/.dev/mpa
|
||||
|
||||
3
.husky/post-checkout
Executable file
3
.husky/post-checkout
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting '.git/hooks/post-checkout'.\n"; exit 2; }
|
||||
git lfs post-checkout "$@"
|
||||
3
.husky/post-commit
Executable file
3
.husky/post-commit
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting '.git/hooks/post-commit'.\n"; exit 2; }
|
||||
git lfs post-commit "$@"
|
||||
3
.husky/post-merge
Executable file
3
.husky/post-merge
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting '.git/hooks/post-merge'.\n"; exit 2; }
|
||||
git lfs post-merge "$@"
|
||||
3
.husky/pre-push
Executable file
3
.husky/pre-push
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting '.git/hooks/pre-push'.\n"; exit 2; }
|
||||
git lfs pre-push "$@"
|
||||
10
README.md
10
README.md
@@ -75,6 +75,16 @@ Everyone is welcome to join us! Whether you find a bug, have a great feature req
|
||||
|
||||
Make sure to read the [Contributing Guide](.github/HOW_TO_CONTRIBUTE.md) / [贡献指南](https://github.com/galacean/engine/wiki/%E5%A6%82%E4%BD%95%E4%B8%8E%E6%88%91%E4%BB%AC%E5%85%B1%E5%BB%BA-Oasis-%E5%BC%80%E6%BA%90%E4%BA%92%E5%8A%A8%E5%BC%95%E6%93%8E) before submitting changes.
|
||||
|
||||
## Clone
|
||||
Prerequisites:
|
||||
- [git-lfs](https://git-lfs.com/) (Install by official website)
|
||||
|
||||
Clone this repository:
|
||||
|
||||
```sh
|
||||
git clone git@github.com:galacean/runtime.git
|
||||
```
|
||||
|
||||
## Build
|
||||
|
||||
Prerequisites:
|
||||
|
||||
71
cypress.config.ts
Normal file
71
cypress.config.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { defineConfig } from "cypress";
|
||||
import { compare } from "odiff-bin";
|
||||
|
||||
const path = require("path");
|
||||
const fs = require("fs-extra");
|
||||
|
||||
const downloadDirectory = path.join(__dirname, "e2e/downloads");
|
||||
let isRunningInCommandLine = false;
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
viewportWidth: 1200,
|
||||
viewportHeight: 800,
|
||||
baseUrl: "http://localhost:5175",
|
||||
defaultCommandTimeout: 60000,
|
||||
fileServerFolder: "e2e",
|
||||
supportFile: "e2e/support/e2e.ts",
|
||||
fixturesFolder: "e2e/fixtures",
|
||||
screenshotsFolder: "e2e/screenshots",
|
||||
videosFolder: "e2e/videos",
|
||||
specPattern: "e2e/tests/*.cy.ts",
|
||||
setupNodeEvents(on, config) {
|
||||
// implement node event listeners here
|
||||
on("before:browser:launch", (browser, launchOptions) => {
|
||||
console.log("launching browser %s is headless? %s", browser.name, browser.isHeadless);
|
||||
// supply the absolute path to an unpacked extension's folder
|
||||
// NOTE: extensions cannot be loaded in headless Chrome
|
||||
if (fs.existsSync("e2e/diff")) {
|
||||
fs.rmdirSync("e2e/diff", { recursive: true });
|
||||
}
|
||||
if (browser.name === "chrome") {
|
||||
launchOptions.preferences.default["download"] = {
|
||||
default_directory: downloadDirectory
|
||||
};
|
||||
}
|
||||
if (browser.isHeadless) {
|
||||
isRunningInCommandLine = true;
|
||||
}
|
||||
launchOptions.args.push("--force-device-scale-factor=1");
|
||||
return launchOptions;
|
||||
}),
|
||||
on("task", {
|
||||
async compare({ fileName, options }) {
|
||||
fileName += ".png";
|
||||
const baseFolder = "e2e/fixtures/originImage/";
|
||||
const newFolder = path.join("e2e/screenshots", isRunningInCommandLine ? options.specFolder : "");
|
||||
const diffFolder = path.join("e2e/diff", options.specFolder);
|
||||
if (!fs.existsSync(diffFolder)) {
|
||||
fs.mkdirSync(diffFolder, { recursive: true });
|
||||
}
|
||||
const baseImage = path.join(baseFolder, fileName);
|
||||
const newImage = path.join(newFolder, fileName);
|
||||
const diffImage = path.join(diffFolder, fileName);
|
||||
console.log("comparing base image %s to the new image %s", baseImage, newImage);
|
||||
if (options) {
|
||||
console.log("odiff options %o", options);
|
||||
}
|
||||
const started = +new Date();
|
||||
|
||||
const result = await compare(baseImage, newImage, diffImage, options);
|
||||
const finished = +new Date();
|
||||
const elapsed = finished - started;
|
||||
console.log("odiff took %dms", elapsed);
|
||||
|
||||
console.log(result);
|
||||
return result;
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
chromeWebSecurity: false
|
||||
});
|
||||
BIN
e2e/.dev/AlibabaSans.ttf
Normal file
BIN
e2e/.dev/AlibabaSans.ttf
Normal file
Binary file not shown.
39
e2e/.dev/index.html
Normal file
39
e2e/.dev/index.html
Normal file
@@ -0,0 +1,39 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Galacean Case</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<a class="logo" href="https://galacean.antgroup.com/" target="Home">
|
||||
<img
|
||||
src="https://mdn.alipayobjects.com/huamei_qbugvr/afts/img/A*ppQsSphM7uUAAAAAAAAAAAAADtKFAQ/original"
|
||||
alt=""
|
||||
/>
|
||||
<span> Galacean </span>
|
||||
</a>
|
||||
</div>
|
||||
<input
|
||||
placeholder="search..."
|
||||
class="search-bar"
|
||||
id="searchBar"
|
||||
autocorrect="off"
|
||||
autocapitalize="off"
|
||||
spellcheck="false"
|
||||
/>
|
||||
<div class="nav-left">
|
||||
<div class="item-list" id="itemList"></div>
|
||||
</div>
|
||||
|
||||
<div class="nav-right">
|
||||
<iframe id="iframe" allowfullscreen src="" frameborder="0"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="module" src="./index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
76
e2e/.dev/index.js
Normal file
76
e2e/.dev/index.js
Normal file
@@ -0,0 +1,76 @@
|
||||
import './index.sass';
|
||||
import demoList from './mpa/.demoList.json';
|
||||
const itemListDOM = document.getElementById('itemList');
|
||||
const searchBarDOM = document.getElementById('searchBar');
|
||||
const iframe = document.getElementById('iframe');
|
||||
const items = []; // itemDOM,label
|
||||
|
||||
Object.keys(demoList).forEach((group) => {
|
||||
const demos = demoList[group];
|
||||
const groupDOM = document.createElement('div');
|
||||
const demosDOM = document.createElement('ul');
|
||||
itemListDOM.appendChild(groupDOM);
|
||||
groupDOM.appendChild(demosDOM);
|
||||
|
||||
demos.forEach((item) => {
|
||||
const { label, src } = item;
|
||||
const itemDOM = document.createElement('a');
|
||||
|
||||
itemDOM.innerHTML = src;
|
||||
itemDOM.title = `${src}`;
|
||||
itemDOM.onclick = function () {
|
||||
clickItem(itemDOM);
|
||||
};
|
||||
demosDOM.appendChild(itemDOM);
|
||||
|
||||
items.push({
|
||||
itemDOM,
|
||||
label,
|
||||
src,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
searchBarDOM.oninput = () => {
|
||||
updateFilter(searchBar.value);
|
||||
};
|
||||
|
||||
function updateFilter(value) {
|
||||
const reg = new RegExp(value, 'i');
|
||||
|
||||
items.forEach(({ itemDOM, label, src }) => {
|
||||
reg.lastIndex = 0;
|
||||
if (reg.test(label) || reg.test(src)) {
|
||||
itemDOM.classList.remove('hide');
|
||||
} else {
|
||||
itemDOM.classList.add('hide');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function clickItem(itemDOM) {
|
||||
window.location.hash = `#mpa/${itemDOM.title}`;
|
||||
}
|
||||
|
||||
function onHashChange() {
|
||||
const hashPath = window.location.hash.split('#')[1];
|
||||
if (!hashPath) {
|
||||
clickItem(items[0].itemDOM);
|
||||
return;
|
||||
}
|
||||
iframe.src = hashPath + '.html';
|
||||
|
||||
items.forEach(({ itemDOM }) => {
|
||||
const itemPath = `mpa/${itemDOM.title}`;
|
||||
if (itemPath === hashPath) {
|
||||
itemDOM.classList.add('active');
|
||||
} else {
|
||||
itemDOM.classList.remove('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
window.onhashchange = onHashChange;
|
||||
|
||||
// init
|
||||
onHashChange();
|
||||
97
e2e/.dev/index.sass
Normal file
97
e2e/.dev/index.sass
Normal file
@@ -0,0 +1,97 @@
|
||||
$header-height: 3.5rem
|
||||
$header-padding: 1rem
|
||||
$container-gap: 2rem
|
||||
$transition-duration: 0.5s
|
||||
|
||||
*
|
||||
box-sizing: border-box
|
||||
|
||||
html, body
|
||||
margin: 0
|
||||
width: 100%
|
||||
height: 100%
|
||||
overflow: hidden
|
||||
|
||||
body
|
||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif
|
||||
font-size: 16px
|
||||
|
||||
a
|
||||
text-decoration: none
|
||||
color: inherit
|
||||
display: inline-block
|
||||
|
||||
.container
|
||||
display: flex
|
||||
height: 100%
|
||||
user-select: none
|
||||
.header
|
||||
position: absolute
|
||||
top: 0
|
||||
left: 0
|
||||
height: $header-height
|
||||
padding: $header-padding $container-gap
|
||||
.logo
|
||||
height: $header-height - 2 * $header-padding
|
||||
display: flex
|
||||
align-items: center
|
||||
img
|
||||
height: 100%
|
||||
span
|
||||
font-weight: bold
|
||||
padding-left: 10px
|
||||
font-size: 1.2em
|
||||
.search-bar
|
||||
position: absolute
|
||||
top: $header-height
|
||||
left: 1rem
|
||||
padding: $header-padding $container-gap
|
||||
background: #fff url("//gw-office.alipayobjects.com/basement_prod/225a73b9-1281-4388-9555-1e671899bc25.svg") .6rem .55rem no-repeat
|
||||
cursor: text
|
||||
width: 10rem
|
||||
height: 2rem
|
||||
color: #666
|
||||
display: inline-block
|
||||
border: 1px solid #ccc
|
||||
border-radius: 2rem
|
||||
font-size: .9rem
|
||||
line-height: 2rem
|
||||
outline: none
|
||||
.nav-left
|
||||
display: flex
|
||||
flex-direction: column
|
||||
overflow: auto
|
||||
padding: 1rem $container-gap
|
||||
margin-top: $header-height + 2rem
|
||||
|
||||
.item-list
|
||||
.hide
|
||||
height: 0
|
||||
ul
|
||||
padding-left: 20px
|
||||
margin: 0
|
||||
.title
|
||||
font-weight: bold
|
||||
cursor: default
|
||||
font-size: 1.2em
|
||||
a
|
||||
height: 2rem
|
||||
line-height: 2rem
|
||||
cursor: pointer
|
||||
transition-property: color,height
|
||||
transition-duration: $transition-duration
|
||||
overflow: hidden
|
||||
display: block
|
||||
&.active
|
||||
text-decoration: underline
|
||||
color: #096dd9
|
||||
&:hover
|
||||
color: #096dd9
|
||||
|
||||
.nav-right
|
||||
flex: 1
|
||||
box-shadow: rgb(125 121 121) -2px 0px 8px
|
||||
|
||||
iframe
|
||||
width: 100%
|
||||
height: 100%
|
||||
55
e2e/.dev/template/iframe.ejs
Normal file
55
e2e/.dev/template/iframe.ejs
Normal file
@@ -0,0 +1,55 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title><%= title %></title>
|
||||
<style>
|
||||
::-webkit-scrollbar {
|
||||
width: 0px; /* For vertical scrollbars */
|
||||
height: 0px; /* For horizontal scrollbars */
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "AlibabaSans";
|
||||
src: url("../AlibabaSans.ttf");
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
canvas {
|
||||
width: 1200px;
|
||||
height: 800px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
background: black;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.dg.main > ul {
|
||||
overflow: initial;
|
||||
}
|
||||
div {
|
||||
font-family: "AlibabaSans";
|
||||
font-size: 20px;
|
||||
color: white;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
</style>
|
||||
<link rel="preload" href="../AlibabaSans.ttf" as="font" type="font/ttf" crossorigin="anonymous">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<canvas id="canvas"></canvas>
|
||||
<script type="module" src="<%= url %>"></script>
|
||||
<script>
|
||||
document.oncontextmenu = (e) => {
|
||||
e.preventDefault();
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
76
e2e/.dev/vite.config.js
Normal file
76
e2e/.dev/vite.config.js
Normal file
@@ -0,0 +1,76 @@
|
||||
const path = require("path");
|
||||
const fs = require("fs-extra");
|
||||
const OUT_PATH = "mpa";
|
||||
const templateStr = fs.readFileSync(path.join(__dirname, "template/iframe.ejs"), "utf8");
|
||||
|
||||
// 替换 ejs 模版格式的字符串,如 <%= title %>: templateStr.replaceEJS("title","replaced title");
|
||||
String.prototype.replaceEJS = function (regStr, replaceStr) {
|
||||
return this.replace(new RegExp(`<%=\\s*${regStr}\\s*%>`, "g"), replaceStr);
|
||||
};
|
||||
|
||||
// clear mpa
|
||||
fs.emptyDirSync(path.resolve(__dirname, OUT_PATH));
|
||||
|
||||
// create mpa
|
||||
const demoList = fs
|
||||
.readdirSync(path.join(__dirname, "../case"))
|
||||
.filter((name) => /.ts$/.test(name) && name.indexOf(".") !== 0)
|
||||
.map((name) => {
|
||||
return {
|
||||
file: name.split(".ts")[0]
|
||||
};
|
||||
});
|
||||
|
||||
demoList.forEach(({ file }) => {
|
||||
const ejs = templateStr.replaceEJS("url", `./${file}.ts`);
|
||||
|
||||
fs.outputFileSync(path.resolve(__dirname, OUT_PATH, file + ".ts"), `import "../../case/${file}"`);
|
||||
fs.outputFileSync(path.resolve(__dirname, OUT_PATH, file + ".html"), ejs);
|
||||
});
|
||||
|
||||
// output demolist
|
||||
const demoSorted = {};
|
||||
demoList.forEach(({ file }) => {
|
||||
if (!demoSorted[file]) {
|
||||
demoSorted[file] = [];
|
||||
}
|
||||
demoSorted[file].push({
|
||||
src: file
|
||||
});
|
||||
});
|
||||
|
||||
fs.outputJSONSync(path.join(__dirname, OUT_PATH, ".demoList.json"), demoSorted);
|
||||
|
||||
module.exports = {
|
||||
server: {
|
||||
open: true,
|
||||
host: "0.0.0.0",
|
||||
port: 5175
|
||||
},
|
||||
resolve: {
|
||||
dedupe: ["@galacean/engine"]
|
||||
},
|
||||
optimizeDeps: {
|
||||
exclude: [
|
||||
"@galacean/engine",
|
||||
"@galacean/engine-draco",
|
||||
"@galacean/engine-lottie",
|
||||
"@galacean/engine-spine",
|
||||
"@galacean/tools-baker",
|
||||
"@galacean/engine-toolkit",
|
||||
"@galacean/engine-toolkit-auxiliary-lines",
|
||||
"@galacean/engine-toolkit-controls",
|
||||
"@galacean/engine-toolkit-framebuffer-picker",
|
||||
"@galacean/engine-toolkit-gizmo",
|
||||
"@galacean/engine-toolkit-lines",
|
||||
"@galacean/engine-toolkit-outline",
|
||||
"@galacean/engine-toolkit-planar-shadow-material",
|
||||
"@galacean/engine-toolkit-skeleton-viewer",
|
||||
"@galacean/engine-toolkit-grid-material",
|
||||
"@galacean/engine-toolkit-navigation-gizmo",
|
||||
"@galacean/engine-toolkit-geometry-sketch",
|
||||
"@galacean/engine-toolkit-stats",
|
||||
"@galacean/engine-toolkit-input-logger"
|
||||
]
|
||||
}
|
||||
};
|
||||
20
e2e/case/.mockForE2E.ts
Normal file
20
e2e/case/.mockForE2E.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
export const updateForE2E = (engine, deltaTime = 100) => {
|
||||
engine._vSyncCount = Infinity;
|
||||
engine._time._lastSystemTime = 0;
|
||||
let times = 0;
|
||||
performance.now = function () {
|
||||
times++;
|
||||
return times * deltaTime;
|
||||
};
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
engine.update();
|
||||
}
|
||||
};
|
||||
|
||||
export const e2eReady = () => {
|
||||
setTimeout(() => {
|
||||
const text = document.createElement("div");
|
||||
text.className = "cypressReady";
|
||||
document.body.appendChild(text);
|
||||
}, 1000);
|
||||
}
|
||||
68
e2e/case/animator-additive.ts
Normal file
68
e2e/case/animator-additive.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* @title Animation Additive
|
||||
* @category Animation
|
||||
*/
|
||||
import {
|
||||
Animator,
|
||||
AnimatorControllerLayer,
|
||||
AnimatorLayerBlendingMode,
|
||||
AnimatorStateMachine,
|
||||
Camera,
|
||||
DirectLight,
|
||||
GLTFResource,
|
||||
Logger,
|
||||
SystemInfo,
|
||||
Vector3,
|
||||
WebGLEngine
|
||||
} from "@galacean/engine";
|
||||
import { OrbitControl } from "@galacean/engine-toolkit";
|
||||
import { e2eReady, updateForE2E } from "./.mockForE2E";
|
||||
|
||||
Logger.enable();
|
||||
|
||||
WebGLEngine.create({ canvas: "canvas" }).then((engine) => {
|
||||
engine.canvas.resizeByClientSize(2);
|
||||
const scene = engine.sceneManager.activeScene;
|
||||
const rootEntity = scene.createRootEntity();
|
||||
|
||||
// camera
|
||||
const cameraEntity = rootEntity.createChild("camera_node");
|
||||
cameraEntity.transform.position = new Vector3(0, 1, 5);
|
||||
cameraEntity.addComponent(Camera);
|
||||
cameraEntity.addComponent(OrbitControl).target = new Vector3(0, 1, 0);
|
||||
|
||||
const lightNode = rootEntity.createChild("light_node");
|
||||
lightNode.addComponent(DirectLight).intensity = 0.6;
|
||||
lightNode.transform.lookAt(new Vector3(0, 0, 1));
|
||||
lightNode.transform.rotate(new Vector3(0, 90, 0));
|
||||
|
||||
engine.resourceManager
|
||||
.load<GLTFResource>("https://gw.alipayobjects.com/os/bmw-prod/5e3c1e4e-496e-45f8-8e05-f89f2bd5e4a4.glb")
|
||||
.then((gltfResource) => {
|
||||
const { animations = [], defaultSceneRoot } = gltfResource;
|
||||
const animator = defaultSceneRoot.getComponent(Animator);
|
||||
const { animatorController } = animator;
|
||||
|
||||
const animatorStateMachine = new AnimatorStateMachine();
|
||||
const additiveLayer = new AnimatorControllerLayer("additiveLayer");
|
||||
additiveLayer.stateMachine = animatorStateMachine;
|
||||
additiveLayer.blendingMode = AnimatorLayerBlendingMode.Additive;
|
||||
animatorController.addLayer(additiveLayer);
|
||||
|
||||
const additivePoseNames = animations.filter((clip) => clip.name.includes("pose")).map((clip) => clip.name);
|
||||
|
||||
additivePoseNames.forEach((name) => {
|
||||
const clip = animator.findAnimatorState(name).clip;
|
||||
const newState = animatorStateMachine.addState(name);
|
||||
newState.clipStartTime = 1;
|
||||
newState.clip = clip;
|
||||
});
|
||||
|
||||
rootEntity.addChild(defaultSceneRoot);
|
||||
|
||||
animator.play("walk", 0);
|
||||
animator.play("sad_pose", 1);
|
||||
updateForE2E(engine);
|
||||
e2eReady();
|
||||
});
|
||||
});
|
||||
49
e2e/case/animator-blendShape.ts
Normal file
49
e2e/case/animator-blendShape.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* @title Animation BlendShape
|
||||
* @category Animation
|
||||
*/
|
||||
import { OrbitControl } from "@galacean/engine-toolkit";
|
||||
import {
|
||||
Animator,
|
||||
Camera,
|
||||
DirectLight,
|
||||
Logger,
|
||||
SkinnedMeshRenderer,
|
||||
SystemInfo,
|
||||
Vector3,
|
||||
WebGLEngine,
|
||||
GLTFResource
|
||||
} from "@galacean/engine";
|
||||
import { e2eReady, updateForE2E } from "./.mockForE2E";
|
||||
|
||||
Logger.enable();
|
||||
WebGLEngine.create({ canvas: "canvas" }).then((engine) => {
|
||||
engine.canvas.resizeByClientSize(2);
|
||||
const scene = engine.sceneManager.activeScene;
|
||||
const rootEntity = scene.createRootEntity();
|
||||
|
||||
// camera
|
||||
const cameraEntity = rootEntity.createChild("camera_node");
|
||||
cameraEntity.transform.position = new Vector3(0, 1, 5);
|
||||
cameraEntity.addComponent(Camera);
|
||||
cameraEntity.addComponent(OrbitControl).target = new Vector3(0, 1, 0);
|
||||
|
||||
const lightNode = rootEntity.createChild("light_node");
|
||||
lightNode.addComponent(DirectLight).intensity = 1.0;
|
||||
lightNode.transform.lookAt(new Vector3(0, 0, 1));
|
||||
lightNode.transform.rotate(new Vector3(-45, -135, 0));
|
||||
|
||||
engine.resourceManager
|
||||
.load<GLTFResource>("https://gw.alipayobjects.com/os/bmw-prod/746da3e3-fdc9-4155-8fee-0e2a97de4e72.glb")
|
||||
.then((asset) => {
|
||||
const { defaultSceneRoot } = asset;
|
||||
rootEntity.addChild(defaultSceneRoot);
|
||||
const animator = defaultSceneRoot.getComponent(Animator);
|
||||
const skinMeshRenderer = defaultSceneRoot.getComponent(SkinnedMeshRenderer);
|
||||
|
||||
skinMeshRenderer.blendShapeWeights[0] = 1.0;
|
||||
animator.play("TheWave");
|
||||
updateForE2E(engine);
|
||||
e2eReady();
|
||||
});
|
||||
});
|
||||
47
e2e/case/animator-crossfade.ts
Normal file
47
e2e/case/animator-crossfade.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* @title Animation Play
|
||||
* @category Animation
|
||||
*/
|
||||
import { OrbitControl } from "@galacean/engine-toolkit";
|
||||
import {
|
||||
Animator,
|
||||
Camera,
|
||||
DirectLight,
|
||||
Logger,
|
||||
SystemInfo,
|
||||
Vector3,
|
||||
WebGLEngine,
|
||||
GLTFResource
|
||||
} from "@galacean/engine";
|
||||
import { e2eReady, updateForE2E } from "./.mockForE2E";
|
||||
|
||||
Logger.enable();
|
||||
WebGLEngine.create({ canvas: "canvas" }).then((engine) => {
|
||||
engine.canvas.resizeByClientSize(2);
|
||||
const scene = engine.sceneManager.activeScene;
|
||||
const rootEntity = scene.createRootEntity();
|
||||
|
||||
// camera
|
||||
const cameraEntity = rootEntity.createChild("camera_node");
|
||||
cameraEntity.transform.position = new Vector3(0, 1, 5);
|
||||
cameraEntity.addComponent(Camera);
|
||||
cameraEntity.addComponent(OrbitControl).target = new Vector3(0, 1, 0);
|
||||
|
||||
const lightNode = rootEntity.createChild("light_node");
|
||||
lightNode.addComponent(DirectLight).intensity = 0.6;
|
||||
lightNode.transform.lookAt(new Vector3(0, 0, 1));
|
||||
lightNode.transform.rotate(new Vector3(0, 90, 0));
|
||||
|
||||
engine.resourceManager
|
||||
.load<GLTFResource>("https://gw.alipayobjects.com/os/bmw-prod/5e3c1e4e-496e-45f8-8e05-f89f2bd5e4a4.glb")
|
||||
.then((gltfResource) => {
|
||||
const { animations = [], defaultSceneRoot } = gltfResource;
|
||||
rootEntity.addChild(defaultSceneRoot);
|
||||
const animator = defaultSceneRoot.getComponent(Animator);
|
||||
animator.play("agree");
|
||||
updateForE2E(engine, 30);
|
||||
animator.crossFade("walk", 0.5, 0, 0);
|
||||
updateForE2E(engine, 30);
|
||||
e2eReady();
|
||||
});
|
||||
});
|
||||
188
e2e/case/animator-customAnimationClip.ts
Normal file
188
e2e/case/animator-customAnimationClip.ts
Normal file
@@ -0,0 +1,188 @@
|
||||
/**
|
||||
* @title Animation CustomAnimationClip
|
||||
* @category Animation
|
||||
*/
|
||||
import {
|
||||
AnimationClip,
|
||||
AnimationColorCurve,
|
||||
AnimationFloatCurve,
|
||||
AnimationVector3Curve,
|
||||
Animator,
|
||||
AnimatorController,
|
||||
AnimatorControllerLayer,
|
||||
AnimatorStateMachine,
|
||||
Camera,
|
||||
Color,
|
||||
DirectLight,
|
||||
GLTFResource,
|
||||
Keyframe,
|
||||
Logger,
|
||||
SpotLight,
|
||||
SystemInfo,
|
||||
Transform,
|
||||
Vector3,
|
||||
WebGLEngine
|
||||
} from "@galacean/engine";
|
||||
import { e2eReady, updateForE2E } from "./.mockForE2E";
|
||||
|
||||
Logger.enable();
|
||||
WebGLEngine.create({ canvas: "canvas" }).then((engine) => {
|
||||
engine.canvas.resizeByClientSize(2);
|
||||
const scene = engine.sceneManager.activeScene;
|
||||
const rootEntity = scene.createRootEntity();
|
||||
|
||||
// camera
|
||||
const cameraWrap = rootEntity.createChild("camera_wrap");
|
||||
const cameraEntity = cameraWrap.createChild("camera");
|
||||
cameraEntity.transform.position = new Vector3(0, 3, 8);
|
||||
cameraEntity.transform.rotation = new Vector3(-2, 0, 0);
|
||||
cameraEntity.addComponent(Camera);
|
||||
|
||||
const lightWrap = rootEntity.createChild("light_wrap");
|
||||
|
||||
const directLightEntity = lightWrap.createChild("light_node");
|
||||
const directLight = directLightEntity.addComponent(DirectLight);
|
||||
directLight.intensity = 0.6;
|
||||
directLightEntity.transform.lookAt(new Vector3(0, 0, 1));
|
||||
directLightEntity.transform.rotate(new Vector3(0, 90, 0));
|
||||
|
||||
const spotLightEntity = lightWrap.createChild("spotLight1");
|
||||
const spotLightEntity2 = spotLightEntity.clone();
|
||||
spotLightEntity2.name = "spotLight2";
|
||||
spotLightEntity2.transform.setRotation(-120, 0, 0);
|
||||
lightWrap.addChild(spotLightEntity2);
|
||||
|
||||
const spotLight = spotLightEntity.addComponent(SpotLight);
|
||||
spotLight.angle = Math.PI / 60;
|
||||
spotLightEntity.transform.setPosition(0, 8, 0);
|
||||
spotLightEntity.transform.setRotation(-60, 0, 0);
|
||||
const spotLight2 = spotLightEntity2.addComponent(SpotLight);
|
||||
spotLight2.angle = Math.PI / 60;
|
||||
spotLightEntity2.transform.setPosition(0, 8, 0);
|
||||
spotLightEntity2.transform.setRotation(-60, 0, 0);
|
||||
|
||||
engine.resourceManager
|
||||
.load<GLTFResource>("https://gw.alipayobjects.com/os/OasisHub/244228a7-361c-4c63-a790-dd9e19d12e78/data.gltf")
|
||||
.then((gltfResource) => {
|
||||
const { defaultSceneRoot, animations = [] } = gltfResource;
|
||||
rootEntity.addChild(defaultSceneRoot);
|
||||
const animator = defaultSceneRoot.getComponent(Animator);
|
||||
|
||||
const sceneAnimator = rootEntity.addComponent(Animator);
|
||||
sceneAnimator.animatorController = new AnimatorController();
|
||||
const layer = new AnimatorControllerLayer("base");
|
||||
sceneAnimator.animatorController.addLayer(layer);
|
||||
const stateMachine = (layer.stateMachine = new AnimatorStateMachine());
|
||||
const sceneState = stateMachine.addState("sceneAnim");
|
||||
const sceneClip = (sceneState.clip = new AnimationClip("sceneAnim"));
|
||||
|
||||
//custom rotate curve
|
||||
const rotateCurve = new AnimationVector3Curve();
|
||||
const key1 = new Keyframe<Vector3>();
|
||||
key1.time = 0;
|
||||
key1.value = new Vector3(0, 0, 0);
|
||||
const key2 = new Keyframe<Vector3>();
|
||||
key2.time = 15;
|
||||
key2.value = new Vector3(0, 360, 0);
|
||||
rotateCurve.addKey(key1);
|
||||
rotateCurve.addKey(key2);
|
||||
|
||||
//custom color curve
|
||||
const colorCurve = new AnimationColorCurve();
|
||||
const key3 = new Keyframe<Color>();
|
||||
key3.time = 0;
|
||||
key3.value = new Color(1, 0, 0, 1);
|
||||
const key4 = new Keyframe<Color>();
|
||||
key4.time = 5;
|
||||
key4.value = new Color(0, 1, 0, 1);
|
||||
const key5 = new Keyframe<Color>();
|
||||
key5.time = 10;
|
||||
key5.value = new Color(0, 0, 1, 1);
|
||||
const key6 = new Keyframe<Color>();
|
||||
key6.time = 15;
|
||||
key6.value = new Color(1, 0, 0, 1);
|
||||
colorCurve.addKey(key3);
|
||||
colorCurve.addKey(key4);
|
||||
colorCurve.addKey(key5);
|
||||
colorCurve.addKey(key6);
|
||||
|
||||
const color2Curve = new AnimationColorCurve();
|
||||
const key16 = new Keyframe<Color>();
|
||||
key16.time = 0;
|
||||
key16.value = new Color(0, 0, 1, 1);
|
||||
const key17 = new Keyframe<Color>();
|
||||
key17.time = 5;
|
||||
key17.value = new Color(0, 1, 0, 1);
|
||||
const key18 = new Keyframe<Color>();
|
||||
key18.time = 10;
|
||||
key18.value = new Color(1, 0, 0, 1);
|
||||
const key19 = new Keyframe<Color>();
|
||||
key19.time = 15;
|
||||
key19.value = new Color(0, 0, 1, 1);
|
||||
color2Curve.addKey(key16);
|
||||
color2Curve.addKey(key17);
|
||||
color2Curve.addKey(key18);
|
||||
color2Curve.addKey(key19);
|
||||
|
||||
//custom fov curve
|
||||
const fovCurve = new AnimationFloatCurve();
|
||||
const key7 = new Keyframe<number>();
|
||||
key7.time = 0;
|
||||
key7.value = 45;
|
||||
const key8 = new Keyframe<number>();
|
||||
key8.time = 8;
|
||||
key8.value = 80;
|
||||
const key9 = new Keyframe<number>();
|
||||
key9.time = 15;
|
||||
key9.value = 45;
|
||||
|
||||
fovCurve.addKey(key7);
|
||||
fovCurve.addKey(key8);
|
||||
fovCurve.addKey(key9);
|
||||
|
||||
//custom spotLight1 rotate curve
|
||||
const spotLight1RotateCurve = new AnimationVector3Curve();
|
||||
const key10 = new Keyframe<Vector3>();
|
||||
key10.time = 0;
|
||||
key10.value = new Vector3(-60, 0, 0);
|
||||
const key11 = new Keyframe<Vector3>();
|
||||
key11.time = 10;
|
||||
key11.value = new Vector3(-120, 0, 0);
|
||||
const key12 = new Keyframe<Vector3>();
|
||||
key12.time = 15;
|
||||
key12.value = new Vector3(-60, 0, 0);
|
||||
spotLight1RotateCurve.addKey(key10);
|
||||
spotLight1RotateCurve.addKey(key11);
|
||||
spotLight1RotateCurve.addKey(key12);
|
||||
|
||||
//custom spotLight2 rotate curve
|
||||
const spotLight2RotateCurve = new AnimationVector3Curve();
|
||||
const key13 = new Keyframe<Vector3>();
|
||||
key13.time = 0;
|
||||
key13.value = new Vector3(-120, 0, 0);
|
||||
const key14 = new Keyframe<Vector3>();
|
||||
key14.time = 10;
|
||||
key14.value = new Vector3(-60, 0, 0);
|
||||
const key15 = new Keyframe<Vector3>();
|
||||
key15.time = 15;
|
||||
key15.value = new Vector3(-120, 0, 0);
|
||||
spotLight2RotateCurve.addKey(key13);
|
||||
spotLight2RotateCurve.addKey(key14);
|
||||
spotLight2RotateCurve.addKey(key15);
|
||||
|
||||
sceneClip.addCurveBinding("/light_wrap/spotLight1", SpotLight, "color", colorCurve);
|
||||
sceneClip.addCurveBinding("/light_wrap/spotLight1", Transform, "rotation", spotLight1RotateCurve);
|
||||
sceneClip.addCurveBinding("/light_wrap/spotLight2", Transform, "rotation", spotLight2RotateCurve);
|
||||
sceneClip.addCurveBinding("/light_wrap/spotLight2", SpotLight, "color", color2Curve);
|
||||
sceneClip.addCurveBinding("/light_wrap", Transform, "rotation", rotateCurve);
|
||||
// curve can be reused
|
||||
sceneClip.addCurveBinding("/camera_wrap", Transform, "rotation", rotateCurve);
|
||||
sceneClip.addCurveBinding("/camera_wrap/camera", Camera, "fieldOfView", fovCurve);
|
||||
|
||||
sceneAnimator.play("sceneAnim", 0);
|
||||
animator.play(animations[0].name, 0);
|
||||
|
||||
updateForE2E(engine, 500);
|
||||
e2eReady();
|
||||
});
|
||||
});
|
||||
98
e2e/case/animator-customBlendShape.ts
Normal file
98
e2e/case/animator-customBlendShape.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* @title Animation CustomBlendShape
|
||||
* @category Animation
|
||||
*/
|
||||
import { OrbitControl } from "@galacean/engine-toolkit";
|
||||
import {
|
||||
AnimationClip,
|
||||
AnimationFloatArrayCurve,
|
||||
Animator,
|
||||
AnimatorController,
|
||||
AnimatorControllerLayer,
|
||||
AnimatorStateMachine,
|
||||
BlendShape,
|
||||
Camera,
|
||||
Keyframe,
|
||||
Logger,
|
||||
ModelMesh,
|
||||
SkinnedMeshRenderer,
|
||||
SystemInfo,
|
||||
UnlitMaterial,
|
||||
Vector3,
|
||||
WebGLEngine
|
||||
} from "@galacean/engine";
|
||||
import { e2eReady, updateForE2E } from "./.mockForE2E";
|
||||
|
||||
Logger.enable();
|
||||
WebGLEngine.create({ canvas: "canvas" }).then((engine) => {
|
||||
engine.canvas.resizeByClientSize(2);
|
||||
const scene = engine.sceneManager.activeScene;
|
||||
const rootEntity = scene.createRootEntity();
|
||||
|
||||
// camera
|
||||
const cameraEntity = rootEntity.createChild("cameraNode");
|
||||
cameraEntity.transform.position = new Vector3(0, 0, 5);
|
||||
cameraEntity.addComponent(Camera);
|
||||
cameraEntity.addComponent(OrbitControl);
|
||||
|
||||
const meshEntity = rootEntity.createChild("meshEntity");
|
||||
const skinnedMeshRenderer = meshEntity.addComponent(SkinnedMeshRenderer);
|
||||
const modelMesh = new ModelMesh(engine);
|
||||
|
||||
// Set vertices data.
|
||||
const positions = [
|
||||
new Vector3(-1.0, -1.0, 1.0),
|
||||
new Vector3(1.0, -1.0, 1.0),
|
||||
new Vector3(1.0, 1.0, 1.0),
|
||||
new Vector3(1.0, 1.0, 1.0),
|
||||
new Vector3(-1.0, 1.0, 1.0),
|
||||
new Vector3(-1.0, -1.0, 1.0)
|
||||
];
|
||||
modelMesh.setPositions(positions);
|
||||
|
||||
// Add SubMesh.
|
||||
modelMesh.addSubMesh(0, 6);
|
||||
|
||||
// Add BlendShape.
|
||||
const deltaPositions = [
|
||||
new Vector3(0.0, 0.0, 0.0),
|
||||
new Vector3(0.0, 0.0, 0.0),
|
||||
new Vector3(-1.0, 0.0, 0.0),
|
||||
new Vector3(-1.0, 0.0, 0.0),
|
||||
new Vector3(1.0, 0.0, 0.0),
|
||||
new Vector3(0.0, 0.0, 0.0)
|
||||
];
|
||||
const blendShape = new BlendShape("BlendShapeA");
|
||||
blendShape.addFrame(1.0, deltaPositions);
|
||||
modelMesh.addBlendShape(blendShape);
|
||||
|
||||
skinnedMeshRenderer.mesh = modelMesh;
|
||||
skinnedMeshRenderer.setMaterial(new UnlitMaterial(engine));
|
||||
|
||||
// Upload data.
|
||||
modelMesh.uploadData(false);
|
||||
|
||||
const animator = meshEntity.addComponent(Animator);
|
||||
animator.animatorController = new AnimatorController();
|
||||
const layer = new AnimatorControllerLayer("base");
|
||||
animator.animatorController.addLayer(layer);
|
||||
const stateMachine = (layer.stateMachine = new AnimatorStateMachine());
|
||||
const state = stateMachine.addState("blendShape");
|
||||
const clip = (state.clip = new AnimationClip("blendShape"));
|
||||
|
||||
//custom blendShape curve
|
||||
const blendShapeCurve = new AnimationFloatArrayCurve();
|
||||
const key1 = new Keyframe<Float32Array>();
|
||||
key1.time = 0;
|
||||
key1.value = new Float32Array([0]);
|
||||
const key2 = new Keyframe<Float32Array>();
|
||||
key2.time = 5;
|
||||
key2.value = new Float32Array([1]);
|
||||
blendShapeCurve.addKey(key1);
|
||||
blendShapeCurve.addKey(key2);
|
||||
|
||||
clip.addCurveBinding("", SkinnedMeshRenderer, "blendShapeWeights", blendShapeCurve);
|
||||
animator.play("blendShape");
|
||||
updateForE2E(engine, 1000);
|
||||
e2eReady();
|
||||
});
|
||||
83
e2e/case/animator-event.ts
Normal file
83
e2e/case/animator-event.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* @title Animation Event
|
||||
* @category Animation
|
||||
*/
|
||||
import { OrbitControl } from "@galacean/engine-toolkit";
|
||||
import * as dat from "dat.gui";
|
||||
import {
|
||||
AnimationEvent,
|
||||
Animator,
|
||||
Camera,
|
||||
DirectLight,
|
||||
Font,
|
||||
FontStyle,
|
||||
GLTFResource,
|
||||
Script,
|
||||
SystemInfo,
|
||||
TextRenderer,
|
||||
Vector3,
|
||||
WebGLEngine
|
||||
} from "@galacean/engine";
|
||||
import { e2eReady, updateForE2E } from "./.mockForE2E";
|
||||
|
||||
const engine = await WebGLEngine.create({ canvas: "canvas" });
|
||||
engine.canvas.resizeByClientSize(2);
|
||||
const scene = engine.sceneManager.activeScene;
|
||||
const rootEntity = scene.createRootEntity();
|
||||
console.log(99, SystemInfo.devicePixelRatio);
|
||||
// camera
|
||||
const cameraEntity = rootEntity.createChild("camera_node");
|
||||
cameraEntity.transform.position = new Vector3(0, 1, 5);
|
||||
cameraEntity.addComponent(Camera);
|
||||
cameraEntity.addComponent(OrbitControl).target = new Vector3(0, 1, 0);
|
||||
|
||||
const lightNode = rootEntity.createChild("light_node");
|
||||
lightNode.addComponent(DirectLight).intensity = 0.6;
|
||||
lightNode.transform.lookAt(new Vector3(0, 0, 1));
|
||||
lightNode.transform.rotate(new Vector3(0, 90, 0));
|
||||
|
||||
// initText
|
||||
const textEntity = rootEntity.createChild("text");
|
||||
const textRenderer = textEntity.addComponent(TextRenderer);
|
||||
textEntity.transform.setPosition(0, 2, 0);
|
||||
textRenderer.fontSize = 12;
|
||||
textRenderer.font = Font.createFromOS(engine, "AlibabaSans");
|
||||
textRenderer.text = "";
|
||||
|
||||
engine.resourceManager
|
||||
.load<GLTFResource>("https://gw.alipayobjects.com/os/bmw-prod/5e3c1e4e-496e-45f8-8e05-f89f2bd5e4a4.glb")
|
||||
.then((gltfResource) => {
|
||||
const { defaultSceneRoot, animations } = gltfResource;
|
||||
rootEntity.addChild(defaultSceneRoot);
|
||||
const animator = defaultSceneRoot.getComponent(Animator);
|
||||
|
||||
const state = animator.findAnimatorState("walk");
|
||||
const clip = state.clip;
|
||||
|
||||
const event0 = new AnimationEvent();
|
||||
event0.functionName = "event0";
|
||||
event0.time = 0.5;
|
||||
clip.addEvent(event0);
|
||||
|
||||
const event1 = new AnimationEvent();
|
||||
event1.functionName = "event1";
|
||||
event1.time = clip.length;
|
||||
clip.addEvent(event1);
|
||||
|
||||
defaultSceneRoot.addComponent(
|
||||
class extends Script {
|
||||
event0(): void {
|
||||
textRenderer.text = "0";
|
||||
}
|
||||
|
||||
event1(): void {
|
||||
textRenderer.text = "1";
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
animator.play("walk", 0);
|
||||
|
||||
updateForE2E(engine, 500);
|
||||
e2eReady();
|
||||
});
|
||||
46
e2e/case/animator-play.ts
Normal file
46
e2e/case/animator-play.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* @title Animation Play
|
||||
* @category Animation
|
||||
*/
|
||||
import {
|
||||
Animator,
|
||||
Camera,
|
||||
DirectLight,
|
||||
GLTFResource,
|
||||
Logger,
|
||||
SystemInfo,
|
||||
Vector3,
|
||||
WebGLEngine
|
||||
} from "@galacean/engine";
|
||||
import { OrbitControl } from "@galacean/engine-toolkit";
|
||||
import { updateForE2E, e2eReady } from "./.mockForE2E";
|
||||
|
||||
Logger.enable();
|
||||
WebGLEngine.create({ canvas: "canvas" }).then((engine) => {
|
||||
engine.canvas.resizeByClientSize(2);
|
||||
const scene = engine.sceneManager.activeScene;
|
||||
const rootEntity = scene.createRootEntity();
|
||||
|
||||
// camera
|
||||
const cameraEntity = rootEntity.createChild("camera_node");
|
||||
cameraEntity.transform.position = new Vector3(0, 1, 5);
|
||||
cameraEntity.addComponent(Camera);
|
||||
cameraEntity.addComponent(OrbitControl).target = new Vector3(0, 1, 0);
|
||||
|
||||
const lightNode = rootEntity.createChild("light_node");
|
||||
lightNode.addComponent(DirectLight).intensity = 0.6;
|
||||
lightNode.transform.lookAt(new Vector3(0, 0, 1));
|
||||
lightNode.transform.rotate(new Vector3(0, 90, 0));
|
||||
|
||||
engine.resourceManager
|
||||
.load<GLTFResource>("https://gw.alipayobjects.com/os/bmw-prod/5e3c1e4e-496e-45f8-8e05-f89f2bd5e4a4.glb")
|
||||
.then((gltfResource) => {
|
||||
const { defaultSceneRoot } = gltfResource;
|
||||
rootEntity.addChild(defaultSceneRoot);
|
||||
const animator = defaultSceneRoot.getComponent(Animator);
|
||||
animator.play("agree");
|
||||
|
||||
updateForE2E(engine);
|
||||
e2eReady();
|
||||
});
|
||||
});
|
||||
69
e2e/case/animator-reuse.ts
Normal file
69
e2e/case/animator-reuse.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* @title Animation Reuse
|
||||
* @category Animation
|
||||
*/
|
||||
import {
|
||||
Animator,
|
||||
AssetPromise,
|
||||
Camera,
|
||||
DirectLight,
|
||||
GLTFResource,
|
||||
Logger,
|
||||
SystemInfo,
|
||||
Vector3,
|
||||
WebGLEngine
|
||||
} from "@galacean/engine";
|
||||
import { OrbitControl } from "@galacean/engine-toolkit";
|
||||
import { e2eReady, updateForE2E } from "./.mockForE2E";
|
||||
|
||||
Logger.enable();
|
||||
WebGLEngine.create({ canvas: "canvas" }).then((engine) => {
|
||||
engine.canvas.resizeByClientSize(2);
|
||||
const scene = engine.sceneManager.activeScene;
|
||||
const rootEntity = scene.createRootEntity();
|
||||
|
||||
// camera
|
||||
const cameraEntity = rootEntity.createChild("camera_node");
|
||||
cameraEntity.transform.position = new Vector3(0, 1, 5);
|
||||
cameraEntity.addComponent(Camera);
|
||||
cameraEntity.addComponent(OrbitControl).target = new Vector3(0, 1, 0);
|
||||
|
||||
const lightNode = rootEntity.createChild("light_node");
|
||||
lightNode.addComponent(DirectLight).intensity = 0.6;
|
||||
lightNode.transform.lookAt(new Vector3(0, 0, 1));
|
||||
lightNode.transform.rotate(new Vector3(0, 90, 0));
|
||||
|
||||
const promises: AssetPromise<GLTFResource>[] = [];
|
||||
// origin model
|
||||
promises.push(
|
||||
engine.resourceManager.load<GLTFResource>(
|
||||
"https://gw.alipayobjects.com/os/OasisHub/6f5b1918-1380-4641-a57a-7507503a524c/data.gltf"
|
||||
)
|
||||
);
|
||||
// animation
|
||||
promises.push(
|
||||
engine.resourceManager.load<GLTFResource>(
|
||||
"https://gw.alipayobjects.com/os/OasisHub/9ef53086-67d4-4be6-bff8-449a8074a5bd/data.gltf"
|
||||
)
|
||||
);
|
||||
|
||||
Promise.all(promises).then((resArr) => {
|
||||
const modelGLTF = resArr[0];
|
||||
const animationGLTF = resArr[1];
|
||||
const { animations: originAnimations = [] } = modelGLTF;
|
||||
const { animations = [] } = animationGLTF;
|
||||
const { defaultSceneRoot } = modelGLTF;
|
||||
rootEntity.addChild(defaultSceneRoot);
|
||||
const animator = defaultSceneRoot.getComponent(Animator);
|
||||
|
||||
const danceState = animator.animatorController.layers[0].stateMachine.addState("dance");
|
||||
danceState.clip = animations[0];
|
||||
|
||||
animator.play("dance");
|
||||
|
||||
const animationNames = originAnimations.map((clip) => clip.name);
|
||||
animationNames.push("dance");
|
||||
updateForE2E(engine);
|
||||
e2eReady();
|
||||
});
|
||||
});
|
||||
82
e2e/case/animator-stateMachineScript.ts
Normal file
82
e2e/case/animator-stateMachineScript.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* @title AnimatorStateScript
|
||||
* @category Animation
|
||||
*/
|
||||
import { OrbitControl } from "@galacean/engine-toolkit";
|
||||
import {
|
||||
Animator,
|
||||
AnimatorState,
|
||||
Camera,
|
||||
DirectLight,
|
||||
Font,
|
||||
FontStyle,
|
||||
GLTFResource,
|
||||
Logger,
|
||||
StateMachineScript,
|
||||
SystemInfo,
|
||||
TextRenderer,
|
||||
Vector3,
|
||||
WebGLEngine
|
||||
} from "@galacean/engine";
|
||||
import { e2eReady, updateForE2E } from "./.mockForE2E";
|
||||
|
||||
Logger.enable();
|
||||
WebGLEngine.create({ canvas: "canvas" }).then((engine) => {
|
||||
engine.canvas.resizeByClientSize(2);
|
||||
const scene = engine.sceneManager.activeScene;
|
||||
const rootEntity = scene.createRootEntity();
|
||||
|
||||
// camera
|
||||
const cameraEntity = rootEntity.createChild("camera_node");
|
||||
cameraEntity.transform.position = new Vector3(0, 1, 5);
|
||||
cameraEntity.addComponent(Camera);
|
||||
cameraEntity.addComponent(OrbitControl).target = new Vector3(0, 1, 0);
|
||||
|
||||
const lightNode = rootEntity.createChild("light_node");
|
||||
lightNode.addComponent(DirectLight).intensity = 0.6;
|
||||
lightNode.transform.lookAt(new Vector3(0, 0, 1));
|
||||
lightNode.transform.rotate(new Vector3(0, 90, 0));
|
||||
|
||||
// initText
|
||||
const textEntity = rootEntity.createChild("text");
|
||||
const textRenderer = textEntity.addComponent(TextRenderer);
|
||||
textEntity.transform.setPosition(0, 2, 0);
|
||||
textRenderer.fontSize = 12;
|
||||
textRenderer.font = Font.createFromOS(engine, "AlibabaSans");
|
||||
textRenderer.text = "";
|
||||
|
||||
engine.resourceManager
|
||||
.load<GLTFResource>("https://gw.alipayobjects.com/os/bmw-prod/5e3c1e4e-496e-45f8-8e05-f89f2bd5e4a4.glb")
|
||||
.then((gltfResource) => {
|
||||
const { animations = [], defaultSceneRoot } = gltfResource;
|
||||
rootEntity.addChild(defaultSceneRoot);
|
||||
|
||||
const animator = defaultSceneRoot.getComponent(Animator);
|
||||
const state = animator.findAnimatorState("walk");
|
||||
|
||||
state.addStateMachineScript(
|
||||
class extends StateMachineScript {
|
||||
onStateEnter(animator: Animator, animatorState: AnimatorState, layerIndex: number): void {
|
||||
textRenderer.text = "0";
|
||||
console.log("onStateEnter: ", animatorState);
|
||||
}
|
||||
|
||||
onStateUpdate(animator: Animator, animatorState: AnimatorState, layerIndex: number): void {
|
||||
console.log("onStateUpdate: ", animatorState);
|
||||
}
|
||||
|
||||
onStateExit(animator: Animator, animatorState: AnimatorState, layerIndex: number): void {
|
||||
textRenderer.text = "1";
|
||||
console.log("onStateExit: ", animatorState);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
animator.play("walk");
|
||||
|
||||
updateForE2E(engine, 30);
|
||||
animator.crossFade("run", 0.5, 0, 0);
|
||||
updateForE2E(engine, 100);
|
||||
e2eReady();
|
||||
});
|
||||
});
|
||||
3
e2e/fixtures/originImage/Animator_animator-additive.png
Normal file
3
e2e/fixtures/originImage/Animator_animator-additive.png
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c9f55ccf7d9ba3eeae64d80dec8a0f1d07a28708b6864200402611c5d70fe2f3
|
||||
size 31060
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6247265cc5a322be8c5870243db59fb945651e56f046a390991130aa20171a8e
|
||||
size 72841
|
||||
3
e2e/fixtures/originImage/Animator_animator-crossfade.png
Normal file
3
e2e/fixtures/originImage/Animator_animator-crossfade.png
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c053b0cc1ac48e4e764d5c16b0ca2d46b5f72e4d07b74ff3b779974af22f40a0
|
||||
size 30566
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2b2f8c4351d39e95152af5604858512e94157b58e74800ec5be8cc9c0344cabe
|
||||
size 14836
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f816564ea3b61a8f6ec6bb6ab55c4526278f15c3167493bd6e7ae7a8d96b9538
|
||||
size 9894
|
||||
3
e2e/fixtures/originImage/Animator_animator-event.png
Normal file
3
e2e/fixtures/originImage/Animator_animator-event.png
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:46d72de6ff304523602e6d651bd19de111d8a92c9ecac5854fcd8ae3686262f8
|
||||
size 30210
|
||||
3
e2e/fixtures/originImage/Animator_animator-play.png
Normal file
3
e2e/fixtures/originImage/Animator_animator-play.png
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d2ae9948fa4082b03e9a2b6e847b2e301497045a1e8a51aed55238dc67fcbb31
|
||||
size 31219
|
||||
3
e2e/fixtures/originImage/Animator_animator-reuse.png
Normal file
3
e2e/fixtures/originImage/Animator_animator-reuse.png
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9a7e5ed546549e84343f7a590eb7d01eed5a8716833b25e305d8356ab174aee6
|
||||
size 28309
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:92beef2371cf9b1cb897902ef47c58646b30133d8ebd418c39820931d226a8f0
|
||||
size 28203
|
||||
25
e2e/package.json
Normal file
25
e2e/package.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "@galacean/engine-e2e",
|
||||
"private": true,
|
||||
"version": "1.0.0-alpha.6",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"case": "vite serve .dev --config .dev/vite.config.js",
|
||||
"b:types": "echo hi"
|
||||
},
|
||||
"files": [
|
||||
],
|
||||
"dependencies": {
|
||||
"@galacean/engine-toolkit": "^1.0.0-beta.1",
|
||||
"@galacean/engine": "workspace:*",
|
||||
"@galacean/engine-core": "workspace:*",
|
||||
"@galacean/engine-loader": "workspace:*",
|
||||
"@galacean/engine-design": "workspace:*",
|
||||
"@galacean/engine-math": "workspace:*",
|
||||
"@galacean/engine-rhi-webgl": "workspace:*",
|
||||
"@galacean/engine-physics-lite": "workspace:*",
|
||||
"dat.gui": "^0.7.9",
|
||||
"vite": "^3.1.6",
|
||||
"sass": "^1.55.0"
|
||||
}
|
||||
}
|
||||
67
e2e/support/commands.ts
Normal file
67
e2e/support/commands.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { recurse } from "cypress-recurse";
|
||||
/// <reference types="cypress" />
|
||||
// ***********************************************
|
||||
// This example commands.ts shows you how to
|
||||
// create various custom commands and overwrite
|
||||
// existing commands.
|
||||
//
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
//
|
||||
//
|
||||
// -- This is a parent command --
|
||||
// Cypress.Commands.add('login', (email, password) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a child command --
|
||||
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a dual command --
|
||||
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
||||
//
|
||||
declare global {
|
||||
namespace Cypress {
|
||||
interface Chainable {
|
||||
screenshotWithThreshold(category: string, name: string, threshold?: number): Chainable<Element>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Cypress.Commands.add("screenshotWithThreshold", (category, name, threshold = 0) => {
|
||||
cy.visit(`/mpa/${name}.html`);
|
||||
|
||||
cy.get(".cypressReady").then(() => {
|
||||
return new Promise((resolve) => {
|
||||
const imageName = `${category}_${name}`;
|
||||
resolve(
|
||||
recurse(
|
||||
() => {
|
||||
return cy
|
||||
.get("#canvas")
|
||||
.screenshot(imageName, { overwrite: true, capture: "viewport" })
|
||||
.then(() => {
|
||||
return cy.task("compare", {
|
||||
fileName: imageName,
|
||||
options: {
|
||||
specFolder: Cypress.spec.name,
|
||||
threshold
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
({ match }) => match,
|
||||
{
|
||||
limit: 3
|
||||
}
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
20
e2e/support/e2e.ts
Normal file
20
e2e/support/e2e.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
// ***********************************************************
|
||||
// This example support/e2e.ts is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import "./commands";
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
||||
37
e2e/tests/animator.cy.ts
Normal file
37
e2e/tests/animator.cy.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
describe("Animator", () => {
|
||||
it("Animator Play", () => {
|
||||
cy.screenshotWithThreshold("Animator", "animator-play", 0.3);
|
||||
});
|
||||
|
||||
it("Animator Crossfade", () => {
|
||||
cy.screenshotWithThreshold("Animator", "animator-crossfade", 0.3);
|
||||
});
|
||||
|
||||
it("Animation Additive", () => {
|
||||
cy.screenshotWithThreshold("Animator", "animator-additive", 0.3);
|
||||
});
|
||||
|
||||
it("Animator Reuse", () => {
|
||||
cy.screenshotWithThreshold("Animator", "animator-reuse", 0.3);
|
||||
});
|
||||
|
||||
it("Animation BlendShape", () => {
|
||||
cy.screenshotWithThreshold("Animator", "animator-blendShape", 0.3);
|
||||
});
|
||||
|
||||
it("Animator CustomBlendShape", () => {
|
||||
cy.screenshotWithThreshold("Animator", "animator-customBlendShape", 0.3);
|
||||
});
|
||||
|
||||
it("Animator stateMachineScript", () => {
|
||||
cy.screenshotWithThreshold("Animator", "animator-stateMachineScript", 0.38);
|
||||
});
|
||||
|
||||
it("Animator event", () => {
|
||||
cy.screenshotWithThreshold("Animator", "animator-event", 0.38);
|
||||
});
|
||||
|
||||
it("Animator CustomAnimationClip", () => {
|
||||
cy.screenshotWithThreshold("Animator", "animator-customAnimationClip", 0.3);
|
||||
});
|
||||
});
|
||||
8
e2e/tsconfig.json
Normal file
8
e2e/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["es5", "dom"],
|
||||
"types": ["cypress", "node"]
|
||||
},
|
||||
"include": ["**/*.ts"]
|
||||
}
|
||||
@@ -18,6 +18,9 @@
|
||||
"b:miniprogram": "cross-env BUILD_TYPE=MINI rollup -c",
|
||||
"b:all": "npm run b:types && cross-env BUILD_TYPE=ALL rollup -c",
|
||||
"clean": "pnpm -r exec rm -rf dist && pnpm -r exec rm -rf types",
|
||||
"e2e:case": "pnpm -C ./e2e run case",
|
||||
"e2e": "cypress run --browser chrome --headless",
|
||||
"e2e:debug": "cypress open",
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -53,7 +56,11 @@
|
||||
"rollup-plugin-swc3": "^0.10.1",
|
||||
"ts-node": "^10",
|
||||
"typescript": "^5.1.6",
|
||||
"husky": "^8.0.0"
|
||||
"husky": "^8.0.0",
|
||||
"fs-extra": "^10.1.0",
|
||||
"cypress": "^12.17.1",
|
||||
"cypress-recurse": "^1.23.0",
|
||||
"odiff-bin": "^2.5.0"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{ts}": [
|
||||
|
||||
1084
pnpm-lock.yaml
generated
1084
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -2,4 +2,5 @@ packages:
|
||||
# all packages in direct subdirs of packages/
|
||||
- 'packages/*'
|
||||
# all packages in subdirs of components/
|
||||
- 'tests'
|
||||
- 'tests'
|
||||
- 'e2e'
|
||||
|
||||
Reference in New Issue
Block a user