1
0
mirror of https://github.com/troisjs/trois.git synced 2024-11-24 04:12:02 +08:00
This commit is contained in:
Kevin Levron 2020-09-14 16:57:11 +02:00
commit a21071af6a
30 changed files with 683 additions and 0 deletions

40
.eslintrc.js Normal file
View File

@ -0,0 +1,40 @@
/* eslint-disable quote-props */
module.exports = {
env: {
browser: true,
es2020: true,
},
extends: [
'plugin:vue/essential',
'standard',
],
parserOptions: {
ecmaVersion: 12,
sourceType: 'module',
},
plugins: [
'vue',
],
rules: {
'semi': [2, 'always'],
'space-before-function-paren': 'off',
'one-var': 'off',
'quotes': 'off',
'quote-props': 'off',
'object-curly-newline': 'off',
'no-unused-vars': 'warn',
// 'comma-dangle': ['warn', 'always-multiline'],
'comma-dangle': ['warn', {
'arrays': 'always-multiline',
'objects': 'always-multiline',
'imports': 'always-multiline',
'exports': 'always-multiline',
'functions': 'never',
}],
'indent': 'warn',
'no-new': 'off',
'object-property-newline': 'off',
'eqeqeq': 'warn',
'no-multiple-empty-lines': 'off',
},
};

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
node_modules
.DS_Store
dist
*.local

13
index.html Normal file
View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

25
package.json Normal file
View File

@ -0,0 +1,25 @@
{
"name": "test-vite",
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "vite build"
},
"dependencies": {
"three": "^0.119",
"vue": "^3.0.0-rc.10",
"vuex": "^4.0.0-beta.4"
},
"devDependencies": {
"@vue/compiler-sfc": "^3.0.0-rc.10",
"eslint": "^7.7.0",
"eslint-config-airbnb-base": "^14.2.0",
"eslint-config-standard": "^14.1.1",
"eslint-plugin-import": "^2.22.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.1",
"eslint-plugin-vue": "^6.2.2",
"vite": "^1.0.0-rc.1"
}
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

14
src/App.vue Normal file
View File

@ -0,0 +1,14 @@
<template>
<Test></Test>
</template>
<script>
import Test from './components/Test.vue';
export default {
name: 'App',
components: {
Test,
},
};
</script>

59
src/components/Test.vue Normal file
View File

@ -0,0 +1,59 @@
<template>
<Renderer ref="renderer" :animate="anim">
<PerspectiveCamera :position="{ z: 100 }"></PerspectiveCamera>
<PhongMaterial name="mat1" color="#ff0000"></PhongMaterial>
<LambertMaterial name="mat2" color="#0000ff"></LambertMaterial>
<Scene>
<PointLight :position="{ x: 0, y: 50, z: 50 }"></PointLight>
<Box ref="box" :size="10" :rotation="{ x: 0.5, y: 0.25 }" material="mat1"></Box>
<Sphere ref="sphere" :radius="10" :position="{ x: 50 }" material="mat2"></Sphere>
</Scene>
</Renderer>
</template>
<script>
import {
Renderer, PerspectiveCamera, Scene,
PointLight,
Box, Sphere,
LambertMaterial, PhongMaterial,
} from '../index.js';
import { useAnimate } from '../core/Renderer.vue';
export default {
components: {
Renderer, PerspectiveCamera, Scene,
PointLight,
Box, Sphere,
LambertMaterial, PhongMaterial,
},
data() {
return {
anim: null,
};
},
mounted() {
console.log('Test mounted');
// useAnimate(() => {
// this.$refs.box.mesh.rotation.x += 0.01;
// });
// useAnimate(this.animate);
},
beforeUnmount() {
console.log('Test beforeUnmount');
},
methods: {
animate() {
this.$refs.box.mesh.rotation.x += 0.01;
// if (this.$refs.box) {
// this.$refs.box.mesh.rotation.x += 0.01;
// this.$refs.box.mesh.rotation.y += 0.013;
// this.$refs.box.mesh.rotation.z += 0.007;
// }
},
},
};
</script>

