particle system as interactive object

This commit is contained in:
2025-11-05 17:31:21 +02:00
parent 4b722750c3
commit fd3f9a6d69
11 changed files with 165 additions and 84 deletions
@@ -0,0 +1,11 @@
class GenericObject{
constructor(engine, data){
return new Promise(async(resolve, reject)=>{
this.source = await engine.load(data.$go.asset.name);
this.object = this.source.scene;
resolve(this);
})
}
}
export {GenericObject}
@@ -1,10 +1,28 @@
<template>
<div>
<v-textarea :label="l.description" v-model="modelValue.description"></v-textarea>
<v-checkbox v-model="modelValue.hud" label="Observe in head-up display"></v-checkbox>
<v-img :src="`/asset/thumb/${modelValue.go}.webp`" />
<div class="text-caption text-center">{{ modelValue.title }}</div>
</div>
</template>
<script>
export default {
//for now this will be dummy
data(){
return {
active: false
}
},
mounted(){
this.active = true;
},
props:{
modelValue: Object
},
methods:{
}
}
</script>
@@ -1,65 +0,0 @@
import {
Matrix4,
Mesh,
PlaneGeometry,
MeshPhongMaterial,
Vector3,
TextureLoader
} from 'three';
import * as BufferGeometryUtils from 'three/addons/utils/BufferGeometryUtils.js';
class Grass {
constructor(positions, texture, x, y) {
return new Promise((resolve, reject) => {
var tLoader = new TextureLoader();
tLoader.load(texture, (t) => {
//t.encoding = sRGBEncoding;
// create the initial geometry
var geometry = new PlaneGeometry(x || 1, y || 1);
var material = new MeshPhongMaterial({
map: t,
alphaTest: .5,
});
geometry.applyMatrix4(new Matrix4().makeTranslation(0, geometry.parameters.height / 2, 0));
geometry.normalizeNormals();
var arr = [];
for (var i = 0; i < positions.length; i++) {
var position = positions[i];
var baseAngle = Math.PI * 2 * Math.random();
var nPlanes = 2;
for (var j = 0; j < nPlanes; j++) {
var angle = baseAngle + j * Math.PI / nPlanes;
var gg = geometry.clone();
gg.rotateY(angle);
gg.translate(position.x, position.y, position.z);
arr.push(gg);
var gg = geometry.clone();
gg.rotateY(angle + Math.PI);
gg.translate(position.x, position.y, position.z);
arr.push(gg);
}
}
var mesh = new Mesh(BufferGeometryUtils.mergeGeometries(arr, false), material);
resolve(mesh);
arr.forEach(g => g.dispose());
});
});
}
static positions(count, x, z) {
var positions = new Array(count);
for (var i = 0; i < count; i++) {
var position = new Vector3();
position.x = (Math.random() - 0.5) * x;
position.z = (Math.random() - 0.5) * z;
positions[i] = position;
}
return positions;
}
}
export { Grass }
@@ -1,4 +1,6 @@
import { Group } from "three";
import { GenericObject } from "./GenenricObject";
import { PuzzleGame1 } from "./PuzzleGame1";
import { PuzzleGame2 } from "./PuzzleGame2";
// import { Game3 } from "./games/Game3";
@@ -9,9 +11,10 @@ import { TextObject } from "./TextObject";
import { ImageObject } from "./ImageObject";
import { VideoPlayer } from "./VideoPlayer";
import { MazeQuizGame } from "./MazeQuizGame/MazeQuizGame";
import { Particles } from "./Particles";
import { assignMaterial, assignParams } from "@/lib/MeshUtils";
const games = {PuzzleGame1, PuzzleGame2, PuzzleGame4, MazeQuizGame};
const InteractiveObjectsImports = { PuzzleGame1, PuzzleGame2, PuzzleGame4, MazeQuizGame, Particles };
class InteractiveObject {
constructor(obj, gameEngine, params) {
@@ -58,14 +61,15 @@ class InteractiveObject {
this.source = gltf;
break;
case 'GenericObject':
this.source = await gameEngine.load(obj.$go.asset.name);
this.object = this.source.scene;
let go = await new GenericObject(gameEngine, obj) //await gameEngine.load(obj.$go.asset.name);
this.source = go.source;
this.object = go.object;
break;
case 'PuzzleGame3':
case 'PuzzleGame4':
case 'PuzzleGame5':
case 'PuzzleGame6':
let game = new games[obj.type](gameEngine, obj.args[0], obj.args[1], obj.args[2]);
let game = new InteractiveObjectsImports[obj.type](gameEngine, obj.args[0], obj.args[1], obj.args[2]);
this.object = game.object;
this.object.game = game;
break;
@@ -77,7 +81,8 @@ class InteractiveObject {
case 'PuzzleGame1':
case 'PuzzleGame2':
case 'MazeQuizGame':
this.source = await new games[obj.type](gameEngine, obj);
case 'Particles':
this.source = await new InteractiveObjectsImports[obj.type](gameEngine, obj);
this.object = this.source.object;
break;
}
@@ -96,7 +101,9 @@ const InteractiveObjectTypes = [
id: 'MazeQuizGame', name: 'Maze Quiz Game'
},{
id: 'VideoPlayer', name: 'Video Player'
},{
id: 'Particles', name: 'Particles'
}
];
export { InteractiveObject, InteractiveObjectTypes }
export { InteractiveObject, InteractiveObjectTypes, InteractiveObjectsImports }
@@ -0,0 +1,62 @@
import {
Matrix4,
Mesh,
PlaneGeometry,
MeshPhongMaterial,
Vector3,
} from 'three';
import * as BufferGeometryUtils from 'three/addons/utils/BufferGeometryUtils.js';
class Particles {
constructor(engine, data) {
const { x, y, count, w, h } = data;
let positions = Particles.positions(count, w, h);
return new Promise(async (resolve, reject) => {
let t = await engine.loadTexture(data.$go.asset.name)
var geometry = new PlaneGeometry(x || 1, y || 1);
var material = new MeshPhongMaterial({
map: t,
alphaTest: .5,
});
geometry.applyMatrix4(new Matrix4().makeTranslation(0, geometry.parameters.height / 2, 0));
geometry.normalizeNormals();
var arr = [];
for (var i = 0; i < positions.length; i++) {
var position = positions[i];
var baseAngle = Math.PI * 2 * Math.random();
var nPlanes = 2;
for (var j = 0; j < nPlanes; j++) {
var angle = baseAngle + j * Math.PI / nPlanes;
var gg = geometry.clone();
gg.rotateY(angle);
gg.translate(position.x, position.y, position.z);
arr.push(gg);
var gg = geometry.clone();
gg.rotateY(angle + Math.PI);
gg.translate(position.x, position.y, position.z);
arr.push(gg);
}
}
this.object = new Mesh(BufferGeometryUtils.mergeGeometries(arr, false), material);
resolve(this);
arr.forEach(g => g.dispose());
});
}
static positions(count, x, z) {
let positions = new Array(count);
for (var i = 0; i < count; i++) {
var position = new Vector3();
position.x = (Math.random() - 0.5) * x;
position.z = (Math.random() - 0.5) * z;
positions[i] = position;
}
return positions;
}
}
export { Particles }
@@ -0,0 +1,49 @@
<template>
<v-card v-if="modelValue.go">
<v-card-item>
<v-number-input density="compact" :precision="null" label="Particle width" v-model="modelValue.x"></v-number-input>
<v-number-input density="compact" :precision="null" label="Particle height" v-model="modelValue.y"></v-number-input>
<v-number-input density="compact" :precision="null" label="Area width" v-model="modelValue.w"></v-number-input>
<v-number-input density="compact" :precision="null" label="Area length" v-model="modelValue.h"></v-number-input>
<v-number-input density="compact" label="Count" v-model="modelValue.count"></v-number-input>
<v-img :src="`/asset/thumb/${modelValue.go}.webp`" />
<div class="text-caption text-center">{{ modelValue.title }}</div>
</v-card-item>
</v-card>
<asset-selector @select="assignTexture" :type="['Texture']">
<template v-slot:activator="props">
<v-btn v-bind="props" prepend-icon="mdi-video-box" color="deep-orange-darken-4" block>Choose image object</v-btn>
</template>
</asset-selector>
</template>
<script>
//Grass.positions(1000,50,50), '/static/textures/grass01.png', 1, .5
export default {
data(){
return {
active: false
}
},
mounted(){
this.active = true;
},
props:{
modelValue: Object
},
methods:{
assignTexture(e){
this.modelValue.go = e.id;
this.modelValue.title = e.name
this.modelValue.x = 1;
this.modelValue.y = 1;
this.modelValue.count = 1000;
this.modelValue.w = 50;
this.modelValue.h = 50;
}
}
}
</script>
+2 -2
View File
@@ -18,7 +18,6 @@
</asset-selector>
<v-form class="pt-4">
<v-text-field density="compact" :label="l.name" v-model="modelValue.title"></v-text-field>
<v-textarea :label="l.description" v-model="modelValue.description"></v-textarea>
<v-text-field density="compact" :label="l.id" v-model="modelValue.id"></v-text-field>
</v-form>
</v-card>
@@ -36,11 +35,12 @@ import VideoPlayer from '../InteractiveObjects/VideoPlayer.vue';
import PuzzleGame1 from '../InteractiveObjects/PuzzleGame1.vue';
import PuzzleGame2 from '../InteractiveObjects/PuzzleGame2.vue';
import MazeQuizGame from '../InteractiveObjects/MazeQuizGame/MazeQuizGame.vue';
import Particles from '../InteractiveObjects/Particles.vue';
import GenericObject from '../InteractiveObjects/GenericObject.vue';
export default {
emits:['target', 'preview'],
components: { SvgIcon, VideoPlayer, PuzzleGame1, PuzzleGame2, MazeQuizGame, GenericObject },
components: { SvgIcon, GenericObject, VideoPlayer, PuzzleGame1, PuzzleGame2, MazeQuizGame, Particles, },
data(){
return {
active: false