Compare commits
8 Commits
37d7c37f3b
...
63aa7fd550
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63aa7fd550 | ||
|
|
dc1b29e52c | ||
|
|
9b8c4891f4 | ||
|
|
b4fcbed781 | ||
|
|
dc1ed8ffee | ||
|
|
57fb00f685 | ||
|
|
08a42801df | ||
|
|
be78b66c5a |
|
|
@ -1,15 +1,15 @@
|
||||||
<div class="card-div">
|
<div class="card-div">
|
||||||
<mat-card class="particle-card">
|
<mat-card class="particle-card">
|
||||||
<mat-card-header>
|
<mat-card-header>
|
||||||
<mat-card-title>Particle Properties</mat-card-title>
|
<mat-card-title>Particle input style</mat-card-title>
|
||||||
</mat-card-header>
|
</mat-card-header>
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<div class="particle-properties">
|
<div class="particle-properties">
|
||||||
<div class="property-row">
|
<div class="property-row">
|
||||||
<div class="color-picker">
|
<mat-form-field appearance="outline">
|
||||||
<input type="color" [(ngModel)]="selectedColor">
|
<mat-label>Current color: {{ selectedColor }}</mat-label>
|
||||||
<span>Current color: {{ selectedColor }}</span>
|
<input type="color" class="color-input" matInput [(ngModel)]="selectedColor">
|
||||||
</div>
|
</mat-form-field>
|
||||||
<mat-form-field appearance="outline" class="type-field">
|
<mat-form-field appearance="outline" class="type-field">
|
||||||
<mat-label>Select Particle Type</mat-label>
|
<mat-label>Select Particle Type</mat-label>
|
||||||
<input type="text"
|
<input type="text"
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,10 @@
|
||||||
.particle-properties {
|
.particle-properties {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 20px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.property-row {
|
.property-row {
|
||||||
|
margin-top: 20px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 15px;
|
gap: 15px;
|
||||||
|
|
@ -22,11 +22,8 @@
|
||||||
max-width: 40ch;
|
max-width: 40ch;
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-picker {
|
.color-input {
|
||||||
display: flex;
|
height: 1em;
|
||||||
flex: 1;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-picker input[type="color"] {
|
.color-picker input[type="color"] {
|
||||||
|
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
|
||||||
|
|
||||||
import {ParticleComponent} from './particle.component';
|
|
||||||
|
|
||||||
describe('ParticleComponent', () => {
|
|
||||||
let component: ParticleComponent;
|
|
||||||
let fixture: ComponentFixture<ParticleComponent>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
imports: [ParticleComponent]
|
|
||||||
})
|
|
||||||
.compileComponents();
|
|
||||||
|
|
||||||
fixture = TestBed.createComponent(ParticleComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -7,11 +7,22 @@
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline">
|
||||||
<mat-label>Particle Name</mat-label>
|
<mat-label>Particle Name</mat-label>
|
||||||
<input matInput [(ngModel)]="particleData.particle_name" placeholder="Enter particle name">
|
<input required matInput [(ngModel)]="particleData.particle_name" placeholder="Enter particle name">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline">
|
||||||
<mat-label>Display Name</mat-label>
|
<mat-label>Display Name</mat-label>
|
||||||
<input matInput [(ngModel)]="particleData.display_name" placeholder="Enter display name">
|
<input required matInput [(ngModel)]="particleData.display_name" placeholder="Enter display name">
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Permission name</mat-label>
|
||||||
|
<input required matInput [(ngModel)]="particleData.permission" placeholder="Enter permission">
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Package name</mat-label>
|
||||||
|
<input matInput [(ngModel)]="particleData.package_permission" placeholder="Enter package permission">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -26,25 +37,14 @@
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline">
|
||||||
<mat-label>Display Item</mat-label>
|
<mat-label>Display Item</mat-label>
|
||||||
<input matInput [(ngModel)]="particleData.display_item" placeholder="Enter display item">
|
<input required matInput [(ngModel)]="particleData.display_item" placeholder="Enter display item">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<mat-form-field appearance="outline" class="lore-double">
|
<mat-form-field appearance="outline" class="lore-double">
|
||||||
<mat-label>Lore</mat-label>
|
<mat-label>Lore</mat-label>
|
||||||
<textarea matInput [(ngModel)]="particleData.lore" placeholder="Enter lore"></textarea>
|
<textarea required matInput [(ngModel)]="particleData.lore" placeholder="Enter lore"></textarea>
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-row">
|
|
||||||
<mat-form-field appearance="outline">
|
|
||||||
<mat-label>Permission name</mat-label>
|
|
||||||
<input matInput [(ngModel)]="particleData.permission" placeholder="Enter permission">
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field appearance="outline">
|
|
||||||
<mat-label>Package name</mat-label>
|
|
||||||
<input matInput [(ngModel)]="particleData.package_permission" placeholder="Enter package permission">
|
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import {Component} from '@angular/core';
|
import {Component, inject} from '@angular/core';
|
||||||
import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from "@angular/material/card";
|
import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from "@angular/material/card";
|
||||||
import {MatCheckbox} from "@angular/material/checkbox";
|
import {MatCheckbox} from "@angular/material/checkbox";
|
||||||
import {MatFormField, MatInput, MatLabel} from "@angular/material/input";
|
import {MatFormField, MatInput, MatLabel} from "@angular/material/input";
|
||||||
|
|
@ -31,10 +31,7 @@ import {ParticleManagerService} from '../../services/particle-manager.service';
|
||||||
export class PropertiesComponent {
|
export class PropertiesComponent {
|
||||||
public particleTypes = Object.values(ParticleType);
|
public particleTypes = Object.values(ParticleType);
|
||||||
|
|
||||||
constructor(
|
private readonly particleManagerService = inject(ParticleManagerService);
|
||||||
private particleManagerService: ParticleManagerService,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public get particleData(): ParticleData {
|
public get particleData(): ParticleData {
|
||||||
return this.particleManagerService.getParticleData();
|
return this.particleManagerService.getParticleData();
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,18 @@
|
||||||
<mat-icon>location_searching</mat-icon>
|
<mat-icon>location_searching</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button mat-mini-fab color="primary" (click)="toggleShowParticlesWhenIntersectingPlane()"
|
||||||
|
[matTooltip]="onlyIntersecting ? 'Show all particles' : 'Show only intersecting particles'">
|
||||||
|
<mat-icon>{{ onlyIntersecting ? 'visibility_off' : 'visibility' }}</mat-icon>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button mat-mini-fab color="primary" (click)="toggleShowCharacter()"
|
||||||
|
[matTooltip]="showCharacter ? 'Hide character' : 'Show character'">
|
||||||
|
<mat-icon>{{ showCharacter ? 'person' : 'person_off' }}</mat-icon>
|
||||||
|
</button>
|
||||||
|
|
||||||
<button mat-mini-fab color="primary" (click)="togglePlaneLock()"
|
<button mat-mini-fab color="primary" (click)="togglePlaneLock()"
|
||||||
[matTooltip]="isPlaneLocked ? 'Unlock Plane' : 'Lock Plane'">
|
[matTooltip]="isPlaneLocked ? 'Unlock plane' : 'Lock plane'">
|
||||||
<mat-icon>{{ isPlaneLocked ? 'lock' : 'lock_open' }}</mat-icon>
|
<mat-icon>{{ isPlaneLocked ? 'lock' : 'lock_open' }}</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { RenderContainerComponent } from './render-container.component';
|
|
||||||
|
|
||||||
describe('RenderContainerComponent', () => {
|
|
||||||
let component: RenderContainerComponent;
|
|
||||||
let fixture: ComponentFixture<RenderContainerComponent>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
imports: [RenderContainerComponent]
|
|
||||||
})
|
|
||||||
.compileComponents();
|
|
||||||
|
|
||||||
fixture = TestBed.createComponent(RenderContainerComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import {AfterViewInit, Component, ElementRef, OnDestroy, ViewChild} from '@angular/core';
|
import {AfterViewInit, Component, ElementRef, inject, OnDestroy, ViewChild} from '@angular/core';
|
||||||
import {MatMiniFabButton} from '@angular/material/button';
|
import {MatMiniFabButton} from '@angular/material/button';
|
||||||
|
|
||||||
import {IntersectionPlaneService, PlaneOrientation} from '../../services/intersection-plane.service';
|
import {IntersectionPlaneService, PlaneOrientation} from '../../services/intersection-plane.service';
|
||||||
|
|
@ -9,6 +9,7 @@ import {PlayerModelService} from '../../services/player-model.service';
|
||||||
import {InputHandlerService} from '../../services/input-handler.service';
|
import {InputHandlerService} from '../../services/input-handler.service';
|
||||||
import {FormsModule} from '@angular/forms';
|
import {FormsModule} from '@angular/forms';
|
||||||
import {MatFormField, MatInput, MatLabel} from '@angular/material/input';
|
import {MatFormField, MatInput, MatLabel} from '@angular/material/input';
|
||||||
|
import {ParticleManagerService} from '../../services/particle-manager.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-render-container',
|
selector: 'app-render-container',
|
||||||
|
|
@ -27,13 +28,11 @@ import {MatFormField, MatInput, MatLabel} from '@angular/material/input';
|
||||||
export class RenderContainerComponent implements AfterViewInit, OnDestroy {
|
export class RenderContainerComponent implements AfterViewInit, OnDestroy {
|
||||||
@ViewChild('rendererContainer') rendererContainer!: ElementRef;
|
@ViewChild('rendererContainer') rendererContainer!: ElementRef;
|
||||||
|
|
||||||
constructor(
|
private readonly intersectionPlaneService = inject(IntersectionPlaneService);
|
||||||
private intersectionPlaneService: IntersectionPlaneService,
|
private readonly playerModelService = inject(PlayerModelService);
|
||||||
private playerModelService: PlayerModelService,
|
private readonly inputHandlerService = inject(InputHandlerService);
|
||||||
private inputHandlerService: InputHandlerService,
|
private readonly rendererService = inject(RendererService);
|
||||||
private rendererService: RendererService,
|
private readonly particleManagerService = inject(ParticleManagerService);
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
ngAfterViewInit(): void {
|
ngAfterViewInit(): void {
|
||||||
this.initializeScene();
|
this.initializeScene();
|
||||||
|
|
@ -99,6 +98,14 @@ export class RenderContainerComponent implements AfterViewInit, OnDestroy {
|
||||||
this.rendererService.resetCamera();
|
this.rendererService.resetCamera();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public toggleShowParticlesWhenIntersectingPlane(): void {
|
||||||
|
this.particleManagerService.onlyIntersectingParticles = !this.particleManagerService.onlyIntersectingParticles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public toggleShowCharacter(): void {
|
||||||
|
this.playerModelService.showCharacter = !this.playerModelService.showCharacter;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the current plane orientation
|
* Get the current plane orientation
|
||||||
*/
|
*/
|
||||||
|
|
@ -106,6 +113,20 @@ export class RenderContainerComponent implements AfterViewInit, OnDestroy {
|
||||||
return this.intersectionPlaneService.getCurrentOrientation();
|
return this.intersectionPlaneService.getCurrentOrientation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the value indicating whether only intersecting particles are being considered.
|
||||||
|
*/
|
||||||
|
public get onlyIntersecting(): boolean {
|
||||||
|
return this.particleManagerService.onlyIntersectingParticles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the value indicating whether the character is being rendered.
|
||||||
|
*/
|
||||||
|
public get showCharacter(): boolean {
|
||||||
|
return this.playerModelService.showCharacter;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the plane orientation
|
* Set the plane orientation
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import {Injectable} from '@angular/core';
|
import {Injectable} from '@angular/core';
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
import {RendererService} from './renderer.service';
|
import {RendererService} from './renderer.service';
|
||||||
|
import {Subject} from 'rxjs';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the possible orientations of the intersection plane
|
* Represents the possible orientations of the intersection plane
|
||||||
|
|
@ -27,6 +28,10 @@ export class IntersectionPlaneService {
|
||||||
private planeLocked: boolean = false;
|
private planeLocked: boolean = false;
|
||||||
private opacity: number = 0.05;
|
private opacity: number = 0.05;
|
||||||
|
|
||||||
|
// Emits whenever plane position, orientation, or lock-affecting orientation updates change visuals
|
||||||
|
public readonly planeChanged$ = new Subject<void>();
|
||||||
|
private lastPlaneSignature: string | null = null;
|
||||||
|
|
||||||
constructor(private rendererService: RendererService) {
|
constructor(private rendererService: RendererService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -144,6 +149,13 @@ export class IntersectionPlaneService {
|
||||||
this.intersectionPlane.position.x = -position;
|
this.intersectionPlane.position.x = -position;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Notify listeners only if signature changed to avoid spamming during animation frames
|
||||||
|
const signature = `${this.currentOrientation}|${this.planePosition}`;
|
||||||
|
if (signature !== this.lastPlaneSignature) {
|
||||||
|
this.lastPlaneSignature = signature;
|
||||||
|
this.planeChanged$.next();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
import {Injectable} from '@angular/core';
|
import {inject, Injectable} from '@angular/core';
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
import {RendererService} from './renderer.service';
|
import {RendererService} from './renderer.service';
|
||||||
import {Particle, ParticleData, ParticleInfo, ParticleType} from '../models/particle.model';
|
import {Particle, ParticleData, ParticleInfo, ParticleType} from '../models/particle.model';
|
||||||
|
import {IntersectionPlaneService, PlaneOrientation} from './intersection-plane.service';
|
||||||
|
import {deepCopy} from '../../../util/deep-copy.util';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service responsible for managing particles in the scene
|
* Service responsible for managing particles in the scene
|
||||||
|
|
@ -33,8 +35,18 @@ export class ParticleManagerService {
|
||||||
private selectedColor: string = '#ff0000';
|
private selectedColor: string = '#ff0000';
|
||||||
private selectedParticle: Particle = Particle.DUST;
|
private selectedParticle: Particle = Particle.DUST;
|
||||||
private selectedSize: number = 1;
|
private selectedSize: number = 1;
|
||||||
|
private onlyIntersecting: boolean = false;
|
||||||
|
|
||||||
constructor(private rendererService: RendererService) {
|
private readonly rendererService = inject(RendererService);
|
||||||
|
private readonly intersectionPlaneService = inject(IntersectionPlaneService);
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.intersectionPlaneService.planeChanged$.subscribe(() => {
|
||||||
|
if (this.onlyIntersecting) {
|
||||||
|
this.clearParticleVisuals();
|
||||||
|
this.renderFrameParticles(this.currentFrame);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -88,7 +100,34 @@ export class ParticleManagerService {
|
||||||
renderFrameParticles(frameId: string): void {
|
renderFrameParticles(frameId: string): void {
|
||||||
if (!this.particleData.frames[frameId]) return;
|
if (!this.particleData.frames[frameId]) return;
|
||||||
|
|
||||||
|
const filter = this.onlyIntersecting;
|
||||||
|
const orientation = this.intersectionPlaneService.getCurrentOrientation();
|
||||||
|
const offset16 = this.intersectionPlaneService.getPlanePosition();
|
||||||
|
const planePos = offset16 / 16; // convert from 1/16th units to world units
|
||||||
|
const epsilon = 0.02; // tolerance for intersection
|
||||||
|
|
||||||
|
const isOnPlane = (p: ParticleInfo) => {
|
||||||
|
if (!filter) return true;
|
||||||
|
switch (orientation) {
|
||||||
|
case PlaneOrientation.VERTICAL_ABOVE:
|
||||||
|
case PlaneOrientation.VERTICAL_BELOW:
|
||||||
|
// Horizontal plane at y = 0.8 +/- planePos
|
||||||
|
return Math.abs(p.y - (0.8 + (orientation === PlaneOrientation.VERTICAL_BELOW ? planePos : -planePos))) <= epsilon;
|
||||||
|
case PlaneOrientation.HORIZONTAL_FRONT:
|
||||||
|
return Math.abs(p.z - planePos) <= epsilon;
|
||||||
|
case PlaneOrientation.HORIZONTAL_BEHIND:
|
||||||
|
return Math.abs(p.z + planePos) <= epsilon;
|
||||||
|
case PlaneOrientation.HORIZONTAL_RIGHT:
|
||||||
|
return Math.abs(p.x - planePos) <= epsilon;
|
||||||
|
case PlaneOrientation.HORIZONTAL_LEFT:
|
||||||
|
return Math.abs(p.x + planePos) <= epsilon;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
for (const particleInfo of this.particleData.frames[frameId]) {
|
for (const particleInfo of this.particleData.frames[frameId]) {
|
||||||
|
if (!isOnPlane(particleInfo)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const particleGeometry = new THREE.SphereGeometry(0.03 * (particleInfo.size ?? 1), 16, 16);
|
const particleGeometry = new THREE.SphereGeometry(0.03 * (particleInfo.size ?? 1), 16, 16);
|
||||||
|
|
||||||
const color = this.getColor(particleInfo);
|
const color = this.getColor(particleInfo);
|
||||||
|
|
@ -243,6 +282,23 @@ export class ParticleManagerService {
|
||||||
* Generates JSON output of the particle data
|
* Generates JSON output of the particle data
|
||||||
*/
|
*/
|
||||||
generateJson(): string {
|
generateJson(): string {
|
||||||
|
const particleData = deepCopy(this.particleData)
|
||||||
|
if (this.particleData.package_permission) {
|
||||||
|
particleData.package_permission = 'apart.set.' + this.particleData.package_permission.toLowerCase().replace(' ', '-');
|
||||||
|
} else {
|
||||||
|
particleData.package_permission = 'apart.set.none';
|
||||||
|
}
|
||||||
|
particleData.permission = 'apart.particle.' + this.particleData.permission.toLowerCase().replace(' ', '-');
|
||||||
return JSON.stringify(this.particleData, null, 2);
|
return JSON.stringify(this.particleData, null, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get onlyIntersectingParticles(): boolean {
|
||||||
|
return this.onlyIntersecting;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set onlyIntersectingParticles(value: boolean) {
|
||||||
|
this.onlyIntersecting = value;
|
||||||
|
this.clearParticleVisuals();
|
||||||
|
this.renderFrameParticles(this.currentFrame);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import {Injectable} from '@angular/core';
|
import {inject, Injectable} from '@angular/core';
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
import {RendererService} from './renderer.service';
|
import {RendererService} from './renderer.service';
|
||||||
|
|
||||||
|
|
@ -6,13 +6,13 @@ import {RendererService} from './renderer.service';
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class PlayerModelService {
|
export class PlayerModelService {
|
||||||
|
private readonly rendererService = inject(RendererService);
|
||||||
|
|
||||||
private playerModel!: THREE.Group;
|
private playerModel!: THREE.Group;
|
||||||
private skinTexture!: THREE.Texture;
|
private skinTexture!: THREE.Texture;
|
||||||
|
private characterVisible: boolean = true;
|
||||||
private textureLoaded = false;
|
private textureLoaded = false;
|
||||||
|
|
||||||
constructor(private rendererService: RendererService) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads a Minecraft skin texture from a URL
|
* Loads a Minecraft skin texture from a URL
|
||||||
* @param textureUrl The URL of the skin texture to load
|
* @param textureUrl The URL of the skin texture to load
|
||||||
|
|
@ -60,6 +60,15 @@ export class PlayerModelService {
|
||||||
return this.playerModel;
|
return this.playerModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get showCharacter(): boolean {
|
||||||
|
return this.characterVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set showCharacter(showCharacter: boolean) {
|
||||||
|
this.playerModel.visible = showCharacter;
|
||||||
|
this.characterVisible = showCharacter;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a simple colored player model (without textures)
|
* Creates a simple colored player model (without textures)
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ export class RendererService {
|
||||||
this.themeService.theme$.subscribe(theme => {
|
this.themeService.theme$.subscribe(theme => {
|
||||||
this.currentTheme = theme;
|
this.currentTheme = theme;
|
||||||
if (this.scene) {
|
if (this.scene) {
|
||||||
this.setBackgroundColor(theme);
|
this.setBackgroundColor();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -32,7 +32,7 @@ export class RendererService {
|
||||||
initializeRenderer(container: ElementRef): void {
|
initializeRenderer(container: ElementRef): void {
|
||||||
// Create scene
|
// Create scene
|
||||||
this.scene = new THREE.Scene();
|
this.scene = new THREE.Scene();
|
||||||
this.setBackgroundColor(this.currentTheme);
|
this.setBackgroundColor();
|
||||||
|
|
||||||
// Get container dimensions
|
// Get container dimensions
|
||||||
const containerWidth = container.nativeElement.clientWidth;
|
const containerWidth = container.nativeElement.clientWidth;
|
||||||
|
|
@ -66,7 +66,7 @@ export class RendererService {
|
||||||
this.addLights();
|
this.addLights();
|
||||||
}
|
}
|
||||||
|
|
||||||
private setBackgroundColor(theme: THEME_MODE) {
|
private setBackgroundColor() {
|
||||||
this.scene.background = new THREE.Color(this.currentTheme === THEME_MODE.DARK ? 0x242526 : 0xFBFBFE);
|
this.scene.background = new THREE.Color(this.currentTheme === THEME_MODE.DARK ? 0x242526 : 0xFBFBFE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
10
frontend/src/app/util/deep-copy.util.ts
Normal file
10
frontend/src/app/util/deep-copy.util.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
export function deepCopy<T>(obj: T): T {
|
||||||
|
// Structured cloning
|
||||||
|
if (typeof globalThis?.structuredClone === 'function') {
|
||||||
|
// @ts-ignore
|
||||||
|
return globalThis.structuredClone(obj) as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normal objects
|
||||||
|
return JSON.parse(JSON.stringify(obj)) as T;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user