View File

@ -0,0 +1,26 @@
import { PerspectiveCamera, Vector3 } from 'three';
import { setFromProp } from '../tools.js';
export default {
inject: ['three'],
props: {
fov: {
type: Number,
default: 50,
},
position: Object,
// position: {
// type: Object,
// default: new Vector3(),
// },
},
created() {
const camera = new PerspectiveCamera(this.fov);
setFromProp(camera.position, this.position);
this.three.camera = camera;
},
render() {
return [];
},
};

70
src/core/Renderer.vue Normal file
View File

@ -0,0 +1,70 @@
<template>
<canvas ref="canvas">
<slot></slot>
</canvas>
</template>
<script>
import useThree from './useThree';
const animateCallbacks = [];
export function useAnimate(callback) {
animateCallbacks.push(callback);
};
export default {
props: {
alpha: {
type: Boolean,
default: false,
},
animate: {
type: Function,
},
},
data() {
return {
raf: true,
};
},
setup(props) {
return {
three: useThree(),
};
},
provide() {
return {
three: this.three,
};
},
mounted() {
// console.log('Renderer mounted');
this.three.init({
canvas: this.$refs.canvas,
});
this._animate();
},
beforeUnmount() {
// console.log('Renderer beforeUnmount');
// animateCallbacks.splice(0);
this.raf = false;
this.three.dispose();
},
methods: {
_animate() {
if (this.raf) requestAnimationFrame(this._animate);
// if (this.animate) this.animate();
animateCallbacks.forEach(c => c());
if (this.three.scene) this.three.render(this.three.scene);
},
},
};
</script>
<style>
canvas {
display: block;
}
</style>

22
src/core/Scene.js Normal file
View File

@ -0,0 +1,22 @@
import { Scene } from 'three';
export default {
emits: ['scene-ready'],
inject: ['three'],
setup (props) {
const scene = new Scene();
return { scene };
},
provide() {
return {
scene: this.scene,
};
},
mounted() {
this.three.scene = this.scene;
this.$emit('scene-ready');
},
render() {
return this.$slots.default();
},
};

3
src/core/index.js Normal file
View File

@ -0,0 +1,3 @@
export { default as Renderer } from './Renderer.vue';
export { default as PerspectiveCamera } from './PerspectiveCamera.js';
export { default as Scene } from './Scene.js';

175
src/core/useThree.js Normal file
View File

