This commit is contained in:
2025-08-28 20:14:37 +03:00
parent 6d913c7d7c
commit 7eb7eb068a
9 changed files with 68 additions and 10 deletions
+5 -5
View File
@@ -54,11 +54,11 @@
import { GameEngine } from '@/lib/GameEngine';
import { Hero } from '@/lib/Hero';
import { Game1 } from '@/lib/interactive-objects/Game1';
import { Game2 } from '@/lib/interactive-objects/Game2';
import { Game4 } from '@/lib/interactive-objects/Game4';
import { Grass } from '@/lib/interactive-objects/Grass';
import { VideoPlayer } from '@/lib/interactive-objects/VideoPlayer';
import { Game1 } from '@/components/InteractiveObjects/PuzzleGame1';
import { Game2 } from '@/components/InteractiveObjects/PuzzleGame2';
import { Game4 } from '@/components/InteractiveObjects/PuzzleGame4';
import { Grass } from '@/components/InteractiveObjects/Grass';
import { VideoPlayer } from '@/components/InteractiveObjects/VideoPlayer';
import { useAppStore } from '@/stores/app';
const store = useAppStore();
@@ -0,0 +1,65 @@
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 }
@@ -0,0 +1,96 @@
import { BoxGeometry, Mesh, MeshBasicMaterial, Group } from 'three';
import { TextureLoader } from 'three/src/loaders/TextureLoader';
import { MotionEngine } from '../../lib/MotionEngine';
class Game1 {
constructor(context, image, w, h) {
this.game = new Group();
const aq = new MotionEngine();
const pr = [[0, -1], [0, 1], [1, 0], [-1, 0], [0, 0], [0, 2]];
let d = 1.2;
let bm = new BoxGeometry(1, 1, 1);
let uv = bm.getAttribute('uv');
for (let i = 0; i < 6; i++) {
let s = [(i % w) / w, (i % h) / h];
//top left
uv.array[8 * i] = s[0];
uv.array[8 * i + 1] = s[1] + 1 / h;
//top right
uv.array[8 * i + 2] = s[0] + 1 / w;
uv.array[8 * i + 3] = s[1] + 1 / h;
//bottom left
uv.array[8 * i + 4] = s[0];
uv.array[8 * i + 5] = s[1];
//bottom right
uv.array[8 * i + 6] = s[0] + 1 / w;
uv.array[8 * i + 7] = s[1];
}
let material = new MeshBasicMaterial({
map: new TextureLoader().load(image)
});
//material.map.encoding = sRGBEncoding;
for (let i = 0; i < 6; i++) {
let b = bm.clone();
let mesh = new Mesh(b, material);
mesh.position.set((i % w) * d, (i % h) * d, 0);
let ri;
do {
ri = Math.floor(Math.random() * 6);
} while (ri == this.game.children.length);
mesh.rotation.set(pr[ri][0] * Math.PI / 2, pr[ri][1] * Math.PI / 2, 0);
mesh._ri = ri;
this.game.add(mesh);
}
this.game.children[0].onBeforeRender = () => {
this.update();
};
var check = () => {
if (!this.game.children.length) return false;
let i = 0;
for (let c of this.game.children) {
if (Math.abs(c.rotation.x - pr[i][0] * Math.PI / 2) > 0.0001 || Math.abs(c.rotation.y - pr[i][1] * Math.PI / 2) > 0.0001) return false;
i++;
}
return true;
};
let clickFn = (i) => {
if (!this.done && !aq.isActive(i.object)) {
i.object._ri = (i.object._ri + 1) % 6;
aq.add({
o: i.object,
a: { rotation: { x: pr[i.object._ri][0] * Math.PI / 2, y: pr[i.object._ri][1] * Math.PI / 2 } },
t: .5
});
}
};
this.game.children.forEach(c => {
context.clickable.add(c, clickFn);
});
this.update = () => {
aq.update();
if (aq.isIdle() && !this.done && check()) {
this.done = true;
this.game.children.forEach((c, i) => {
aq.add({
o: c,
a: { position: { x: i % w, y: i % h } },
t: 1,
f: i == 0 && this.onfinish
});
});
//context.dashboard.addPoints(10);
}
};
}
}
export {Game1}
@@ -0,0 +1,146 @@
import { BoxGeometry, Mesh, MeshBasicMaterial, Group } from 'three';
import { TextureLoader } from 'three/src/loaders/TextureLoader';
import { MotionEngine } from '../../lib/MotionEngine';
class Game2 {
constructor(context, image, w, h) {
const texture = new TextureLoader().load(image);
//texture.encoding = sRGBEncoding;
const material = new MeshBasicMaterial({
map: texture
});
const aq = new MotionEngine();
const m2 = new MeshBasicMaterial({
map: texture,
transparent: true,
opacity: 0.37
});
let last, lidx = w - 1;
this.game = new Group();
let d = 1.2, p = [];
function check() {
if (p.length) {
for (let i = 0; i < p.length; i++) {
if (p[i] != i) return false;
}
return true;
}
return false;
}
function xch(x, y) {
let temp = p[x];
p[x] = p[y];
p[y] = temp;
}
this.shuffle = function () {
function getMoves() {
let m = [], pl = p[lidx];
let xl = pl % w, yl = ~~(pl / h);
if (xl > 0) m.push(p.indexOf(pl - 1));
if (xl < w - 1) m.push(p.indexOf(pl + 1));
if (yl > 0) m.push(p.indexOf(pl - w));
if (yl < h - 1) m.push(p.indexOf(pl + w));
return m;
}
for (let i = 0; i < w * h; i++) {
p[i] = i;
}
for (let iter = 0; iter < 73 * w * h; iter++) {
let m = getMoves();
xch(m[Math.floor(Math.random() * m.length)], lidx);
}
// while (p.length<9){
// let n = Math.floor(Math.random()*9);
// if (p.indexOf(n) == -1) p.push(n);
// }
p.forEach((e, i) => {
let x = e % w, y = ~~(e / h);
this.game.children[i].position.set(x * d, y * d, 0);
});
};
for (let i = 0; i < w * h; i++) {
let x = i % w, xp = x / w;
let y = ~~(i / h), yp = y / h;
let bg = new BoxGeometry(1, 1, 1);
let uv = bg.getAttribute('uv');
for (let k = 0; k < 6; k++) {
//top left
uv.array[8 * k] = xp;
uv.array[8 * k + 1] = yp + 1 / h;
//top right
uv.array[8 * k + 2] = xp + 1 / w;
uv.array[8 * k + 3] = yp + 1 / h;
//bottom left
uv.array[8 * k + 4] = xp;
uv.array[8 * k + 5] = yp;
//bottom right
uv.array[8 * k + 6] = xp + 1 / w;
uv.array[8 * k + 7] = yp;
}
let mesh = new Mesh(bg, i != lidx ? material : m2);
mesh.position.set(x * d, y * d, 0);
this.game.add(mesh);
}
last = this.game.children[lidx];
this.game.children[0].onBeforeRender = () => {
this.update();
};
this.shuffle();
let clickFn = (i) => {
if (!this.done && !aq.isActive(i.object)) {
let idx = this.game.children.indexOf(i.object);
if (idx == lidx) return; //we ignore the empty cell
let xc = p[idx] % w, yc = ~~(p[idx] / h);
let xl = p[lidx] % w, yl = ~~(p[lidx] / h);
if (Math.abs(xc - xl) + Math.abs(yc - yl) == 1) {
aq.add({
o: i.object,
a: { position: { x: (xl - xc) * d, y: (yl - yc) * d } },
t: .3,
m: 'offset'
});
aq.add({
o: last,
a: { position: { x: (xc - xl) * d, y: (yc - yl) * d } },
t: .3,
m: 'offset'
});
xch(idx, lidx);
}
}
};
this.game.children.forEach(c => {
context.clickable.add(c, clickFn);
});
this.update = () => {
aq.update();
if (aq.isIdle() && !this.done && check()) {
this.done = true;
this.game.children.forEach((c, i) => {
last.material = material;
aq.add({
o: c,
a: { position: { x: i % w, y: ~~(i / h) } },
t: 1,
f: i == 0 && this.onfinish
});
});
//context.dashboard.addPoints(10);
}
};
}
}
export { Game2 };
@@ -0,0 +1,124 @@
import { Group, RGBAFormat } from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { MotionEngine } from '../../lib/MotionEngine';
var Game4 = function(context, gltf, w, h){
this.game = new Group();
const aq = new MotionEngine();
const pr = [];
let d = .51, c = w * h / 2, tc = w * h, m0=1, r0=.2;
let lastClicked;
function setMaterial(m, v){
if (v){
m.metalness = .5;
m.roughness = .383;
}else{
m.metalness = m0;
m.roughness = r0;
m.transparent = true;
m.format = RGBAFormat;
}
}
new GLTFLoader().load(gltf, gltf=>{
for (let i = 0; i<c; i++){
let o = gltf.scene.getObjectByName('c'+(i+1));
let ref = [];
for (let j = 0; j<2; j++){
let position;
do {
position = Math.floor(Math.random() * tc);
}while (pr[position]);
ref[j] = o.clone();
ref[j].material = o.material.clone();
setMaterial(ref[j].material, 0);
pr[position] = ref[j];
}
ref[0].$ref = ref[1];
ref[1].$ref = ref[0];
}
pr.forEach((c, i)=>{
c.position.set((i % w)*d , (~~(i / w))*d);
c.rotation.set(0, Math.PI, 0);
this.game.add(c);
context.clickable.add(c, clickFn);
})
this.game.children[0].onBeforeRender = ()=>{
this.update();
}
});
var check = ()=>{
if (!this.game.children.length) return false;
return pr.filter(c=>c.$active === false).length == tc;
}
let clickFn = (i)=>{
let clicked = i.object;
if (this.done && clicked){
aq.add({
o: clicked,
a: {rotation:{y: Math.PI}},
t: 1,
m: 'offset'
});
}
if (!clicked || clicked.$active === false || aq.isActive(clicked)) return;
setMaterial(clicked.material, 1);
let f;
if (lastClicked && lastClicked.$ref == clicked){
clicked.$active = lastClicked.$active = false;
[clicked, lastClicked].forEach(c => {
aq.add({
o: c,
a: {material:{opacity:0}},
t: 1,
d:1
});
});
lastClicked = null;
}else if(!lastClicked){
lastClicked = clicked;
}else{
f = ()=>{
setTimeout(()=>{
[clicked, lastClicked].filter(c=>c).forEach(c=>{
aq.add({
o: c,
a: {rotation:{y: Math.PI}, material:{metalness:m0, roughness:r0}},
t: 1
});
});
lastClicked = null;
}, 500);
}
}
aq.add({
o: clicked,
a: {rotation:{y: 0}},
t: .5,
f
});
}
this.update = ()=>{
aq.update();
if (!this.done && check()){
this.done = true;
this.game.children.forEach((c, i)=>{
aq.add({
o: c,
a: {material:{opacity:1}},
t: 1,
d:1+0.1*i
})
})
this.onfinish && this.onfinish();
//context.dashboard.addPoints(10);
}
}
}
export { Game4 }
@@ -0,0 +1,29 @@
import * as THREE from 'three';
class VideoPlayer {
constructor(context, video, w, h){
let geometry = new THREE.PlaneGeometry( w, h );
let map = new THREE.VideoTexture( video );
map.colorSpace = THREE.SRGBColorSpace;
let material = new THREE.MeshStandardMaterial( {
color: 0xffffff,
map,
transparent: true,
opacity: 0.5,
} );
let plane = new THREE.Mesh( geometry, material );
this.videoPlayer = plane;
context.clickable.add(plane, ()=>{
material.opacity = 0.9
if (video.paused){
video.play();
}else{
video.pause();
}
});
}
}
export {VideoPlayer}
@@ -0,0 +1,60 @@
<template>
<v-card v-if="selected" :title="modelValue.title" class="mx-2" variant="text">
<asset-selector @select="assignVideoObject" :type="['Descriptive']">
<template v-slot:activator="props">
<v-btn v-bind="props" prepend-icon="mdi-panorama-outline" color="success" block>Choose video object</v-btn>
</template>
</asset-selector>
<v-form class="py-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>
</template>
<script>
import Utils from '@/lib/utils';
export default {
emits:['target', 'preview'],
data(){
return {
active: false
}
},
mounted(){
this.active = true;
},
props:{
//context: Object,
modelValue: Object,
vd: Object,
selected: Boolean,
cid:String,
visible: Boolean,
parent: Object
},
computed:{
showInView(){
this.vd.__showInView = this.visible && this.parent.visible;
return this.vd.__showInView;
}
},
steps: [['x1', 'y1']],
name: 'game-object',
modifiers: ['x1', 'y1'],
methods:{
intersect(v){
return Utils.intersectPointRect([this.vd.x1, this.vd.y1], v);
},
assignGameObject(e){
this.modelValue.go = e.id;
if (this.modelValue.id == this.modelValue.title){
this.modelValue.title = e.name
}
}
}
}
</script>