mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-27 04:29:50 +01:00
Compare commits
6
Commits
05572918b1
...
c85cd36818
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c85cd36818 | ||
|
|
5d2c63fed9 | ||
|
|
cead140855 | ||
|
|
63a0143d4f | ||
|
|
19f22993f1 | ||
|
|
b2eb74914f |
No files matched your search
@@ -1,4 +1,4 @@
|
||||
# BlitzKit 1.30.3
|
||||
# BlitzKit 1.30.4
|
||||
|
||||
## Fixes
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
# BlitzKit 1.30.5
|
||||
|
||||
## Features
|
||||
|
||||
- Reduced the amount of code that executes for every rendered frame, hopefully improving performance
|
||||
- Reduced the number of lights by 20% to decrease the amount of shadow calculations
|
||||
- Decreased the ground size by 80% for an equal reduction in shadow texture sizes
|
||||
- Added the ability for BlitzKit to dismount the components that handled the intro lighting animation when not animating, reducing checks running every frame
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "blitzkit",
|
||||
"version": "1.30.4",
|
||||
"version": "1.30.5",
|
||||
"license": "BlitzKit License",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { J_HAT, K_HAT } from "@blitzkit/core";
|
||||
import { Box, Flex, Text } from "@radix-ui/themes";
|
||||
import { Html } from "@react-three/drei";
|
||||
import { invalidate, useFrame } from "@react-three/fiber";
|
||||
import { useFrame } from "@react-three/fiber";
|
||||
import { clamp } from "lodash-es";
|
||||
import { useEffect, useRef, type ComponentProps } from "react";
|
||||
import { useEffect, useRef, type ComponentProps, type RefObject } from "react";
|
||||
import {
|
||||
BufferGeometry,
|
||||
DoubleSide,
|
||||
@@ -42,11 +42,10 @@ const TRACER_THIN = TRACER_THICK / 2;
|
||||
|
||||
export function ShotDisplay() {
|
||||
const shot = Tankopedia.use((state) => state.shot);
|
||||
const inTracer = useRef<Mesh>(null);
|
||||
const outTracer = useRef<Mesh>(null);
|
||||
const splashRadiusWrapper = useRef<Group>(null);
|
||||
const splashRadiusMaterial = useRef<MeshBasicMaterial>(null);
|
||||
let animationStartTime: number | null = null;
|
||||
const inTracer = useRef<Mesh>(null!);
|
||||
const outTracer = useRef<Mesh>(null!);
|
||||
const splashRadiusWrapper = useRef<Group>(null!);
|
||||
const splashRadiusMaterial = useRef<MeshBasicMaterial>(null!);
|
||||
const { locale } = useLocale();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -64,10 +63,10 @@ export function ShotDisplay() {
|
||||
outlineMaterial,
|
||||
).computeLineDistances();
|
||||
|
||||
splashRadiusWrapper.current?.add(outline);
|
||||
splashRadiusWrapper.current.add(outline);
|
||||
|
||||
return () => {
|
||||
splashRadiusWrapper.current?.remove(outline);
|
||||
splashRadiusWrapper.current.remove(outline);
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -86,44 +85,6 @@ export function ShotDisplay() {
|
||||
}
|
||||
}, [shot]);
|
||||
|
||||
useFrame(({ clock }) => {
|
||||
if (!shot) return;
|
||||
|
||||
const t = clock.elapsedTime;
|
||||
|
||||
if (inTracer.current) {
|
||||
const tracerT1 = t % 1;
|
||||
const tracerT2 = (t + 0.5) % 1;
|
||||
|
||||
inTracer.current.scale.set(1, 1 - 2 * Math.abs(tracerT1 - 0.5), 1);
|
||||
inTracer.current.position.set(0, (1 - tracerT1) * inLength, 0);
|
||||
|
||||
if (outTracer.current) {
|
||||
outTracer.current.scale.set(1, 1 - 2 * Math.abs(tracerT2 - 0.5), 1);
|
||||
outTracer.current.position.set(0, tracerT2 * outLength, 0);
|
||||
}
|
||||
|
||||
invalidate();
|
||||
}
|
||||
|
||||
if (
|
||||
shot.splashRadius !== undefined &&
|
||||
splashRadiusWrapper.current &&
|
||||
splashRadiusMaterial.current
|
||||
) {
|
||||
if (animationStartTime === null) animationStartTime = t;
|
||||
|
||||
const scale =
|
||||
clamp(4 * (t - animationStartTime), 0, 1) * shot.splashRadius;
|
||||
const opacity = 1 - clamp(2 * (t - animationStartTime), 0, 1);
|
||||
|
||||
splashRadiusWrapper.current.scale.set(scale, scale, scale);
|
||||
splashRadiusMaterial.current.opacity = opacity;
|
||||
|
||||
invalidate();
|
||||
}
|
||||
});
|
||||
|
||||
if (!shot) return null;
|
||||
|
||||
const inLength =
|
||||
@@ -149,6 +110,15 @@ export function ShotDisplay() {
|
||||
|
||||
return (
|
||||
<group>
|
||||
<Animator
|
||||
inTracer={inTracer}
|
||||
outTracer={outTracer}
|
||||
inLength={inLength}
|
||||
outLength={outLength}
|
||||
splashRadiusWrapper={splashRadiusWrapper}
|
||||
splashRadiusMaterial={splashRadiusMaterial}
|
||||
/>
|
||||
|
||||
{shot.splashRadius && (
|
||||
<group
|
||||
position={inLast.point}
|
||||
@@ -283,3 +253,59 @@ export function ShotDisplay() {
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
interface AnimatorProps {
|
||||
inTracer: RefObject<Mesh>;
|
||||
outTracer: RefObject<Mesh>;
|
||||
|
||||
splashRadiusWrapper: RefObject<Group>;
|
||||
splashRadiusMaterial: RefObject<MeshBasicMaterial>;
|
||||
|
||||
inLength: number;
|
||||
outLength: number;
|
||||
}
|
||||
|
||||
function Animator({
|
||||
inTracer,
|
||||
outTracer,
|
||||
splashRadiusWrapper,
|
||||
splashRadiusMaterial,
|
||||
inLength,
|
||||
outLength,
|
||||
}: AnimatorProps) {
|
||||
const shot = Tankopedia.use((state) => state.shot!);
|
||||
|
||||
let animationStartTime: number | null = null;
|
||||
|
||||
useFrame(({ clock, invalidate }) => {
|
||||
const t = clock.elapsedTime;
|
||||
|
||||
const tracerT1 = t % 1;
|
||||
const tracerT2 = (t + 0.5) % 1;
|
||||
|
||||
inTracer.current.scale.set(1, 1 - 2 * Math.abs(tracerT1 - 0.5), 1);
|
||||
inTracer.current.position.set(0, (1 - tracerT1) * inLength, 0);
|
||||
|
||||
if (outTracer.current) {
|
||||
outTracer.current.scale.set(1, 1 - 2 * Math.abs(tracerT2 - 0.5), 1);
|
||||
outTracer.current.position.set(0, tracerT2 * outLength, 0);
|
||||
}
|
||||
|
||||
invalidate();
|
||||
|
||||
if (shot.splashRadius !== undefined) {
|
||||
if (animationStartTime === null) animationStartTime = t;
|
||||
|
||||
const scale =
|
||||
clamp(4 * (t - animationStartTime), 0, 1) * shot.splashRadius;
|
||||
const opacity = 1 - clamp(2 * (t - animationStartTime), 0, 1);
|
||||
|
||||
splashRadiusWrapper.current.scale.set(scale, scale, scale);
|
||||
splashRadiusMaterial.current.opacity = opacity;
|
||||
|
||||
invalidate();
|
||||
}
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import { createPortal, useFrame, useThree } from '@react-three/fiber';
|
||||
import { memo, useState } from 'react';
|
||||
import { DepthTexture, Scene, Vector2 } from 'three';
|
||||
import { PrimaryArmorScene } from './components/PrimaryArmorScene';
|
||||
import { spacedArmorRenderTarget } from './components/PrimaryArmorSceneComponent/target';
|
||||
import { SpacedArmorScene } from './components/SpacedArmorScene';
|
||||
import { createPortal, useFrame, useThree } from "@react-three/fiber";
|
||||
import { memo, useState } from "react";
|
||||
import { DepthTexture, Scene, Vector2 } from "three";
|
||||
import { PrimaryArmorScene } from "./components/PrimaryArmorScene";
|
||||
import { spacedArmorRenderTarget } from "./components/PrimaryArmorSceneComponent/target";
|
||||
import { SpacedArmorScene } from "./components/SpacedArmorScene";
|
||||
|
||||
export const Armor = memo(() => {
|
||||
const rootScene = useThree((state) => state.scene);
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { useFrame } from '@react-three/fiber';
|
||||
import { useFrame } from "@react-three/fiber";
|
||||
|
||||
export const zoom = 60;
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { useFrame } from '@react-three/fiber';
|
||||
import { useFrame } from "@react-three/fiber";
|
||||
|
||||
export function AutoClear() {
|
||||
useFrame(({ gl }) => {
|
||||
|
||||
+34
-26
@@ -19,6 +19,8 @@ const poseDistances: Record<Pose, number> = {
|
||||
[Pose.Default]: -1,
|
||||
};
|
||||
|
||||
const inspectModeInitialPosition = new Vector3(-8, 2, -13);
|
||||
|
||||
const modelDefinitions = await awaitableModelDefinitions;
|
||||
|
||||
interface ControlsProps {
|
||||
@@ -77,22 +79,6 @@ export function Controls({
|
||||
protagonistHullOrigin.y +
|
||||
protagonistTurretOrigin.y +
|
||||
protagonistGunOrigin.y;
|
||||
const inspectModeInitialPosition = new Vector3(-8, 2, -13);
|
||||
|
||||
const t0 = useRef(Date.now() / 1000);
|
||||
useFrame(() => {
|
||||
if (!doAutoRotate) return;
|
||||
|
||||
const now = Date.now() / 1000;
|
||||
const t = now - t0.current;
|
||||
|
||||
camera.position
|
||||
.copy(inspectModeInitialPosition)
|
||||
.applyAxisAngle(I_HAT, (Math.PI / 32) * Math.sin(t / 9))
|
||||
.applyAxisAngle(J_HAT, (-Math.PI / 16) * Math.sin(t / 7));
|
||||
|
||||
invalidate();
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
function handleControlsEnabled(event: QuicklimeEvent<boolean>) {
|
||||
@@ -249,15 +235,37 @@ export function Controls({
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<OrbitControls
|
||||
enableRotate={enableRotate}
|
||||
maxDistance={40}
|
||||
minDistance={5}
|
||||
enableZoom={zoomable}
|
||||
zoomSpeed={zoomable ? undefined : 0}
|
||||
ref={orbitControls}
|
||||
enabled={controlsEnabledEvent.last!}
|
||||
enableDamping={false}
|
||||
/>
|
||||
<>
|
||||
{doAutoRotate && <Animator />}
|
||||
|
||||
<OrbitControls
|
||||
enableRotate={enableRotate}
|
||||
maxDistance={40}
|
||||
minDistance={5}
|
||||
enableZoom={zoomable}
|
||||
zoomSpeed={zoomable ? undefined : 0}
|
||||
ref={orbitControls}
|
||||
enabled={controlsEnabledEvent.last!}
|
||||
enableDamping={false}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function Animator() {
|
||||
const clock = useThree((state) => state.clock);
|
||||
const t0 = useRef(clock.elapsedTime);
|
||||
|
||||
useFrame(({ camera }) => {
|
||||
const t = clock.elapsedTime - t0.current;
|
||||
|
||||
camera.position
|
||||
.copy(inspectModeInitialPosition)
|
||||
.applyAxisAngle(I_HAT, (Math.PI / 32) * Math.sin(t / 9))
|
||||
.applyAxisAngle(J_HAT, (-Math.PI / 16) * Math.sin(t / 7));
|
||||
|
||||
invalidate();
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
+99
-73
@@ -1,63 +1,127 @@
|
||||
import { useFrame, useThree } from "@react-three/fiber";
|
||||
import { times } from "lodash-es";
|
||||
import { useFrame } from "@react-three/fiber";
|
||||
import { clamp, times } from "lodash-es";
|
||||
import { Quicklime } from "quicklime";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useEffect, useRef, useState, type RefObject } from "react";
|
||||
import { HemisphereLight, SpotLight, type Group } from "three";
|
||||
import { clamp, degToRad, lerp } from "three/src/math/MathUtils.js";
|
||||
import { degToRad, lerp } from "three/src/math/MathUtils.js";
|
||||
import { Tankopedia } from "../../../../../../stores/tankopedia";
|
||||
import { TankopediaPersistent } from "../../../../../../stores/tankopediaPersistent";
|
||||
import { HelpingSpotLight } from "../../../../../HelpingSpotLight";
|
||||
|
||||
const ANGLE = Math.PI * 2 ** -2;
|
||||
const ANGLE = degToRad(10);
|
||||
const REVEAL_ANIMATION_TIME = 3;
|
||||
const TRANSITION_ANIMATION_TIME = 0.5;
|
||||
|
||||
const LIGHTS_COUNT = 5;
|
||||
const THETA_OFFSET = degToRad(-152);
|
||||
const LIGHT_DISTANCE = 13;
|
||||
const LIGHTS_COUNT = 4;
|
||||
const THETA_OFFSET = degToRad(180 - 45);
|
||||
const LIGHT_DISTANCE = 20;
|
||||
const LIGHT_HEIGHT_0 = 4;
|
||||
const LIGHT_HEIGHT_1 = 2;
|
||||
const INTENSITY_0 = 3.5;
|
||||
const INTENSITY_1 = 1.5;
|
||||
const LIGHT_HEIGHT_1 = 6;
|
||||
const INTENSITY_0 = 6;
|
||||
const INTENSITY_1 = 3;
|
||||
const HEMISPHERE_INTENSITY = 2;
|
||||
|
||||
export const transitionEvent = new Quicklime<number>(0);
|
||||
|
||||
export function Lighting() {
|
||||
const wrapper = useRef<Group>(null!);
|
||||
const t0 = useRef(0);
|
||||
const transition = useRef(false);
|
||||
const clock = useThree((state) => state.clock);
|
||||
const display = Tankopedia.use((state) => state.display);
|
||||
const requestedDisplay = Tankopedia.use((state) => state.requestedDisplay);
|
||||
const animationTime = useRef(REVEAL_ANIMATION_TIME);
|
||||
|
||||
const highGraphics = TankopediaPersistent.use((state) => state.highGraphics);
|
||||
const requestedDisplay = Tankopedia.use((state) => state.requestedDisplay);
|
||||
|
||||
const animationTime = useRef(REVEAL_ANIMATION_TIME);
|
||||
const t0 = useRef(performance.now() / 1e3 - animationTime.current);
|
||||
|
||||
const isRevealing = useRef(true);
|
||||
|
||||
const [animate, setAnimate] = useState(true);
|
||||
|
||||
console.log(animate);
|
||||
|
||||
useEffect(() => {
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.revealed = true;
|
||||
});
|
||||
|
||||
t0.current = performance.now() / 1000 - REVEAL_ANIMATION_TIME;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (isRevealing.current) {
|
||||
isRevealing.current = false;
|
||||
} else {
|
||||
animationTime.current = TRANSITION_ANIMATION_TIME;
|
||||
t0.current = performance.now() / 1e3;
|
||||
}
|
||||
|
||||
setAnimate(true);
|
||||
}, [requestedDisplay]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{animate && (
|
||||
<Animator
|
||||
t0={t0}
|
||||
animationTime={animationTime}
|
||||
wrapper={wrapper}
|
||||
stop={() => {
|
||||
setAnimate(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<group ref={wrapper}>
|
||||
<hemisphereLight intensity={0} color="#ffffff" groundColor="#afafaf" />
|
||||
|
||||
{times(LIGHTS_COUNT, (index) => {
|
||||
const x = index / (LIGHTS_COUNT - 1);
|
||||
const theta = 2 * Math.PI * (index / LIGHTS_COUNT) + THETA_OFFSET;
|
||||
const position = [
|
||||
LIGHT_DISTANCE * Math.sin(theta),
|
||||
lerp(LIGHT_HEIGHT_0, LIGHT_HEIGHT_1, x),
|
||||
LIGHT_DISTANCE * Math.cos(theta),
|
||||
] as const;
|
||||
const intensity = lerp(INTENSITY_0, INTENSITY_1, x);
|
||||
|
||||
return (
|
||||
<HelpingSpotLight
|
||||
userData={{ index }}
|
||||
key={index}
|
||||
position={position}
|
||||
intensity={intensity}
|
||||
penumbra={1}
|
||||
castShadow={highGraphics}
|
||||
decay={0}
|
||||
color="#ffffff"
|
||||
angle={0}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</group>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
interface AnimatorProps {
|
||||
stop: () => void;
|
||||
|
||||
t0: RefObject<number>;
|
||||
animationTime: RefObject<number>;
|
||||
wrapper: RefObject<Group>;
|
||||
}
|
||||
|
||||
function Animator({ stop, t0, animationTime, wrapper }: AnimatorProps) {
|
||||
const requestedDisplay = Tankopedia.use((state) => state.requestedDisplay);
|
||||
|
||||
useFrame(({ invalidate }) => {
|
||||
if (transition.current) {
|
||||
transition.current = false;
|
||||
t0.current = performance.now() / 1000;
|
||||
const dt = performance.now() / 1e3 - t0.current;
|
||||
const x = clamp(dt / animationTime.current, 0, 2);
|
||||
|
||||
if (x === 2) {
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
|
||||
const x = clamp(
|
||||
(performance.now() / 1000 - t0.current) / animationTime.current,
|
||||
0,
|
||||
2,
|
||||
);
|
||||
const t = (0.5 * Math.sin(Math.PI * (x + 0.5)) + 0.5) ** 2;
|
||||
|
||||
if (t !== transitionEvent.last) {
|
||||
transitionEvent.dispatch(t);
|
||||
}
|
||||
|
||||
for (const child of wrapper.current.children) {
|
||||
if (child instanceof SpotLight) {
|
||||
child.angle = ANGLE * t;
|
||||
@@ -66,53 +130,15 @@ export function Lighting() {
|
||||
}
|
||||
}
|
||||
|
||||
if (x < 2) invalidate();
|
||||
transitionEvent.dispatch(t);
|
||||
invalidate();
|
||||
|
||||
if (
|
||||
x > 1 &&
|
||||
Tankopedia.state.display !== Tankopedia.state.requestedDisplay
|
||||
) {
|
||||
if (x >= 1 && Tankopedia.state.display !== requestedDisplay) {
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.display = requestedDisplay;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (requestedDisplay === display) return;
|
||||
|
||||
transition.current = true;
|
||||
animationTime.current = TRANSITION_ANIMATION_TIME;
|
||||
}, [requestedDisplay]);
|
||||
|
||||
return (
|
||||
<group ref={wrapper}>
|
||||
<hemisphereLight intensity={0} color="#ffffff" groundColor="#afafaf" />
|
||||
|
||||
{times(LIGHTS_COUNT, (index) => {
|
||||
const x = index / (LIGHTS_COUNT - 1);
|
||||
const theta = 2 * Math.PI * (index / LIGHTS_COUNT) + THETA_OFFSET;
|
||||
const position = [
|
||||
LIGHT_DISTANCE * Math.sin(theta),
|
||||
lerp(LIGHT_HEIGHT_0, LIGHT_HEIGHT_1, x),
|
||||
LIGHT_DISTANCE * Math.cos(theta),
|
||||
] as const;
|
||||
const intensity = lerp(INTENSITY_0, INTENSITY_1, x);
|
||||
|
||||
return (
|
||||
<HelpingSpotLight
|
||||
userData={{ index }}
|
||||
key={index}
|
||||
position={position}
|
||||
intensity={intensity}
|
||||
penumbra={1}
|
||||
castShadow={highGraphics}
|
||||
decay={0}
|
||||
color="#ffffff"
|
||||
angle={0}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</group>
|
||||
);
|
||||
return null;
|
||||
}
|
||||
+2
-10
@@ -1,10 +1,9 @@
|
||||
import { invalidate, useFrame } from "@react-three/fiber";
|
||||
import { clamp } from "lodash-es";
|
||||
import { invalidate } from "@react-three/fiber";
|
||||
import { Quicklime, type QuicklimeEvent } from "quicklime";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { Mesh, MeshStandardMaterial } from "three";
|
||||
|
||||
const SIZE = 2 ** 6;
|
||||
const SIZE = 14;
|
||||
|
||||
export const screenshotReadyEvent = new Quicklime(false);
|
||||
|
||||
@@ -12,11 +11,6 @@ export function SceneProps() {
|
||||
const mesh = useRef<Mesh>(null!);
|
||||
const material = useRef<MeshStandardMaterial>(null);
|
||||
|
||||
useFrame(({ camera }) => {
|
||||
if (!material.current) return;
|
||||
material.current.opacity = clamp(0.5 * camera.position.y, 0, 0.5);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
function handleScreenshotReady(event: QuicklimeEvent<boolean>) {
|
||||
mesh.current.visible = !event.data;
|
||||
@@ -36,11 +30,9 @@ export function SceneProps() {
|
||||
receiveShadow
|
||||
visible={!screenshotReadyEvent.last!}
|
||||
ref={mesh}
|
||||
renderOrder={-1}
|
||||
>
|
||||
<planeGeometry args={[SIZE, SIZE]} />
|
||||
<meshStandardMaterial
|
||||
transparent
|
||||
ref={material}
|
||||
color="black"
|
||||
roughness={1}
|
||||
|
||||
+1
-1
@@ -156,7 +156,7 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
|
||||
localClippingEnabled: true,
|
||||
preserveDrawingBuffer: true,
|
||||
}}
|
||||
shadows="soft"
|
||||
shadows="basic"
|
||||
onPointerDown={handlePointerDown}
|
||||
onPointerMissed={() => {
|
||||
Tankopedia.mutate((draft) => {
|
||||
|
||||
@@ -40,5 +40,5 @@ export const TankopediaPersistent = new Varuna<TankopediaPersistent>(
|
||||
direction: "ascending",
|
||||
},
|
||||
},
|
||||
"tankopedia-2"
|
||||
"tankopedia-3",
|
||||
);
|
||||
Reference in new issue
Block a user