@ -0,0 +1,175 @@
import {
PerspectiveCamera,
Plane,
Raycaster,
Vector2,
Vector3,
WebGLRenderer,
} from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
/**
* Three.js helper
*/
export default function useThree() {
// default conf
const conf = {
canvas: null,
antialias: true,
alpha: false,
camera_fov: 50,
camera_pos: new Vector3(0, 0, 100),
camera_ctrl: false,
mouse_move: false,
mouse_raycast: false,
window_resize: true,
};
// size
const size = {
width: 0, height: 0,
wWidth: 0, wHeight: 0,
ratio: 0,
};
// mouse tracking
const mouse = new Vector2();
const mouseV3 = new Vector3();
const mousePlane = new Plane(new Vector3(0, 0, 1), 0);
const raycaster = new Raycaster();
// returned object
const obj = {
conf,
renderer: null,
camera: null,
cameraCtrl: null,
materials: {},
size,
mouse, mouseV3,
init,
dispose,
render,
setSize,
};
/**
* init three
*/
function init(params) {
if (params) {
for (const [key, value] of Object.entries(params)) {
conf[key] = value;
}
}
obj.renderer = new WebGLRenderer({ canvas: conf.canvas, antialias: conf.antialias, alpha: conf.alpha });
// obj.camera = new PerspectiveCamera(conf.camera_fov);
// obj.camera.position.copy(conf.camera_pos);
// if (conf.camera_ctrl) {
// obj.cameraCtrl = new OrbitControls(obj.camera, obj.renderer.domElement);
// if (conf.camera_ctrl instanceof Object) {
// for (const [key, value] of Object.entries(conf.camera_ctrl)) {
// obj.cameraCtrl[key] = value;
// }
// }
// }
if (conf.window_resize) {
onResize();
window.addEventListener('resize', onResize);
}
if (conf.mouse_move) {
obj.renderer.domElement.addEventListener('mousemove', onMousemove);
obj.renderer.domElement.addEventListener('mouseleave', onMouseleave);
}
return obj;
};
/**
* default render
*/
function render(scene) {
if (obj.cameraCtrl) obj.cameraCtrl.update();
obj.renderer.render(scene, obj.camera);
}
/**
* remove listeners
*/
function dispose() {
window.removeEventListener('resize', onResize);
obj.renderer.domElement.removeEventListener('mousemove', onMousemove);
obj.renderer.domElement.removeEventListener('mouseleave', onMouseleave);
}
/**
* mousemove listener
*/
function onMousemove(e) {
mouse.x = (e.clientX / size.width) * 2 - 1;
mouse.y = -(e.clientY / size.height) * 2 + 1;
updateMouseV3();
}
/**
* mouseleave listener
*/
function onMouseleave(e) {
mouse.x = 0;
mouse.y = 0;
updateMouseV3();
}
/**
* get 3d mouse position
*/
function updateMouseV3() {
if (conf.mouse_raycast) {
obj.camera.getWorldDirection(mousePlane.normal);
mousePlane.normal.normalize();
raycaster.setFromCamera(mouse, obj.camera);
raycaster.ray.intersectPlane(mousePlane, mouseV3);
}
}
/**
* resize listener
*/
function onResize() {
setSize(window.innerWidth, window.innerHeight);
}
/**
* update renderer size and camera
*/
function setSize(width, height) {
size.width = width;
size.height = height;
size.ratio = width / height;
obj.renderer.setSize(width, height, false);
obj.camera.aspect = size.ratio;
obj.camera.updateProjectionMatrix();
const wsize = getCameraSize();
size.wWidth = wsize[0]; size.wHeight = wsize[1];
}
/**
* calculate camera visible area size
*/
function getCameraSize() {
const vFOV = (obj.camera.fov * Math.PI) / 180;
const h = 2 * Math.tan(vFOV / 2) * Math.abs(obj.camera.position.z);
const w = h * obj.camera.aspect;
return [w, h];
}
return obj;
}

6
src/index.css Normal file
View File

@ -0,0 +1,6 @@
body {
margin: 0;
}
#app {
}

4
src/index.js Normal file
View File

@ -0,0 +1,4 @@
export * from './core/index.js';
export * from './lights/index.js';
export * from './materials/index.js';
export * from './meshes/index.js';

39
src/lights/Light.js Normal file
View File

@ -0,0 +1,39 @@
import {
Vector3,
} from 'three';
import { setFromProp } from '../tools.js';
export default {
inject: ['scene'],
props: {
color: {
type: String,
default: '#ffffff',
},
intensity: {
type: Number,
default: 1,
},
distance: {
type: Number,
default: 0,
},
decay: {
type: Number,
default: 1,
},
position: Object,
// position: {
// type: Object,
// default: new Vector3(0, 0, 0),
// },
},
mounted() {
setFromProp(this.light.position, this.position);
this.scene.add(this.light);
},
render() {
return [];
},
};

9
src/lights/PointLight.js Normal file
View File

@ -0,0 +1,9 @@
import { PointLight } from 'three';
import Light from './Light.js';
export default {
extends: Light,
created() {
this.light = new PointLight(this.color, this.intensity, this.distance, this.decay);
},
};

1
src/lights/index.js Normal file
View File

@ -0,0 +1 @@
export { default as PointLight } from './PointLight.js';

6
src/main.js Normal file
View File

@ -0,0 +1,6 @@
import { createApp } from 'vue';
import App from './App.vue';
import './index.css';
const app = createApp(App);
app.mount('#app');

View File

@ -0,0 +1,11 @@
import { MeshBasicMaterial } from 'three';
import Material from './Material';
export default {
extends: Material,
created() {
this.material = new MeshBasicMaterial({
color: this.color,
});
},
};

View File

@ -0,0 +1,11 @@
import { MeshLambertMaterial } from 'three';
import Material from './Material';
export default {
extends: Material,
created() {
this.material = new MeshLambertMaterial({
color: this.color,
});
},
};

16
src/materials/Material.js Normal file
View File

@ -0,0 +1,16 @@
export default {
inject: ['three'],
props: {
name: String,
color: {
type: String,
default: '#ffffff',
},
},
mounted() {
this.three.materials[this.name] = this.material;
},
render() {
return [];
},
};

View File

@ -0,0 +1,11 @@
import { MeshPhongMaterial } from 'three';
import Material from './Material';
export default {
extends: Material,
created() {
this.material = new MeshPhongMaterial({
color: this.color,
});
},
};

View File

@ -0,0 +1,11 @@
import { MeshPhysicalMaterial } from 'three';
import Material from './Material';
export default {
extends: Material,
created() {
this.material = new MeshPhysicalMaterial({
color: this.color,
});
},
};

View File

@ -0,0 +1,11 @@
import { MeshStandardMaterial } from 'three';
import Material from './Material';
export default {
extends: Material,
created() {
this.material = new MeshStandardMaterial({
color: this.color,
});
},
};

5
src/materials/index.js Normal file
View File

@ -0,0 +1,5 @@
export { default as BasicMaterial } from './BasicMaterial.js';
export { default as LambertMaterial } from './LambertMaterial.js';
export { default as PhongMaterial } from './PhongMaterial.js';
export { default as PhysicalMaterial } from './PhysicalMaterial.js';
export { default as StandardMaterial } from './StandardMaterial.js';

33
src/meshes/Box.js Normal file
View File

@ -0,0 +1,33 @@
import {
BoxBufferGeometry,
} from 'three';
import Mesh from './Mesh.js';
export default {
extends: Mesh,
props: {
size: {
type: Number,
},
width: {
type: Number,
default: 1,
},
height: {
type: Number,
default: 1,
},
depth: {
type: Number,
default: 1,
},
},
created() {
if (this.size) {
this.geometry = new BoxBufferGeometry(this.size, this.size, this.size);
} else {
this.geometry = new BoxBufferGeometry(this.width, this.height, this.depth);
}
},
};

34
src/meshes/Mesh.js Normal file
View File

@ -0,0 +1,34 @@
import { Mesh } from 'three';
import { setFromProp } from '../tools.js';
export default {
inject: ['three', 'scene'],
props: {
material: String,
position: Object,
rotation: Object,
scale: Object,
// position: {
// type: Object,
// default: new Vector3(),
// },
// rotation: {
// type: Object,
// default: new Euler(),
// },
// scale: {
// type: Object,
// default: new Vector3(1, 1, 1),
// },
},
mounted() {
this.mesh = new Mesh(this.geometry, this.three.materials[this.material]);
setFromProp(this.mesh.position, this.position);
setFromProp(this.mesh.rotation, this.rotation);
setFromProp(this.mesh.scale, this.scale);
this.scene.add(this.mesh);
},
render() {
return [];
},
};

15
src/meshes/Sphere.js Normal file
View File

@ -0,0 +1,15 @@
import {
SphereBufferGeometry,
} from 'three';
import Mesh from './Mesh.js';
export default {
extends: Mesh,
props: {
radius: Number,
},
created() {
this.geometry = new SphereBufferGeometry(this.radius, 32, 32);
},
};

2
src/meshes/index.js Normal file
View File

@ -0,0 +1,2 @@
export { default as Box } from './Box.js';
export { default as Sphere } from './Sphere.js';

7
src/tools.js Normal file
View File

@ -0,0 +1,7 @@
export function setFromProp(o, prop) {
if (prop instanceof Object) {
for (const [key, value] of Object.entries(prop)) {
o[key] = value;
}
}
};